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