Compare commits
25 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 |
66 changed files with 1773 additions and 1004 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 {
|
namespace Neighbourhood.omg.lol {
|
||||||
public class NavigatorService {
|
public class NavigatorService {
|
||||||
internal NavigationManager? NavigationManager { get; set; }
|
internal NavigationManager? NavigationManager { get; set; }
|
||||||
|
internal Page? Page { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,362 +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 T? Deserialize<T>(string str) {
|
|
||||||
T? responseObj = default(T);
|
|
||||||
try {
|
|
||||||
responseObj = JsonSerializer.Deserialize<T>(str, _serializerOptions);
|
|
||||||
}
|
|
||||||
catch (JsonException ex) {
|
|
||||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
|
||||||
Debug.WriteLine(str);
|
|
||||||
}
|
|
||||||
return responseObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddToken(string? token = null) {
|
|
||||||
if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
|
|
||||||
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 == null || (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?> PostBinary<TResponse, TData>(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
|
||||||
TResponse? responseData = default(TResponse);
|
|
||||||
try {
|
|
||||||
if (fileResult != null) using (var fileStream = await fileResult.OpenReadAsync()) {
|
|
||||||
Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
|
|
||||||
if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
|
|
||||||
else if (!url.Query.Contains("binary")) uri += "&binary";
|
|
||||||
|
|
||||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
|
||||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(fileResult.ContentType ?? "application/octet-stream");
|
|
||||||
fileStreamContent.Headers.ContentLength = fileStream.Length;
|
|
||||||
HttpResponseMessage response = await _client.PostAsync(uri, fileStreamContent, cancellationToken: cancellationToken);
|
|
||||||
string str = await response.Content.ReadAsStringAsync();
|
|
||||||
if (response.IsSuccessStatusCode) {
|
|
||||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
|
||||||
if (responseObj?.Request?.Success ?? false) {
|
|
||||||
responseData = responseObj.Response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TResponse?> PostMultipart<TResponse, TData>(string uri, TData? data = null, FileResult? fileResult = null, CancellationToken cancellationToken = default)
|
|
||||||
where TResponse : IOmgLolResponseData where TData : class
|
|
||||||
{
|
|
||||||
if(fileResult != null) {
|
|
||||||
using (var fileStream = await fileResult.OpenReadAsync())
|
|
||||||
return await PostMultipart<TResponse, TData>(uri, data: data, fileStream: fileStream, fileName: fileResult.FileName, contentType: fileResult.ContentType);
|
|
||||||
}
|
|
||||||
else return await PostMultipart<TResponse, TData>(uri, data, fileStream: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TResponse?> PostMultipart<TResponse, TData>(string uri, TData? data = null, Stream? fileStream = null, string? fileName = null, string? contentType = null, CancellationToken cancellationToken = default)
|
|
||||||
where TResponse : IOmgLolResponseData where TData : class
|
|
||||||
{
|
|
||||||
TResponse? responseData = default;
|
|
||||||
try {
|
|
||||||
using (MultipartFormDataContent formData = new MultipartFormDataContent()) {
|
|
||||||
if(fileStream != null) {
|
|
||||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
|
||||||
fileStreamContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
|
|
||||||
Name = "\"file\"",
|
|
||||||
FileName = $"\"{fileName}\"" ?? "\"unknown\""
|
|
||||||
};
|
|
||||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType ?? "application/octet-stream");
|
|
||||||
formData.Add(fileStreamContent);
|
|
||||||
}
|
|
||||||
if (data != null) {
|
|
||||||
HttpContent jsonContent = JsonContent.Create(data, options: _serializerOptions);
|
|
||||||
formData.Add(jsonContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponseMessage response = await _client.PostAsync(uri, formData, cancellationToken: cancellationToken);
|
|
||||||
string str = await response.Content.ReadAsStringAsync();
|
|
||||||
if (response.IsSuccessStatusCode) {
|
|
||||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
|
||||||
if (responseObj?.Request?.Success ?? false) {
|
|
||||||
responseData = responseObj.Response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TResponse?> Put<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
|
||||||
TResponse? responseData = default(TResponse);
|
|
||||||
try {
|
|
||||||
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<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, object>($"/address/{address}/pfp", fileResult: image);
|
|
||||||
|
|
||||||
public async Task<string?> OAuth(string code, string client_id, string client_secret, string redirect_uri) {
|
|
||||||
string? token = null;
|
|
||||||
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,7 +24,7 @@ namespace Neighbourhood.omg.lol {
|
||||||
public List<MarkupString>? EphemeralMessages { get; set; }
|
public List<MarkupString>? EphemeralMessages { get; set; }
|
||||||
public List<string>? AddressDirectory { 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; }
|
public Dictionary<string, Theme>? Themes { get; set; }
|
||||||
|
|
||||||
|
|
@ -73,6 +73,7 @@ namespace Neighbourhood.omg.lol {
|
||||||
// data for selected address
|
// data for selected address
|
||||||
public List<Status>? CachedAddressStatuses { get; set; }
|
public List<Status>? CachedAddressStatuses { get; set; }
|
||||||
public List<Pic>? CachedAddressPics { get; set; }
|
public List<Pic>? CachedAddressPics { get; set; }
|
||||||
|
public List<Paste>? CachedAddressPastes { get; set; }
|
||||||
public MarkupString? CachedAddressBio { get; set; }
|
public MarkupString? CachedAddressBio { get; set; }
|
||||||
private string? _cachedAddress;
|
private string? _cachedAddress;
|
||||||
public string? CachedAddress {
|
public string? CachedAddress {
|
||||||
|
|
@ -82,6 +83,7 @@ namespace Neighbourhood.omg.lol {
|
||||||
_cachedAddress = value;
|
_cachedAddress = value;
|
||||||
CachedAddressStatuses = new List<Status>();
|
CachedAddressStatuses = new List<Status>();
|
||||||
CachedAddressPics = new List<Pic>();
|
CachedAddressPics = new List<Pic>();
|
||||||
|
CachedAddressPastes = new List<Paste>();
|
||||||
CachedAddressBio = null;
|
CachedAddressBio = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +157,9 @@ namespace Neighbourhood.omg.lol {
|
||||||
}
|
}
|
||||||
|
|
||||||
// api service
|
// api service
|
||||||
private RestService api { get; set; }
|
private ApiService api { get; set; }
|
||||||
|
|
||||||
public State(RestService restService) {
|
public State(ApiService restService) {
|
||||||
api = restService;
|
api = restService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +226,7 @@ namespace Neighbourhood.omg.lol {
|
||||||
public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) {
|
public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) {
|
||||||
CachedAddress = address;
|
CachedAddress = address;
|
||||||
if (forceRefresh || CachedAddressBio == null) {
|
if (forceRefresh || CachedAddressBio == null) {
|
||||||
CachedAddressBio = await api.StatuslogBio(address);
|
CachedAddressBio = Utilities.MdToHtmlMarkup(await api.StatuslogBio(address));
|
||||||
}
|
}
|
||||||
return CachedAddressBio;
|
return CachedAddressBio;
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +286,19 @@ namespace Neighbourhood.omg.lol {
|
||||||
return CachedAddressPics;
|
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() {
|
public async Task RefreshStatuses() {
|
||||||
await GetStatuses(forceRefresh: true);
|
await GetStatuses(forceRefresh: true);
|
||||||
if(SelectedAddressName != null)
|
if(SelectedAddressName != null)
|
||||||
|
|
@ -296,12 +311,18 @@ namespace Neighbourhood.omg.lol {
|
||||||
}
|
}
|
||||||
public async Task RefreshNow() => await GetNowGarden(forceRefresh: true);
|
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) {
|
if(forceRefresh || Feed == null || Feed.Count == 0) {
|
||||||
Feed = new List<StatusOrPic>();
|
Feed = new List<FeedItem>();
|
||||||
foreach(string address in Following ?? new List<string>()) {
|
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 GetStatuses(address, forceRefresh))?.Select(s => new FeedItem { Status = s }) ?? new List<FeedItem>());
|
||||||
Feed.AddRange((await GetPics(address, forceRefresh))?.Select(p => new StatusOrPic { Pic = p }) ?? new List<StatusOrPic>());
|
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);
|
return Feed.OrderByDescending(s => s.CreatedTime);
|
||||||
|
|
|
||||||
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 IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
|
|
||||||
<div class="overlay" data-ui="#@id"></div>
|
<div class="overlay" data-ui="#@id"></div>
|
||||||
<dialog id="@id">
|
<dialog id="@id">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject NavigationManager navigationManager
|
@inject NavigationManager navigationManager
|
||||||
|
|
||||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||||
|
|
@ -57,11 +57,8 @@
|
||||||
loading = true;
|
loading = true;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
//TODO: upload the profile pic
|
|
||||||
//PutPicResponseData? response = await api.PutPic(State.SelectedAddressName!, Base64File!);
|
|
||||||
if (Base64File != null && File != null)
|
if (Base64File != null && File != null)
|
||||||
{
|
{
|
||||||
// using var fileStream = await File.OpenReadAsync();
|
|
||||||
BasicResponseData? response = await api.PostProfilePic(Address!, File);
|
BasicResponseData? response = await api.PostProfilePic(Address!, File);
|
||||||
if (response != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
|
|
||||||
<div class="overlay" data-ui="#@id"></div>
|
<div class="overlay" data-ui="#@id"></div>
|
||||||
<dialog id="@id">
|
<dialog id="@id">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@if(Html != null) {
|
@if(Html != null) {
|
||||||
<iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe>
|
<iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@
|
||||||
<i class="fa-duotone fa-seedling"></i>
|
<i class="fa-duotone fa-seedling"></i>
|
||||||
<span>/Now</span>
|
<span>/Now</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="indent row" href="/person/@State.SelectedAddressName#pastebin">
|
||||||
|
<i class="fa-solid fa-clipboard"></i>
|
||||||
|
<span>Pastebin</span>
|
||||||
|
</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject NavigationManager navigationManager
|
@inject NavigationManager navigationManager
|
||||||
|
|
||||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject NavigationManager navigationManager
|
@inject NavigationManager navigationManager
|
||||||
|
|
||||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject NavigationManager navigationManager
|
@inject NavigationManager navigationManager
|
||||||
|
@inject NavigatorService navigatorService
|
||||||
|
|
||||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||||
<dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active">
|
<dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active">
|
||||||
|
|
@ -27,7 +28,8 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="field textarea border max">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<nav class="right-align no-space">
|
<nav class="right-align no-space">
|
||||||
|
|
@ -66,16 +68,26 @@
|
||||||
|
|
||||||
public async Task PostStatus() {
|
public async Task PostStatus() {
|
||||||
|
|
||||||
StatusPost post = new StatusPost
|
StatusPost post = new StatusPost {
|
||||||
{
|
|
||||||
Emoji = Emoji,
|
Emoji = Emoji,
|
||||||
Content = Content
|
Content = Content
|
||||||
};
|
};
|
||||||
|
|
||||||
if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false){
|
if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false) {
|
||||||
post.SkipMastodonPost = !postToMastodon;
|
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;
|
loading = true;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
var result = await api.StatusPost(State!.SelectedAddressName!, post);
|
var result = await api.StatusPost(State!.SelectedAddressName!, post);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
<li>
|
<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">
|
<img class="circle avatar responsive" src="https://profiles.cache.lol/@linkAddress/picture">
|
||||||
<span>@displayAddress</span>
|
<span>@displayAddress</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@page "/editNow"
|
@page "/editNow"
|
||||||
@inject NavigationManager Nav
|
@inject NavigationManager Nav
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
|
|
@ -11,7 +11,9 @@
|
||||||
@bind-Value="@markdownValue"
|
@bind-Value="@markdownValue"
|
||||||
Theme="material-darker"
|
Theme="material-darker"
|
||||||
MaxHeight="100%"
|
MaxHeight="100%"
|
||||||
CustomButtonClicked="@OnCustomButtonClicked">
|
CustomButtonClicked="@OnCustomButtonClicked"
|
||||||
|
AutoDownloadFontAwesome="false"
|
||||||
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
<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.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@page "/editProfile"
|
@page "/editProfile"
|
||||||
@inject NavigationManager Nav
|
@inject NavigationManager Nav
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
|
|
@ -11,7 +11,9 @@
|
||||||
@bind-Value="@markdownValue"
|
@bind-Value="@markdownValue"
|
||||||
Theme="material-darker"
|
Theme="material-darker"
|
||||||
MaxHeight="100%"
|
MaxHeight="100%"
|
||||||
CustomButtonClicked="@OnCustomButtonClicked">
|
CustomButtonClicked="@OnCustomButtonClicked"
|
||||||
|
AutoDownloadFontAwesome="false"
|
||||||
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
<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.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,16 @@ else {
|
||||||
<div class="responsive page-container">
|
<div class="responsive page-container">
|
||||||
<div id="feed" class="page no-padding active">
|
<div id="feed" class="page no-padding active">
|
||||||
@if (feed != null){
|
@if (feed != null){
|
||||||
foreach (StatusOrPic item in feed) {
|
foreach (FeedItem item in feed) {
|
||||||
if (item.IsStatus) {
|
if (item.IsStatus) {
|
||||||
<StatusCard Status="@item.Status"></StatusCard>
|
<StatusCard Status="@item.Status"></StatusCard>
|
||||||
}
|
}
|
||||||
else if (item.IsPic) {
|
else if (item.IsPic) {
|
||||||
<PicCard Pic="@item.Pic"></PicCard>
|
<PicCard Pic="@item.Pic"></PicCard>
|
||||||
}
|
}
|
||||||
|
else if (item.IsPaste) {
|
||||||
|
<PasteCard Paste="@item.Paste"></PasteCard>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<LoadingCard id="feedLoading" icon="fa-solid fa-list-timeline"></LoadingCard>
|
<LoadingCard id="feedLoading" icon="fa-solid fa-list-timeline"></LoadingCard>
|
||||||
|
|
@ -75,7 +78,7 @@ else {
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private IOrderedEnumerable<StatusOrPic>? feed;
|
private IOrderedEnumerable<FeedItem>? feed;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<article class="now">
|
<article class="now">
|
||||||
<nav>
|
<nav>
|
||||||
<a class="author" href="/person/@now.Address#now">
|
<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>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
<nav>
|
<nav>
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@
|
||||||
<span>/Now</span>
|
<span>/Now</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
<a data-ui="#pastebin">
|
||||||
|
<i class="fa-solid fa-clipboard"></i>
|
||||||
|
<span>Paste.lol</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -79,6 +83,12 @@
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</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>
|
<StatusList @ref="StatusList" StatusFunc="@(async(refresh) => await State.GetStatuses(Address, refresh))" Editable="@IsMe"></StatusList>
|
||||||
@if(IsMe) {
|
@if(IsMe) {
|
||||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||||
|
|
@ -109,6 +119,15 @@
|
||||||
}
|
}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
@ -120,6 +139,7 @@
|
||||||
_address = value;
|
_address = value;
|
||||||
if (StatusList != null) StatusList.StatusFunc = async (refresh) => await State.GetStatuses(_address, refresh);
|
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 (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 {
|
public string ProfileUrl {
|
||||||
|
|
@ -135,6 +155,7 @@
|
||||||
|
|
||||||
private StatusList? StatusList { get; set; }
|
private StatusList? StatusList { get; set; }
|
||||||
private PicList? PicList { get; set; }
|
private PicList? PicList { get; set; }
|
||||||
|
private PasteList? PasteList { get; set; }
|
||||||
|
|
||||||
private bool IsMe {
|
private bool IsMe {
|
||||||
get => State.AddressList?.Any(a => a.Address == Address) ?? false;
|
get => State.AddressList?.Any(a => a.Address == Address) ?? false;
|
||||||
|
|
|
||||||
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;
|
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() {
|
protected override async Task OnInitializedAsync() {
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
if (PicsFunc == null) return;
|
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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject State State
|
@inject State State
|
||||||
@inject RestService api
|
@inject ApiService api
|
||||||
@inject NavigationManager navigationManager
|
@inject NavigationManager navigationManager
|
||||||
|
|
||||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||||
|
|
@ -73,7 +73,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UseTheme() {
|
public async Task UseTheme() {
|
||||||
// todo: update theme
|
|
||||||
onthemechanged?.Invoke(activeTheme);
|
onthemechanged?.Invoke(activeTheme);
|
||||||
activeTheme = null;
|
activeTheme = null;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using CommunityToolkit.Maui;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -7,12 +8,15 @@ namespace Neighbourhood.omg.lol {
|
||||||
public static MauiApp CreateMauiApp() {
|
public static MauiApp CreateMauiApp() {
|
||||||
var builder = MauiApp.CreateBuilder();
|
var builder = MauiApp.CreateBuilder();
|
||||||
builder
|
builder
|
||||||
.UseMauiApp<App>();
|
.UseMauiApp<App>()
|
||||||
|
.UseMauiCommunityToolkit(options => {
|
||||||
|
options.SetShouldEnableSnackbarOnWindows(true);
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddMauiBlazorWebView();
|
builder.Services.AddMauiBlazorWebView();
|
||||||
builder.Services.AddTransient<LoginWebViewPage>();
|
builder.Services.AddTransient<LoginWebViewPage>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<RestService>();
|
builder.Services.AddSingleton<ApiService>();
|
||||||
builder.Services.AddSingleton<State>();
|
builder.Services.AddSingleton<State>();
|
||||||
builder.Services.AddSingleton<NavigatorService>();
|
builder.Services.AddSingleton<NavigatorService>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,5 @@
|
||||||
public string Email { get; set; } = string.Empty;
|
public string Email { get; set; } = string.Empty;
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public TimeData Created { get; set; } = TimeData.Empty;
|
public TimeData Created { get; set; } = TimeData.Empty;
|
||||||
//TODO: api_key and settings
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
namespace Neighbourhood.omg.lol.Models {
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
public class AddressResponseList : List<AddressResponseData>, IOmgLolResponseList<AddressResponseData> {
|
public class AddressResponseList : List<AddressResponseData>, IOmgLolResponseList<AddressResponseData> {
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
namespace Neighbourhood.omg.lol.Models {
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
public interface IOmgLolResponseData {
|
public interface IOmgLolResponseData {
|
||||||
|
public string Message { 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Neighbourhood.omg.lol.Models {
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
public class StatusPostResponseData : IOmgLolResponseData {
|
public class StatusPostResponseData : IOmgLolResponseData {
|
||||||
public string? Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
public string? Id { get; set; }
|
public string? Id { get; set; }
|
||||||
public string? Url { get; set; }
|
public string? Url { get; set; }
|
||||||
public string? ExternalUrl { get; set; }
|
public string? ExternalUrl { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
namespace Neighbourhood.omg.lol.Models {
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
public class StatusOrPic {
|
public class FeedItem {
|
||||||
public Status? Status { get; set; }
|
public Status? Status { get; set; }
|
||||||
public Pic? Pic { get; set; }
|
public Pic? Pic { get; set; }
|
||||||
|
public Paste? Paste { get; set; }
|
||||||
|
|
||||||
public bool IsStatus { get => Status != null; }
|
public bool IsStatus { get => Status != null; }
|
||||||
public bool IsPic { get => Pic != 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
public string? SampleProfile { get; set; } = string.Empty;
|
public string? SampleProfile { get; set; } = string.Empty;
|
||||||
|
|
||||||
public PreviewCssData? PreviewCssData {
|
public PreviewCssData? PreviewCssData {
|
||||||
get => new RestService().Deserialize<PreviewCssData>(this.PreviewCss);
|
get => new ApiService().Deserialize<PreviewCssData>(this.PreviewCss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,22 +43,22 @@
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-maccatalyst|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-maccatalyst|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android34.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android34.0|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<AndroidKeyStore>True</AndroidKeyStore>
|
<AndroidKeyStore>True</AndroidKeyStore>
|
||||||
<AndroidSigningKeyStore>D:\Neighbourhood.omg.lol\neighbourhood.omg.lol.keystore</AndroidSigningKeyStore>
|
<AndroidSigningKeyStore>D:\_assets\neighbourhood.omg.lol\neighbourhood.omg.lol.keystore</AndroidSigningKeyStore>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
<AndroidSigningStorePass>a!zobzizl</AndroidSigningStorePass>
|
<AndroidSigningStorePass>a!zobzizl</AndroidSigningStorePass>
|
||||||
<AndroidSigningKeyAlias>neighbourhood.omg.lol</AndroidSigningKeyAlias>
|
<AndroidSigningKeyAlias>neighbourhood.omg.lol</AndroidSigningKeyAlias>
|
||||||
<AndroidSigningKeyPass>a!zobzizl</AndroidSigningKeyPass>
|
<AndroidSigningKeyPass>a!zobzizl</AndroidSigningKeyPass>
|
||||||
|
|
@ -66,36 +66,37 @@
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-maccatalyst|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-maccatalyst|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android34.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android34.0|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.19041.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||||
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>7</ApplicationVersion>
|
<ApplicationVersion>9</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- App Icon -->
|
<!-- 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" />
|
<MauiIcon Include="Resources\AppIcon\icon_background.svg" ForegroundFile="Resources\AppIcon\icon_foreground.svg" Color="#f3eb76" BaseSize="1024,1024" />
|
||||||
|
|
||||||
<!-- Splash Screen -->
|
<!-- Splash Screen -->
|
||||||
|
|
@ -115,23 +116,29 @@
|
||||||
<Content Remove="appsettings.json" />
|
<Content Remove="appsettings.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Resources\AppIcon\icon.svg" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="appsettings.json" />
|
<EmbeddedResource Include="appsettings.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Maui" Version="9.0.2" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.70" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.70" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.70" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Maui.Essentials" Version="8.0.40" />
|
<PackageReference Include="Microsoft.Maui.Essentials" Version="8.0.70" />
|
||||||
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" Version="8.0.0" />
|
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" Version="8.0.4" />
|
||||||
|
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="au.death.lol.omg.neighbourhood" android:versionCode="7" android:versionName="0.9.7">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="au.death.lol.omg.neighbourhood" android:versionCode="9" android:versionName="0.9.9">
|
||||||
<application android:allowBackup="true" android:icon="@mipmap/icon_background" android:supportsRtl="true" android:label="omg.lol"></application>
|
<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.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,15 @@ namespace Neighbourhood.omg.lol {
|
||||||
}
|
}
|
||||||
else if (intent.Type.Equals(Intent.ActionSendMultiple)) //Multiple files
|
else if (intent.Type.Equals(Intent.ActionSendMultiple)) //Multiple files
|
||||||
{
|
{
|
||||||
// TODO: we don't really support this at the moment.
|
//NOTE we don't really support recieving multiple files from a share request at the moment.
|
||||||
//System.Collections.IList? uriList;
|
// <!--
|
||||||
//if (OperatingSystem.IsAndroidVersionAtLeast(33))
|
// order:0
|
||||||
|
// -->
|
||||||
|
|
||||||
|
// System.Collections.IList? uriList;
|
||||||
|
// if (OperatingSystem.IsAndroidVersionAtLeast(33))
|
||||||
// uriList = intent.GetParcelableArrayListExtra(Intent.ExtraStream, Java.Lang.Class.FromType(typeof(Android.Net.Uri)));
|
// 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:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
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" />
|
<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:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||||
<uap:SplashScreen Image="$placeholder$.png" />
|
<uap:SplashScreen Image="$placeholder$.png" />
|
||||||
</uap:VisualElements>
|
</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>
|
</Application>
|
||||||
</Applications>
|
</Applications>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,35 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<integer>2</integer>
|
<integer>2</integer>
|
||||||
</array>
|
</array>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array>
|
<array>
|
||||||
<string>arm64</string>
|
<string>arm64</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/appicon.appiconset</string>
|
<string>Assets.xcassets/icon.appiconset</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.9.9</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>14.2</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
37
Resources/AppIcon/icon.svg
Normal file
37
Resources/AppIcon/icon.svg
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0%" x2="0%" y1="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#F8F081" />
|
||||||
|
<stop offset="100%" stop-color="#E1DA51" />
|
||||||
|
</linearGradient>
|
||||||
|
<filter filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" id="filter_1">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
|
||||||
|
<feOffset dx="0" dy="27" />
|
||||||
|
<feGaussianBlur stdDeviation="13.5" />
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2509804 0" />
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect0_dropShadow" />
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 0" />
|
||||||
|
<feOffset dx="0" dy="10" />
|
||||||
|
<feGaussianBlur stdDeviation="10.5" />
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.29803923 0" />
|
||||||
|
<feBlend mode="normal" in2="effect0_dropShadow" result="effect1_dropShadow" />
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="neighbourhood-purple">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="url(#grad1)"/>
|
||||||
|
<path d="M750.419 224.839L750.419 262.111M613.471 208.066L826 306.515M397.732 306.837L610.26 208.388M352.708 208.066L480.225 267.135M197 278.883L350.021 208" id="Shape" fill="none" fill-rule="evenodd" stroke="#8C53E7" stroke-width="29" stroke-linecap="round" stroke-linejoin="round" filter="url(#filter_1)" clip-path="url(#clip_1)" />
|
||||||
|
<g filter="url(#filter_1)" clip-path="url(#clip_1)">
|
||||||
|
<g id="Group" transform="translate(214.02026 338.67798)">
|
||||||
|
<path d="M297.822 476.322C252.091 476.322 206.361 458.882 171.467 424.006L52.3385 304.925C-17.4462 235.169 -17.4462 122.074 52.3385 52.3178C119.763 -15.0793 227.66 -17.3598 297.822 45.4785C367.986 -17.3576 475.883 -15.0771 543.306 52.3178C613.09 122.074 613.09 235.169 543.306 304.925L424.177 424.006C389.286 458.882 343.554 476.322 297.822 476.322" id="Path" fill="#B776FC" stroke="none" />
|
||||||
|
<path d="M248.68 206.983C271.167 243.662 324.478 243.662 346.964 206.983" id="Path" fill="none" stroke="#22184C" stroke-width="22" stroke-linecap="round" />
|
||||||
|
<path d="M325.877 150.757C325.877 139.083 335.344 129.62 347.022 129.62C358.7 129.62 368.167 139.083 368.167 150.757C368.167 162.43 358.7 171.893 347.022 171.893C335.344 171.893 325.877 162.43 325.877 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
|
||||||
|
<path d="M227.357 150.757C227.357 139.083 236.824 129.62 248.502 129.62C260.181 129.62 269.648 139.083 269.648 150.757C269.648 162.43 260.181 171.893 248.502 171.893C236.824 171.893 227.357 162.43 227.357 150.757Z" id="Oval" fill="#22184C" fill-rule="evenodd" stroke="none" />
|
||||||
|
<path d="M72.7875 192.911C72.7875 153.977 104.362 122.416 143.312 122.416C182.261 122.416 213.836 153.977 213.836 192.911C213.836 231.845 182.261 263.407 143.312 263.407C104.362 263.407 72.7875 231.845 72.7875 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
|
||||||
|
<path d="M381.808 192.911C381.808 153.977 413.382 122.416 452.332 122.416C491.281 122.416 522.856 153.977 522.856 192.911C522.856 231.845 491.281 263.407 452.332 263.407C413.382 263.407 381.808 231.845 381.808 192.911Z" id="Oval" fill="#8C53E7" fill-rule="evenodd" stroke="none" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -18,7 +18,8 @@ namespace Neighbourhood.omg.lol {
|
||||||
protected override Window CreateWindow(IActivationState? activationState) {
|
protected override Window CreateWindow(IActivationState? activationState) {
|
||||||
// always create new windows. This allows share intents to not crash on android
|
// always create new windows. This allows share intents to not crash on android
|
||||||
// (with the side effect that multiple windows are opened, which is messy but better than a crash)
|
// (with the side effect that multiple windows are opened, which is messy but better than a crash)
|
||||||
return new Window(new AppShell());
|
NavigatorService.Page = new AppShell();
|
||||||
|
return new Window(NavigatorService.Page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ public partial class LoginWebViewPage : ContentPage
|
||||||
private AuthenticationStateProvider AuthStateProvider { get; set; }
|
private AuthenticationStateProvider AuthStateProvider { get; set; }
|
||||||
private NavigatorService NavigatorService { get; set; }
|
private NavigatorService NavigatorService { get; set; }
|
||||||
private IConfiguration Configuration { get; set; }
|
private IConfiguration Configuration { get; set; }
|
||||||
private RestService api { get; set; }
|
private ApiService api { get; set; }
|
||||||
|
|
||||||
private string? client_id;
|
private string? client_id;
|
||||||
private string? client_secret;
|
private string? client_secret;
|
||||||
private string? redirect_uri;
|
private string? redirect_uri;
|
||||||
|
|
||||||
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService, IConfiguration configuration, RestService restService)
|
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService, IConfiguration configuration, ApiService restService)
|
||||||
{
|
{
|
||||||
this.AuthStateProvider = authStateProvider;
|
this.AuthStateProvider = authStateProvider;
|
||||||
this.NavigatorService = navigatorService;
|
this.NavigatorService = navigatorService;
|
||||||
|
|
|
||||||
63
backlog.md
Normal file
63
backlog.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
- [Pull to refresh #WantToHave](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-177.5
|
||||||
|
-->
|
||||||
|
- [Be a share target for pastes? #WantToHave](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-171.25
|
||||||
|
-->
|
||||||
|
- [Update / manage [PURLs](https://api.omg.lol/#purls) #WantToHave](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-172.5
|
||||||
|
-->
|
||||||
|
- [Combined status / pics posting (upload pic in new status dialog and paste in link) #WantToHave](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-171.875
|
||||||
|
-->
|
||||||
|
- [Update / manage [Weblog](https://api.omg.lol/#weblog) (should probably wait for [Neato](https://neato.pub/)) #WantToHave](#WAITING:)
|
||||||
|
<!--
|
||||||
|
order:0
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [Account settings #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-80
|
||||||
|
-->
|
||||||
|
- [Address preferences #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-90
|
||||||
|
-->
|
||||||
|
- [DNS Records #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-100
|
||||||
|
-->
|
||||||
|
- [Switchboard #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-110
|
||||||
|
-->
|
||||||
|
- [Email forwarding #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-120
|
||||||
|
-->
|
||||||
|
- [Keys #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-130
|
||||||
|
-->
|
||||||
|
- [Proofs #Someday](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-140
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [Slow rendering, especially pics, due to large amount of data #BUG](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-150
|
||||||
|
-->
|
||||||
|
- [Sharing to app while the sharing page is already open does literally nothing #BUG](#TODO:)
|
||||||
|
- Not an issue? Only an issue when you have the app and the sharing app open side-by-side, which is rare?
|
||||||
|
<!--
|
||||||
|
order:-160
|
||||||
|
-->
|
||||||
|
- [Can't select svgs as pics. #BUG](#TODO:)
|
||||||
|
<!--
|
||||||
|
order:-170
|
||||||
|
-->
|
||||||
|
|
@ -1,42 +1,4 @@
|
||||||
/* default styles suppload by template */
|
/* default styles suppload by template */
|
||||||
/*html, body {
|
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a, .btn-link {
|
|
||||||
color: #006bb7;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1b6ec2;
|
|
||||||
border-color: #1861ac;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
|
||||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding-top: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.valid.modified:not([type=checkbox]) {
|
|
||||||
outline: 1px solid #26b050;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invalid {
|
|
||||||
outline: 1px solid #e50000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-message {
|
|
||||||
color: #e50000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#blazor-error-ui {
|
#blazor-error-ui {
|
||||||
background: lightyellow;
|
background: lightyellow;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
@ -49,12 +11,12 @@ h1:focus {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#blazor-error-ui .dismiss {
|
#blazor-error-ui .dismiss {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.75rem;
|
right: 0.75rem;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blazor-error-boundary {
|
.blazor-error-boundary {
|
||||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||||
|
|
@ -62,9 +24,9 @@ h1:focus {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blazor-error-boundary::after {
|
.blazor-error-boundary::after {
|
||||||
content: "An error has occurred."
|
content: "An error has occurred."
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-bar-safe-area {
|
.status-bar-safe-area {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -76,7 +38,6 @@ h1:focus {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: env(safe-area-inset-top);
|
height: env(safe-area-inset-top);
|
||||||
background-color: #f7f7f7;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
107
wwwroot/css/color.css
Normal file
107
wwwroot/css/color.css
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
/* Colours */
|
||||||
|
:root {
|
||||||
|
--background: var(--gray-8);
|
||||||
|
--shadow: var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark {
|
||||||
|
--background: var(--gray-8);
|
||||||
|
--on-background: var(--gray-1);
|
||||||
|
--surface: var(--gray-9);
|
||||||
|
--on-surface: var(--gray-4);
|
||||||
|
--on-surface-variant: var(--gray-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
body.dark {
|
||||||
|
--primary: #6750a4;
|
||||||
|
--on-primary: #ffffff;
|
||||||
|
--primary-container: #e9ddff;
|
||||||
|
--on-primary-container: #22005d;
|
||||||
|
--secondary: #625b71;
|
||||||
|
--on-secondary: #ffffff;
|
||||||
|
--secondary-container: var(--gray-4);
|
||||||
|
--on-secondary-container: #1e192b;
|
||||||
|
--tertiary: #7e5260;
|
||||||
|
--on-tertiary: #ffffff;
|
||||||
|
--tertiary-container: #ffd9e3;
|
||||||
|
--on-tertiary-container: #31101d;
|
||||||
|
--error: #ba1a1a;
|
||||||
|
--on-error: #ffffff;
|
||||||
|
--error-container: #ffdad6;
|
||||||
|
--on-error-container: #410002;
|
||||||
|
--background: var(--gray-0);
|
||||||
|
--on-background: var(--gray-8);
|
||||||
|
--surface: var(--gray-2);
|
||||||
|
--on-surface: var(--gray-9);
|
||||||
|
--surface-variant: var(--gray-1);
|
||||||
|
--on-surface-variant: var(--gray-7);
|
||||||
|
--outline: #7a757f;
|
||||||
|
--outline-variant: #cac4cf;
|
||||||
|
--shadow: #000000;
|
||||||
|
--scrim: #000000;
|
||||||
|
--inverse-surface: var(--gray-1);
|
||||||
|
--inverse-on-surface: var(--gray-9);
|
||||||
|
--inverse-primary: #cfbcff;
|
||||||
|
--surface-dim: #ddd8dd;
|
||||||
|
--surface-bright: #fdf8fd;
|
||||||
|
--surface-container-lowest: #ffffff;
|
||||||
|
--surface-container-low: var(--gray-3);
|
||||||
|
--surface-container: var(--gray-3);
|
||||||
|
--surface-container-high: #ece7eb;
|
||||||
|
--surface-container-highest: #e6e1e6;
|
||||||
|
--overlay: rgb(0 0 0 / .5);
|
||||||
|
--active: rgb(0 0 0 / .1);
|
||||||
|
--elevate1: 0 .125rem .125rem 0 rgb(0 0 0 / .32);
|
||||||
|
--elevate2: 0 .25rem .5rem 0 rgb(0 0 0 / .4);
|
||||||
|
--elevate3: 0 .375rem .75rem 0 rgb(0 0 0 / .48);
|
||||||
|
--secondary-container: var(--gray-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, nav:is(.left,.right) {
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
background-color: var(--surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
color: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
.status nav, .status nav .chip {
|
||||||
|
color: var(--gray-7)
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar::after {
|
||||||
|
background-color: var(--surface);
|
||||||
|
border: 1px dashed var(--on-surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
article.ephemeral {
|
||||||
|
border: 2px dashed var(--outline)
|
||||||
|
}
|
||||||
|
|
||||||
|
article.now {
|
||||||
|
background-color: var(--green-2);
|
||||||
|
color: var(--black)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.row.indent {
|
||||||
|
border-left: 1px solid var(--outline)
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor .editor-preview {
|
||||||
|
background: var(--surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) {
|
||||||
|
background-color: var(--active)
|
||||||
|
}
|
||||||
|
|
||||||
|
#advanced :is(.field.textarea, textarea), .EasyMDEContainer, article.paste code {
|
||||||
|
background-color: #212121;
|
||||||
|
color: #eff
|
||||||
|
}
|
||||||
49
wwwroot/css/icon.css
Normal file
49
wwwroot/css/icon.css
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* Icons and emoji */
|
||||||
|
:root { --emoji-font: TwemojiCountryFlags, SegoeUIEmoji, 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Symbol' }
|
||||||
|
|
||||||
|
.animated[data-emoji|=🫥] { content: url(/vendor/fluent-emoji/1fae5/animated.png) }
|
||||||
|
|
||||||
|
.fa-seedling { color: var(--green-9) !important }
|
||||||
|
.fa-message-smile { color: var(--blue-5) !important }
|
||||||
|
.fa-images { color: var(--orange-5) !important }
|
||||||
|
.fa-id-card { color: var(--red-5) !important }
|
||||||
|
.fa-comment-dots { color: var(--gray-6) !important }
|
||||||
|
.fa-clipboard { color: var(--violet-5) !important }
|
||||||
|
|
||||||
|
i.tiny { ---size: 1em }
|
||||||
|
|
||||||
|
:is(h1,h2,h3,h4,h5,h6) i {
|
||||||
|
---size: 1em;
|
||||||
|
margin-right: .3em;
|
||||||
|
&.fa-at, &:only-child {
|
||||||
|
margin-right: 0;
|
||||||
|
inline-size: auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 i:only-child { margin-block-start: 1rem }
|
||||||
|
|
||||||
|
#new-status-emoji, #status-emoji, :is(.status,.pic) .emoji {
|
||||||
|
margin-bottom: auto;
|
||||||
|
inline-size: 3.5rem;
|
||||||
|
block-size: 3.5rem;
|
||||||
|
font-size: 3.1rem;
|
||||||
|
line-height: 3.5rem;
|
||||||
|
text-indent: -6px
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(#feed) > .pic .emoji { display: none }
|
||||||
|
.chip i { border-radius: 0 }
|
||||||
|
|
||||||
|
i[class*=fa-at] {
|
||||||
|
vertical-align: unset;
|
||||||
|
font-size: .75em
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toolbar :is(.easymde-dropdown, button) { color: inherit }
|
||||||
|
|
||||||
|
.editor-toolbar i.separator {
|
||||||
|
---size: unset;
|
||||||
|
opacity: .25;
|
||||||
|
border-radius: 0
|
||||||
|
}
|
||||||
|
|
@ -2,268 +2,120 @@
|
||||||
@import url(../vendor/color.css);
|
@import url(../vendor/color.css);
|
||||||
@import url(../vendor/type.css);
|
@import url(../vendor/type.css);
|
||||||
|
|
||||||
|
@import url(icon.css);
|
||||||
.animated[data-emoji|="🫥"] { content: url(/vendor/fluent-emoji/1fae5/animated.png); }
|
@import url(type.css);
|
||||||
|
@import url(color.css);
|
||||||
|
@import url(z.css);
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--emoji-font: TwemojiCountryFlags, SegoeUIEmoji, 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Symbol';
|
|
||||||
--font: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif, var(--emoji-font);
|
|
||||||
--prami-svg: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="%23FF6BAE" stroke="none" d="M250 450C211.612 450 173.225 435.354 143.934 406.066L43.9346 306.066C-14.6446 247.487 -14.6446 152.513 43.9346 93.9341C100.533 37.3361 191.104 35.421 250 88.1907C308.898 35.4229 399.47 37.3379 456.066 93.9341C514.645 152.513 514.645 247.487 456.066 306.066L356.066 406.066C326.778 435.354 288.389 450 250 450" /><path fill="none" stroke="%23471036" stroke-width="19" stroke-linecap="round" d="M208.749 223.817C227.625 254.619 272.376 254.619 291.251 223.817" /><circle fill="%23471036" cx="291.3" cy="176.6" r="17.75" /><circle fill="%23471036" cx="208.6" cy="176.6" r="17.75" /><circle fill="%23E24097" cx="120.3" cy="212" r="59.2" /><circle fill="%23E24097" cx="379.7" cy="212" r="59.2" /></svg>');
|
|
||||||
--spacing: 1.5rem;
|
--spacing: 1.5rem;
|
||||||
--radius: .75em;
|
|
||||||
--small-radius: .13em;
|
|
||||||
--color: var(--gray-1);
|
|
||||||
--background: var(--gray-8);
|
|
||||||
--shadow: var(--black);
|
|
||||||
--button-shadow: var(--gray-7);
|
|
||||||
--max-article-size: 75rem;
|
--max-article-size: 75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark {
|
#app {
|
||||||
--background: var(--gray-8);
|
position: relative
|
||||||
--on-background: var(--gray-1);
|
|
||||||
--surface: var(--gray-9);
|
|
||||||
--on-surface: var(--gray-4);
|
|
||||||
/* --surface-variant: #49454e;
|
|
||||||
--on-surface-variant: #cac4cf;
|
|
||||||
--outline: #948f99;
|
|
||||||
--outline-variant: #49454e;
|
|
||||||
--shadow: #000000;
|
|
||||||
--scrim: #000000;
|
|
||||||
--inverse-surface: #e6e1e6;
|
|
||||||
--inverse-on-surface: #313033;
|
|
||||||
--inverse-primary: #6750a4;
|
|
||||||
--surface-dim: #141316;
|
|
||||||
--surface-bright: #3a383c;
|
|
||||||
--surface-container-lowest: #0f0e11;
|
|
||||||
--surface-container-low: #1c1b1e;
|
|
||||||
--surface-container: #201f22;
|
|
||||||
--surface-container-high: #2b292d;
|
|
||||||
--surface-container-highest: #363438;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
nav:is(.left, .right){
|
||||||
font-family: var(--font);
|
margin-block-start: env(safe-area-inset-top);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
main {
|
||||||
font-size: 1.2em;
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a:not(.author) { text-decoration: underline }
|
||||||
text-decoration: underline;
|
:is(nav, .tabs, #directory) a { text-decoration: none }
|
||||||
}
|
|
||||||
|
|
||||||
:is(h1, h2, h3, h4, h5, h6) i {
|
|
||||||
---size: 1em;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(nav) a, .author {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
p, li {
|
|
||||||
line-height: 160%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author, .address, .page-heading, .honey {
|
|
||||||
font-family: 'VC Honey Deck', var(--font);
|
|
||||||
}
|
|
||||||
.author {
|
|
||||||
color: inherit;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-inline-size: 100%;
|
max-inline-size: 100%;
|
||||||
max-block-size: 100%;
|
max-block-size: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(.status,.pic) .emoji {
|
.avatar { position: relative }
|
||||||
margin-bottom: auto;
|
|
||||||
inline-size: 5.5rem;
|
|
||||||
block-size: 5.5rem;
|
|
||||||
font-size: 5rem;
|
|
||||||
line-height: 5.5rem;
|
|
||||||
text-indent: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(.status,.pic) .emoji, #status-emoji, #new-status-emoji {
|
.avatar::after {
|
||||||
margin-bottom: auto;
|
content: "";
|
||||||
inline-size: 3.5rem;
|
position: absolute;
|
||||||
block-size: 3.5rem;
|
left: 0;
|
||||||
font-size: 3.1rem;
|
right: 0;
|
||||||
line-height: 3.5rem;
|
top: 0;
|
||||||
text-indent: -6px;
|
bottom: 0;
|
||||||
}
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
:not(#feed)>.pic .emoji{
|
background-position: center bottom;
|
||||||
display:none;
|
border-radius: 50%
|
||||||
}
|
|
||||||
|
|
||||||
:is(h1, h2, h3, h4, h5, h6) :is(i.fa-at, i:only-child){
|
|
||||||
margin-right: 0;
|
|
||||||
inline-size: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 i:only-child {
|
|
||||||
margin-block-start: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chip i {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status nav .chip, .status nav {
|
|
||||||
color: var(--gray-7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile.avatar {
|
.profile.avatar {
|
||||||
border-radius: .75rem;
|
border-radius: .75rem;
|
||||||
max-block-size: 10rem;
|
max-block-size: 10rem;
|
||||||
max-inline-size: 10rem;
|
max-inline-size: 10rem
|
||||||
}
|
}
|
||||||
|
|
||||||
#bio :is(h1, h2, h3, h4, h5, h6) {
|
:is(h1,h2,h3,h4,h5,h6):is(:focus-visible) {
|
||||||
text-align:center;
|
outline:none;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton {
|
#bio :is(h1,h2,h3,h4,h5,h6) {
|
||||||
animation: skeleton-loading 1s linear infinite alternate;
|
text-align: center;
|
||||||
min-height: 1em;
|
display: block
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes skeleton-loading {
|
|
||||||
0% {
|
|
||||||
background-color: hsl(200, 20%, 70%);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-color: hsl(200, 20%, 95%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.author.skeleton {
|
|
||||||
width: 6em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.skeleton {
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chip.skeleton {
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-container {
|
|
||||||
flex:1 1 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav header {
|
|
||||||
z-index: 101;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(button,.button).tiny {
|
:is(button,.button).tiny {
|
||||||
block-size: 1.5rem;
|
block-size: 1.5rem;
|
||||||
/*max-inline-size: 1.5rem;*/
|
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
border-radius: .75rem;
|
border-radius: .75rem
|
||||||
}
|
|
||||||
|
|
||||||
nav.bottom.s:not(.drawer) > a:not(.button,.chip) {
|
|
||||||
inline-size: unset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav.bottom.s:not(.drawer) > a:not(.button,.chip) { inline-size: unset }
|
||||||
nav.bottom.s:not(.drawer) :is(button,.button) > menu {
|
nav.bottom.s:not(.drawer) :is(button,.button) > menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
margin-top:auto;
|
margin-top: auto;
|
||||||
margin-bottom: 5rem;
|
margin-bottom: 5rem;
|
||||||
margin-left:auto;
|
margin-left: auto;
|
||||||
margin-right:0;
|
margin-right: 0;
|
||||||
inline-size: auto;
|
inline-size: auto;
|
||||||
min-inline-size: 12rem;
|
min-inline-size: 12rem;
|
||||||
z-index: 100;
|
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
inset: auto 0 0 auto;
|
inset: auto 0 0 auto
|
||||||
}
|
|
||||||
|
|
||||||
i[class*=fa-at] {
|
|
||||||
vertical-align:unset;
|
|
||||||
font-size: .75em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab {
|
.fab {
|
||||||
position:fixed;
|
position: fixed;
|
||||||
right: 2rem;
|
right: 2rem;
|
||||||
bottom: 2rem;
|
bottom: 2rem
|
||||||
z-index: 1;
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width:992px) {
|
||||||
|
.fab {
|
||||||
|
bottom: 7rem
|
||||||
|
}
|
||||||
|
|
||||||
|
:has(>main.responsive) {
|
||||||
|
max-block-size: calc(100vh - 5rem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog {
|
dialog {
|
||||||
overflow:visible;
|
overflow: visible;
|
||||||
width:80%;
|
width: 80%
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#edit-bio {
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invisible {
|
.invisible {
|
||||||
max-width:0px;
|
max-width: 0;
|
||||||
max-height:0px;
|
max-height: 0;
|
||||||
border: none;
|
border: 0;
|
||||||
margin:0;
|
margin: 0;
|
||||||
padding:0;
|
padding: 0
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 992px) {
|
|
||||||
.fab {
|
|
||||||
bottom: 7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
*:has(> main.responsive) {
|
|
||||||
/*flex-direction: row;*/
|
|
||||||
max-block-size: calc(100vh - 5rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
position:relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar::after {
|
|
||||||
content: '';
|
|
||||||
position:absolute;
|
|
||||||
left:0; right:0;
|
|
||||||
top:0; bottom:0;
|
|
||||||
background-color: var(--surface-container-low);
|
|
||||||
/*background-image: var(--prami-svg);*/
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center bottom;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px dashed var(--gray-4);
|
|
||||||
z-index:1;
|
|
||||||
}
|
|
||||||
|
|
||||||
article.ephemeral {
|
|
||||||
max-width: 50rem;
|
|
||||||
border: 2px dashed var(--gray-7);
|
|
||||||
}
|
|
||||||
|
|
||||||
#pics article {
|
|
||||||
max-width: 50rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs a {
|
|
||||||
text-decoration:none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
|
|
@ -271,238 +123,132 @@ article.ephemeral {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between
|
||||||
}
|
}
|
||||||
|
.card-grid > * { flex-grow: 1; flex-shrink: 1 }
|
||||||
|
|
||||||
#pics article {
|
#now-garden { gap: 1rem }
|
||||||
margin: 1rem auto;
|
#now-garden > :not(.now) { position: absolute }
|
||||||
text-align:center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pics article>:not(:first-child) {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pics article nav {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pics.card-grid article{
|
|
||||||
max-width: 24rem;
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 895px) {
|
|
||||||
#pics.card-grid article {
|
|
||||||
max-width: calc(100% - 1rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pics article > img:first-child {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#now-garden{
|
|
||||||
gap:1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#now-garden > :not(.now) {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid > * {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-container > .page.active {
|
|
||||||
display:flex;
|
|
||||||
flex-direction:column;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
iframe {
|
|
||||||
width:100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
border: none;
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.now {
|
|
||||||
background-color: var(--green-2);
|
|
||||||
color: var(--black);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
main, .page-container {
|
|
||||||
flex-grow:1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#info :is(ul, ol) {
|
|
||||||
margin-left: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#info :is(p, ul, ol) {
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-seedling { color: var(--green-9) !important; }
|
|
||||||
.fa-message-smile { color: var(--blue-4) !important; }
|
|
||||||
.fa-images { color: var(--yellow-6) !important; }
|
|
||||||
.fa-id-card { color: var(--pink-4) !important; }
|
|
||||||
.fa-comment-dots { color: var(--gray-6) !important; }
|
|
||||||
|
|
||||||
nav.bottom.s :is(small, .label) {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.tiny {
|
|
||||||
---size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row p {
|
|
||||||
white-space: normal;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.row.indent {
|
|
||||||
margin-left: 1rem;
|
|
||||||
border-left: 1px solid var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(.circle,.square).large.small:not(i,img,video,svg) {
|
|
||||||
block-size: 2rem;
|
|
||||||
inline-size: 2rem;
|
|
||||||
}
|
|
||||||
:is(button,.button,.chip).large.small > .responsive {
|
|
||||||
inline-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* s */
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
.m:not(.s), .l:not(.s), .m.l:not(.s) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* m */
|
|
||||||
@media only screen and (min-width: 601px) and (max-width: 992px) {
|
|
||||||
.s:not(.m), .l:not(.m), .s.l:not(.m) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* l */
|
|
||||||
@media only screen and (min-width: 993px) {
|
|
||||||
:is(.circle,.square).large.small:not(i,img,video,svg) {
|
|
||||||
block-size: 3rem;
|
|
||||||
inline-size: 3rem;
|
|
||||||
}
|
|
||||||
:is(button,.button,.chip).large.small > .responsive {
|
|
||||||
inline-size: 3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#directory{
|
|
||||||
column-width: 14rem;
|
|
||||||
}
|
|
||||||
#directory a {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
article#directory ul{
|
|
||||||
list-style:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#directoryIndex a {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-toolbar .easymde-dropdown, .editor-toolbar button {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
.editor-toolbar i.separator {
|
|
||||||
---size: unset;
|
|
||||||
opacity: 0.25;
|
|
||||||
border-radius: 0
|
|
||||||
}
|
|
||||||
.markdown-editor .editor-preview {
|
|
||||||
background: var(--surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-editor {
|
|
||||||
flex: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction:column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-editor + nav {
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
.markdown-editor > .EasyMDEContainer {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-editor > .EasyMDEContainer > .editor-toolbar{
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-editor > .EasyMDEContainer > .CodeMirror {
|
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
article {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--max-article-size);
|
||||||
|
margin-inline: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
nav label:is(.checkbox, .radio, .switch) {
|
article.now { margin: 0; width: auto }
|
||||||
|
article.theme { height: 10rem; width: 15rem }
|
||||||
|
article.ephemeral { max-width: 50rem }
|
||||||
|
|
||||||
|
article.pic {
|
||||||
|
max-width: 50rem;
|
||||||
|
margin: 1rem auto;
|
||||||
|
text-align: left
|
||||||
|
}
|
||||||
|
article.pic > img:first-child { text-align: center }
|
||||||
|
article.pic nav { flex-wrap: wrap }
|
||||||
|
|
||||||
|
#pics.card-grid article { max-width: 24rem }
|
||||||
|
|
||||||
|
@media only screen and (max-width:895px) {
|
||||||
|
#pics.card-grid article {
|
||||||
|
max-width: calc(100% - 1rem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
article.paste code {
|
||||||
|
overflow-x: scroll;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-container, .page-container > .page.active, main {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column
|
||||||
|
}
|
||||||
|
|
||||||
|
#info :is(ul,ol) { margin-left: var(--spacing) }
|
||||||
|
#info :is(p,ul,ol) { margin-bottom: var(--spacing) }
|
||||||
|
|
||||||
|
#directory { column-width: 14rem }
|
||||||
|
|
||||||
|
article#directory ul { list-style: none }
|
||||||
|
|
||||||
|
nav label:is(.checkbox,.radio,.switch) {
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
menu > details .row, menu > li > details .row {
|
menu > details .row, menu > li > details .row {
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
min-block-size: 3rem;
|
min-block-size: 3rem;
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.row p {
|
||||||
|
white-space: normal;
|
||||||
|
max-width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
a.row.indent { margin-left: 1rem }
|
||||||
|
|
||||||
|
:is(.circle,.square).large.small:not(i,img,video,svg) {
|
||||||
|
block-size: 2rem;
|
||||||
|
inline-size: 2rem
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(button,.button,.chip).large.small > .responsive {
|
||||||
|
inline-size: 2rem
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width:600px) {
|
||||||
|
.l:not(.s), .m.l:not(.s), .m:not(.s) {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width:601px) and (max-width:992px) {
|
||||||
|
.l:not(.m), .s.l:not(.m), .s:not(.m) {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width:993px) {
|
||||||
|
:is(.circle,.square).large.small:not(i,img,video,svg) {
|
||||||
|
block-size: 3rem;
|
||||||
|
inline-size: 3rem
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(button,.button,.chip).large.small > .responsive {
|
||||||
|
inline-size: 3rem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor + nav, .markdown-editor > .EasyMDEContainer > .editor-toolbar {
|
||||||
|
flex: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-editor > .EasyMDEContainer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column
|
||||||
}
|
}
|
||||||
|
|
||||||
menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) {
|
.markdown-editor > .EasyMDEContainer > .CodeMirror {
|
||||||
background-color: var(--active);
|
flex: auto
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--max-article-size);
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
article.now {
|
|
||||||
margin: 0;
|
|
||||||
width:auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#advanced textarea, #advanced .field.textarea {
|
|
||||||
background-color: #212121;
|
|
||||||
color: #EEFFFF;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
article.theme {
|
|
||||||
height: 10rem;
|
|
||||||
width: 15rem;
|
|
||||||
}
|
}
|
||||||
18
wwwroot/css/type.css
Normal file
18
wwwroot/css/type.css
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* typography and fonts */
|
||||||
|
|
||||||
|
:root { --font: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif, var(--emoji-font) }
|
||||||
|
|
||||||
|
body, html { font-family: var(--font) }
|
||||||
|
body { font-size: 1.2em }
|
||||||
|
|
||||||
|
.address, .author, .honey, .page-heading { font-family: "VC Honey Deck",var(--font) }
|
||||||
|
|
||||||
|
.mono, #advanced :is(.field.textarea, textarea), .EasyMDEContainer, article.paste code {
|
||||||
|
font-family: 'MD IO 0.4', monospace, var(--emoji-font)
|
||||||
|
}
|
||||||
|
|
||||||
|
li, p { line-height: 160% }
|
||||||
|
.author { font-size: 1.2em }
|
||||||
|
nav.bottom.s :is(small,.label) { font-size: .75rem }
|
||||||
|
#directory a { font-size: 1.1rem }
|
||||||
|
#directoryIndex a { font-size: 1.5rem }
|
||||||
13
wwwroot/css/z.css
Normal file
13
wwwroot/css/z.css
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* Anything related to z-indexes */
|
||||||
|
|
||||||
|
nav header { z-index: 101 }
|
||||||
|
|
||||||
|
nav.bottom.s:not(.drawer) :is(button,.button) > menu { z-index: 100 }
|
||||||
|
|
||||||
|
.fab {
|
||||||
|
z-index: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar::after { z-index: 1 }
|
||||||
|
|
||||||
|
.hover { z-index: 1 }
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<link href="/vendor/cm-material.css" rel="stylesheet" />
|
<link href="/vendor/cm-material.css" rel="stylesheet" />
|
||||||
<script src="/_content/PSC.Blazor.Components.MarkdownEditor/js/easymde.min.js"></script>
|
<script src="/_content/PSC.Blazor.Components.MarkdownEditor/js/easymde.min.js"></script>
|
||||||
<script src="/_content/PSC.Blazor.Components.MarkdownEditor/js/markdownEditor.js"></script>
|
<script src="/_content/PSC.Blazor.Components.MarkdownEditor/js/markdownEditor.js"></script>
|
||||||
<script src="/js/csharp.js"></script>
|
<script src="/js/script.js"></script>
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
window.injectCSharp = async function (helper) {
|
|
||||||
window.CSHARP = helper
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function delay(t) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, t);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeElementById(id) {
|
|
||||||
document.getElementById(id)?.remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToId(id) {
|
|
||||||
const element = document.getElementById(id);
|
|
||||||
if (element instanceof HTMLElement) {
|
|
||||||
element.scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
block: "start",
|
|
||||||
inline: "nearest"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDetails(id) {
|
|
||||||
const element = document.getElementById(id)
|
|
||||||
if (element instanceof HTMLDetailsElement)
|
|
||||||
element.open = !element.open
|
|
||||||
}
|
|
||||||
|
|
||||||
function cacheBust(url) {
|
|
||||||
fetch(new Request(url), {
|
|
||||||
headers: new Headers({
|
|
||||||
"pragma": "no-cache",
|
|
||||||
"cache-control": "no-cache"
|
|
||||||
}),
|
|
||||||
mode: 'no-cors',
|
|
||||||
cache: 'no-cache',
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
let els = document.querySelectorAll(`[src="${url}"]`)
|
|
||||||
els.forEach(el => el.removeAttribute('src'))
|
|
||||||
els.forEach(el => el.src = url)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
32
wwwroot/js/script.js
Normal file
32
wwwroot/js/script.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
window.injectCSharp = async function (helper) { window.CSHARP = helper }
|
||||||
|
|
||||||
|
async function delay(t) { return new Promise((resolve) => { setTimeout(resolve, t) }) }
|
||||||
|
|
||||||
|
async function removeElementById(id) { document.getElementById(id)?.remove() }
|
||||||
|
|
||||||
|
function scrollToId(id) {
|
||||||
|
const element = document.getElementById(id)
|
||||||
|
if (element instanceof HTMLElement)
|
||||||
|
element.scrollIntoView({behavior: "smooth",block: "start",inline: "nearest"})
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDetails(id) {
|
||||||
|
const element = document.getElementById(id)
|
||||||
|
if (element instanceof HTMLDetailsElement) element.open = !element.open
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheBust(url) {
|
||||||
|
fetch(new Request(url), {
|
||||||
|
headers: new Headers({
|
||||||
|
"pragma": "no-cache",
|
||||||
|
"cache-control": "no-cache"
|
||||||
|
}),
|
||||||
|
mode: 'no-cors',
|
||||||
|
cache: 'no-cache',
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
let els = document.querySelectorAll(`[src="${url}"]`)
|
||||||
|
els.forEach(el => el.removeAttribute('src'))
|
||||||
|
els.forEach(el => el.src = url)
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue