Initial commit
This commit is contained in:
		
						commit
						55030b1e54
					
				
					 33 changed files with 5603 additions and 0 deletions
				
			
		
							
								
								
									
										9
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | root = true | ||||||
|  | 
 | ||||||
|  | [*] | ||||||
|  | indent_style = space | ||||||
|  | indent_size = 2 | ||||||
|  | end_of_line = lf | ||||||
|  | charset = utf-8 | ||||||
|  | trim_trailing_whitespace = false | ||||||
|  | insert_final_newline = true | ||||||
							
								
								
									
										20
									
								
								.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | module.exports = { | ||||||
|  | 	root: true, | ||||||
|  | 	parser: '@typescript-eslint/parser', | ||||||
|  | 	extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], | ||||||
|  | 	plugins: ['svelte3', '@typescript-eslint'], | ||||||
|  | 	ignorePatterns: ['*.cjs'], | ||||||
|  | 	overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], | ||||||
|  | 	settings: { | ||||||
|  | 		'svelte3/typescript': () => require('typescript') | ||||||
|  | 	}, | ||||||
|  | 	parserOptions: { | ||||||
|  | 		sourceType: 'module', | ||||||
|  | 		ecmaVersion: 2020 | ||||||
|  | 	}, | ||||||
|  | 	env: { | ||||||
|  | 		browser: true, | ||||||
|  | 		es2017: true, | ||||||
|  | 		node: true | ||||||
|  | 	} | ||||||
|  | }; | ||||||
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | .DS_Store | ||||||
|  | node_modules | ||||||
|  | /build | ||||||
|  | /.svelte-kit | ||||||
|  | /package | ||||||
|  | .env | ||||||
|  | .env.* | ||||||
|  | !.env.example | ||||||
|  | .output | ||||||
							
								
								
									
										1
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | engine-strict=true | ||||||
							
								
								
									
										7
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | { | ||||||
|  | 	"useTabs": false, | ||||||
|  | 	"tabWidth": 2, | ||||||
|  | 	"singleQuote": true, | ||||||
|  | 	"trailingComma": "none", | ||||||
|  | 	"printWidth": 100 | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | # default-template | ||||||
|  | 
 | ||||||
|  | ## 0.0.2-next.0 | ||||||
|  | ### Patch Changes | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - [chore] upgrade cookie library ([#4592](https://github.com/sveltejs/kit/pull/4592)) | ||||||
							
								
								
									
										7
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | # rainbow-fe | ||||||
|  | 
 | ||||||
|  | A queer front-end for Pleroma :) | ||||||
|  | 
 | ||||||
|  | ## Why Pleroma? | ||||||
|  | 
 | ||||||
|  | It's API-compatible with Mastodon. In the future I plan to support GoToSocial. | ||||||
							
								
								
									
										4980
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4980
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										38
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | { | ||||||
|  |   "name": "rainbow-fe", | ||||||
|  |   "version": "0.0.1", | ||||||
|  |   "scripts": { | ||||||
|  |     "dev": "svelte-kit dev", | ||||||
|  |     "build": "svelte-kit build", | ||||||
|  |     "package": "svelte-kit package", | ||||||
|  |     "preview": "svelte-kit preview", | ||||||
|  |     "prepare": "svelte-kit sync", | ||||||
|  |     "check": "svelte-check --tsconfig ./tsconfig.json", | ||||||
|  |     "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", | ||||||
|  |     "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", | ||||||
|  |     "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "@sveltejs/adapter-auto": "next", | ||||||
|  |     "@sveltejs/kit": "next", | ||||||
|  |     "@types/cookie": "^0.4.1", | ||||||
|  |     "@typescript-eslint/eslint-plugin": "^5.10.1", | ||||||
|  |     "@typescript-eslint/parser": "^5.10.1", | ||||||
|  |     "eslint": "^7.32.0", | ||||||
|  |     "eslint-config-prettier": "^8.3.0", | ||||||
|  |     "eslint-plugin-svelte3": "^3.2.1", | ||||||
|  |     "prettier": "^2.5.1", | ||||||
|  |     "prettier-plugin-svelte": "^2.5.0", | ||||||
|  |     "svelte": "^3.46.0", | ||||||
|  |     "svelte-check": "^2.2.6", | ||||||
|  |     "svelte-preprocess": "^4.10.1", | ||||||
|  |     "tslib": "^2.3.1", | ||||||
|  |     "typescript": "~4.6.2" | ||||||
|  |   }, | ||||||
|  |   "type": "module", | ||||||
|  |   "dependencies": { | ||||||
|  |     "@fontsource/fira-mono": "^4.5.0", | ||||||
|  |     "@lukeed/uuid": "^2.0.0", | ||||||
|  |     "cookie": "^0.4.1" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/app.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/app.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | :root { | ||||||
|  |   font-family: 'Inter', sans-serif; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | html, body { | ||||||
|  |   background-color: var(--col-bg-0); | ||||||
|  |   color: var(--col-fg-0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a { | ||||||
|  |   color: var(--col-accent-1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a.link-muted { | ||||||
|  |   color: var(--col-fg-2); | ||||||
|  |   text-decoration: none; | ||||||
|  |   border-bottom: 1px solid var(--col-fg-2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, h2, h3 { | ||||||
|  |   margin: 0; | ||||||
|  |   margin-bottom: 0.25em; | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | /// <reference types="@sveltejs/kit" />
 | ||||||
|  | 
 | ||||||
|  | // See https://kit.svelte.dev/docs/types#app
 | ||||||
|  | declare namespace App { | ||||||
|  |   interface Session { | ||||||
|  |     instance: { url: string; token?: string }; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/app.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/app.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8" /> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||||
|  |     <link rel="icon" href="/favicon.png" /> | ||||||
|  | 
 | ||||||
|  |     <link rel="stylesheet" href="/themes/purple.css" /> | ||||||
|  |     %svelte.head% | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     %svelte.body% | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										8
									
								
								src/hooks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/hooks.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | import type { GetSession } from '@sveltejs/kit'; | ||||||
|  | 
 | ||||||
|  | export const getSession: GetSession = async () => { | ||||||
|  |   const session: App.Session = { | ||||||
|  |     instance: { url: 'https://trans.enby.town', token: 'bweep' } | ||||||
|  |   }; | ||||||
|  |   return session; | ||||||
|  | }; | ||||||
							
								
								
									
										42
									
								
								src/lib/ago.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/lib/ago.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | // Adapted from: https://github.com/sebastiansandqvist/s-ago (MIT)
 | ||||||
|  | 
 | ||||||
|  | function format( | ||||||
|  |   diff: number, | ||||||
|  |   divisor: number, | ||||||
|  |   unit: string, | ||||||
|  |   past: string, | ||||||
|  |   future: string, | ||||||
|  |   isInTheFuture: boolean | ||||||
|  | ) { | ||||||
|  |   var val = Math.floor(Math.abs(diff) / divisor); | ||||||
|  |   if (isInTheFuture) return val <= 1 ? future : 'in ' + val + ' ' + unit + 's'; | ||||||
|  |   return val <= 1 ? past : val + ' ' + unit + 's ago'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const units = [ | ||||||
|  |   { max: 60000, value: 1000, name: 'second', past: 'a second ago', future: 'in a second' }, // max: 60 seconds
 | ||||||
|  |   { max: 3600000, value: 60000, name: 'minute', past: 'a minute ago', future: 'in a minute' }, // max: 60 minutes
 | ||||||
|  |   { max: 72000000, value: 3600000, name: 'hour', past: 'an hour ago', future: 'in an hour' }, // max: 20 hours
 | ||||||
|  |   { max: 518400000, value: 86400000, name: 'day', past: 'yesterday', future: 'tomorrow' }, // max: 6 days
 | ||||||
|  |   { max: 2419200000, value: 604800000, name: 'week', past: 'last week', future: 'in a week' }, // max: 28 days
 | ||||||
|  |   { max: 28512000000, value: 2592000000, name: 'month', past: 'last month', future: 'in a month' } // max: 11 months
 | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | export default function ago( | ||||||
|  |   date: Date, | ||||||
|  |   now: Date = new Date(), | ||||||
|  |   max?: 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | ||||||
|  | ): string { | ||||||
|  |   const diff = now.getTime() - date.getTime(); | ||||||
|  | 
 | ||||||
|  |   // less than a second
 | ||||||
|  |   if (Math.abs(diff) < 1000) return 'just now'; | ||||||
|  | 
 | ||||||
|  |   for (var i = 0; i < units.length; i++) { | ||||||
|  |     if (Math.abs(diff) < units[i].max || (max && units[i].name === max)) { | ||||||
|  |       return format(diff, units[i].value, units[i].name, units[i].past, units[i].future, diff < 0); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return format(diff, 31536000000, 'year', 'last year', 'in a year', diff < 0); | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								src/lib/components/AccountProfile.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/lib/components/AccountProfile.svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   import type { MastodonAccount } from '$lib/mastoapi/account'; | ||||||
|  | 
 | ||||||
|  |   export let account: MastodonAccount; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <article class="account-profile"> | ||||||
|  |   <header> | ||||||
|  |     <img class="avatar" alt={account.acct} src={account.avatar} /> | ||||||
|  |     <div> | ||||||
|  |       <h1>{account.display_name}</h1> | ||||||
|  |       <a class="link-muted" href={account.url}>@{account.fqn}</a> | ||||||
|  |     </div> | ||||||
|  |   </header> | ||||||
|  | 
 | ||||||
|  |   <div class="about"> | ||||||
|  |     {@html account.note} | ||||||
|  |   </div> | ||||||
|  | </article> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  |   .account-profile { | ||||||
|  |     background: var(--col-bg-1); | ||||||
|  |     padding: 1em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .account-profile header { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  |     margin-bottom: 1em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .account-profile header .avatar { | ||||||
|  |     width: 5em; | ||||||
|  |     height: 5em; | ||||||
|  |     border-radius: 6px; | ||||||
|  | 
 | ||||||
|  |     margin-inline-end: 1em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .account-profile header > div { | ||||||
|  |     width: 100%; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
							
								
								
									
										88
									
								
								src/lib/components/Status.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/lib/components/Status.svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   import ago from '$lib/ago'; | ||||||
|  |   import { now } from '$lib/global-now'; | ||||||
|  |   import { linkAccount } from '$lib/mastoapi/account'; | ||||||
|  | 
 | ||||||
|  |   import { linkStatus, type MastodonStatus } from '$lib/mastoapi/status'; | ||||||
|  |   import StatusControls from './StatusControls.svelte'; | ||||||
|  | 
 | ||||||
|  |   export let status: MastodonStatus; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <article class="status"> | ||||||
|  |   <div class="status-content"> | ||||||
|  |     <aside> | ||||||
|  |       <a href={linkAccount(status.account)}> | ||||||
|  |         <img class="avatar" alt={status.account.acct} src={status.account.avatar_static} /> | ||||||
|  |       </a> | ||||||
|  |     </aside> | ||||||
|  | 
 | ||||||
|  |     <div class="status-main"> | ||||||
|  |       <div class="top-line"> | ||||||
|  |         <a href={linkAccount(status.account)} class="author-link"> | ||||||
|  |           <strong class="author-display-name">{status.account.display_name}</strong> | ||||||
|  |           <span class="author-handle">@{status.account.fqn}</span> | ||||||
|  |         </a> | ||||||
|  | 
 | ||||||
|  |         <a class="link-muted" href={linkStatus(status)}> | ||||||
|  |           <time>{ago(new Date(status.created_at), $now)}</time> | ||||||
|  |         </a> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <p class="status-body"> | ||||||
|  |         {@html status.content} | ||||||
|  |       </p> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  |   <StatusControls {status} /> | ||||||
|  | </article> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  |   .status { | ||||||
|  |     background-color: var(--col-bg-1); | ||||||
|  |     padding: 0.25em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status-content { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  |     padding: 0.25em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status aside { | ||||||
|  |     width: 4rem; | ||||||
|  |     margin-inline-end: 1em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status .avatar { | ||||||
|  |     border-radius: 6px; | ||||||
|  |     height: 3em; | ||||||
|  |     max-width: 3em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status .author-link { | ||||||
|  |     color: var(--col-fg-0); | ||||||
|  |     text-decoration: none; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status .author-handle { | ||||||
|  |     margin-inline-start: 0.5em; | ||||||
|  |     color: var(--col-fg-1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status .status-main { | ||||||
|  |     width: 100%; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status .status-main .top-line { | ||||||
|  |     display: inline-flex; | ||||||
|  |     flex-direction: row; | ||||||
|  |     justify-content: space-between; | ||||||
|  |     width: 100%; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .status .status-body { | ||||||
|  |     margin-bottom: 0; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
							
								
								
									
										31
									
								
								src/lib/components/StatusControls.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/lib/components/StatusControls.svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   import { session } from '$app/stores'; | ||||||
|  | 
 | ||||||
|  |   import type { MastodonStatus } from '$lib/mastoapi/status'; | ||||||
|  |   import type { InstanceInfo } from '$lib/mastoapi/util'; | ||||||
|  | 
 | ||||||
|  |   export let status: MastodonStatus; | ||||||
|  | 
 | ||||||
|  |   // TODO: fave/boost buttons should send proper POST request | ||||||
|  |   // reply button should open the composer | ||||||
|  |   // the more button should have a popup with like, bookmark, pin, etc | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if $session.instance.token} | ||||||
|  |   <div class="status-controls"> | ||||||
|  |     <button>reply</button> | ||||||
|  |     <button>favourite</button> | ||||||
|  |     <button>boost</button> | ||||||
|  |     <button>more</button> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  |   .status-controls { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  |     justify-content: space-evenly; | ||||||
|  | 
 | ||||||
|  |     padding: 0.5em; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
							
								
								
									
										13
									
								
								src/lib/global-now.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/lib/global-now.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | import { derived, readable, writable, type Readable } from 'svelte/store'; | ||||||
|  | 
 | ||||||
|  | const freq = readable(1); // Hz
 | ||||||
|  | 
 | ||||||
|  | export const now: Readable<Date> = derived(freq, ($freq, set) => { | ||||||
|  |   const interval = setInterval(() => { | ||||||
|  |     set(new Date()); | ||||||
|  |   }, 1000 / $freq); | ||||||
|  | 
 | ||||||
|  |   return () => { | ||||||
|  |     clearInterval(interval); | ||||||
|  |   }; | ||||||
|  | }); | ||||||
							
								
								
									
										26
									
								
								src/lib/mastoapi/account.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/lib/mastoapi/account.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | import type { MastodonObject } from './base'; | ||||||
|  | import { fetchAPI, type InstanceInfo } from './util'; | ||||||
|  | 
 | ||||||
|  | export interface MastodonAccount extends MastodonObject { | ||||||
|  |   fqn: string; | ||||||
|  |   display_name: string; | ||||||
|  |   acct: string; | ||||||
|  |   url: string; | ||||||
|  | 
 | ||||||
|  |   avatar: string; | ||||||
|  |   avatar_static: string; | ||||||
|  | 
 | ||||||
|  |   note: string; | ||||||
|  | 
 | ||||||
|  |   bot: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function linkAccount(account: MastodonAccount) { | ||||||
|  |   return '/acc/' + account.id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function fetchAccount(instance: InstanceInfo, id: string): Promise<MastodonAccount> { | ||||||
|  |   return await fetchAPI(instance, '/api/v1/accounts/' + id) | ||||||
|  |     .then((r) => r.json()) | ||||||
|  |     .then((b) => b as MastodonAccount); | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								src/lib/mastoapi/base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/lib/mastoapi/base.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | export interface MastodonObject { | ||||||
|  |   id: string; | ||||||
|  |   created_at: string; | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								src/lib/mastoapi/oauth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/lib/mastoapi/oauth.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | // TODO
 | ||||||
|  | export {} | ||||||
							
								
								
									
										21
									
								
								src/lib/mastoapi/status.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/lib/mastoapi/status.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | import type { MastodonAccount } from './account'; | ||||||
|  | import type { MastodonObject } from './base'; | ||||||
|  | 
 | ||||||
|  | import { fetchAPI, MastodonAPIError, type InstanceInfo } from './util'; | ||||||
|  | 
 | ||||||
|  | export interface MastodonStatus extends MastodonObject { | ||||||
|  |   account: MastodonAccount; | ||||||
|  |   content: string; | ||||||
|  |   spoiler_text: string; | ||||||
|  |   url: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function linkStatus(status: MastodonStatus) { | ||||||
|  |   return '/post/' + status.id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function fetchStatus(instance: InstanceInfo, id: string): Promise<MastodonStatus> { | ||||||
|  |   return await fetchAPI(instance, '/api/v1/statuses/' + id) | ||||||
|  |     .then((r) => r.json()) | ||||||
|  |     .then((b) => b as MastodonStatus); | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/lib/mastoapi/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/lib/mastoapi/util.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | export interface InstanceInfo { | ||||||
|  |   url: string; | ||||||
|  |   token?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class MastodonAPIError extends Error { | ||||||
|  |   public status: number; | ||||||
|  | 
 | ||||||
|  |   constructor(status: number, error: string) { | ||||||
|  |     super(error); | ||||||
|  |     this.status = status; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function isLoggedIn(instance: InstanceInfo) { | ||||||
|  |   return !!instance.token; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function fetchAPI( | ||||||
|  |   instance: InstanceInfo, | ||||||
|  |   path: string, | ||||||
|  |   options?: any | ||||||
|  | ): Promise<Response> { | ||||||
|  |   const opts = { ...options }; | ||||||
|  | 
 | ||||||
|  |   if (opts.headers == null) opts.headers = {}; | ||||||
|  |   if (instance.token != null) { | ||||||
|  |     opts.headers['Authorization'] = instance.token; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const response = await fetch(`${instance.url}${path}`, opts); | ||||||
|  |   if (response.status != 200) { | ||||||
|  |     const json = await response.json(); | ||||||
|  |     throw new MastodonAPIError(response.status, json.error); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return response; | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								src/routes/__layout.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/routes/__layout.svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   import '../app.css'; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <slot /> | ||||||
							
								
								
									
										61
									
								
								src/routes/acc/[id].svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/routes/acc/[id].svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | ||||||
|  | <script context="module" lang="ts"> | ||||||
|  |   import { fetchAccount, type MastodonAccount } from '$lib/mastoapi/account'; | ||||||
|  |   import type { Load } from '@sveltejs/kit'; | ||||||
|  | 
 | ||||||
|  |   export const load: Load = async ({ session, params }) => { | ||||||
|  |     try { | ||||||
|  |       const account = await fetchAccount(session.instance, params.id); | ||||||
|  |       return { props: { account } }; | ||||||
|  |     } catch (err) { | ||||||
|  |       return { status: 404, error: err as Error }; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  |   import AccountProfile from '$lib/components/AccountProfile.svelte'; | ||||||
|  |   import { session } from '$app/stores'; | ||||||
|  |   import { fetchAPI } from '$lib/mastoapi/util'; | ||||||
|  |   import type { MastodonStatus } from '$lib/mastoapi/status'; | ||||||
|  |   import Status from '$lib/components/Status.svelte'; | ||||||
|  | 
 | ||||||
|  |   export let account: MastodonAccount; | ||||||
|  | 
 | ||||||
|  |   export const pinnedStatuses = (async () => { | ||||||
|  |     const response = await fetchAPI( | ||||||
|  |       $session.instance, | ||||||
|  |       '/api/v1/accounts/' + account.id + '/statuses?pinned=true' | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const statuses = []; | ||||||
|  |     for (const statusObj of await response.json()) { | ||||||
|  |       statuses.push(statusObj as MastodonStatus); | ||||||
|  |     } | ||||||
|  |     statuses.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); | ||||||
|  | 
 | ||||||
|  |     return statuses; | ||||||
|  |   })(); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div class="profile"> | ||||||
|  |   <AccountProfile {account} /> | ||||||
|  | </div> | ||||||
|  | <div class="statuses"> | ||||||
|  |   {#await pinnedStatuses then statuses} | ||||||
|  |     {#each statuses as status} | ||||||
|  |       <div class="timeline-status"> | ||||||
|  |         <Status {status} /> | ||||||
|  |       </div> | ||||||
|  |     {/each} | ||||||
|  |   {/await} | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  |   .profile { | ||||||
|  |     margin-bottom: 0.5em; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .timeline-status { | ||||||
|  |     border-bottom: 1px solid var(--col-fg-2); | ||||||
|  |   } | ||||||
|  | </style> | ||||||
							
								
								
									
										10
									
								
								src/routes/index.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/routes/index.svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | <script context="module" lang="ts"> | ||||||
|  |   export const prerender = true; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <svelte:head> | ||||||
|  |   <title>Rainbow FE</title> | ||||||
|  |   <meta name="description" content="RainbowFE - A queer frontend for the fediverse." /> | ||||||
|  | </svelte:head> | ||||||
|  | 
 | ||||||
|  | <p>Welcome!</p> | ||||||
							
								
								
									
										16
									
								
								src/routes/login.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/routes/login.svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   let instanceName: string; | ||||||
|  | 
 | ||||||
|  |   export async function onSubmitInstance(event: SubmitEvent) { | ||||||
|  |     event.preventDefault(); | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <form on:submit={onSubmitInstance}> | ||||||
|  |   <h1>Log in</h1> | ||||||
|  | 
 | ||||||
|  |   <label for="instance">Instance:</label> | ||||||
|  |   <input bind:value={instanceName} type="url" id="instance" name="instance" /> | ||||||
|  | 
 | ||||||
|  |   <input type="submit" value="Log in" /> | ||||||
|  | </form> | ||||||
							
								
								
									
										24
									
								
								src/routes/post/[id].svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/routes/post/[id].svelte
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | <script lang="ts" context="module"> | ||||||
|  |   import type { Load } from '@sveltejs/kit'; | ||||||
|  |   import { fetchStatus, type MastodonStatus } from '$lib/mastoapi/status'; | ||||||
|  | 
 | ||||||
|  |   export const load: Load = async ({ session, params }) => { | ||||||
|  |     try { | ||||||
|  |       const status = await fetchStatus(session.instance, params.id); | ||||||
|  |       return { props: { status } }; | ||||||
|  |     } catch (err) { | ||||||
|  |       return { status: 404, error: err as Error }; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  |   import Status from '$lib/components/Status.svelte'; | ||||||
|  |   export let status: MastodonStatus; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if status != null} | ||||||
|  |   <Status {status} /> | ||||||
|  | {:else} | ||||||
|  |   <p>a</p> | ||||||
|  | {/if} | ||||||
							
								
								
									
										
											BIN
										
									
								
								static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.2 KiB | 
							
								
								
									
										3
									
								
								static/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								static/robots.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | # https://www.robotstxt.org/robotstxt.html | ||||||
|  | User-agent: * | ||||||
|  | Disallow: | ||||||
							
								
								
									
										11
									
								
								static/themes/purple.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								static/themes/purple.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | :root { | ||||||
|  |   --col-bg-0: #18161d; | ||||||
|  |   --col-bg-1: #221e28; | ||||||
|  |    | ||||||
|  |   --col-fg-0: #f2ecff; | ||||||
|  |   --col-fg-1: #ae96cc; | ||||||
|  |   --col-fg-2: #85739a; | ||||||
|  | 
 | ||||||
|  |   --col-accent-0: #e3cdff; | ||||||
|  |   --col-accent-1: #cda6ff; | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								svelte.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								svelte.config.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | import adapter from '@sveltejs/adapter-auto'; | ||||||
|  | import preprocess from 'svelte-preprocess'; | ||||||
|  | 
 | ||||||
|  | /** @type {import('@sveltejs/kit').Config} */ | ||||||
|  | const config = { | ||||||
|  | 	// Consult https://github.com/sveltejs/svelte-preprocess
 | ||||||
|  | 	// for more information about preprocessors
 | ||||||
|  | 	preprocess: preprocess(), | ||||||
|  | 
 | ||||||
|  | 	kit: { | ||||||
|  | 		adapter: adapter(), | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default config; | ||||||
							
								
								
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | { | ||||||
|  | 	"extends": "./.svelte-kit/tsconfig.json", | ||||||
|  | 	"compilerOptions": { | ||||||
|  | 		"allowJs": true, | ||||||
|  | 		"checkJs": true, | ||||||
|  | 		"esModuleInterop": true, | ||||||
|  | 		"forceConsistentCasingInFileNames": true, | ||||||
|  | 		"lib": ["es2020", "DOM"], | ||||||
|  | 		"moduleResolution": "node", | ||||||
|  | 		"module": "es2020", | ||||||
|  | 		"resolveJsonModule": true, | ||||||
|  | 		"skipLibCheck": true, | ||||||
|  | 		"sourceMap": true, | ||||||
|  | 		"strict": true, | ||||||
|  | 		"target": "es2020" | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue