parent
bb1dbee306
commit
3517c338e5
38 changed files with 1006 additions and 11 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
book
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "themes/abridge"]
|
||||
path = themes/abridge
|
||||
url = https://github.com/jieiku/abridge.git
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# docs
|
||||
|
||||
the documentation and main website
|
|
@ -1,6 +0,0 @@
|
|||
[book]
|
||||
authors = ["tezlm"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Jackwagon Docs"
|
15
config.toml
Normal file
15
config.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
# The URL the site will be built for
|
||||
base_url = "https://example.com"
|
||||
|
||||
# Whether to automatically compile all Sass files in the sass directory
|
||||
compile_sass = true
|
||||
|
||||
# Whether to build a search index to be used later on by a JavaScript library
|
||||
build_search_index = false
|
||||
|
||||
[markdown]
|
||||
highlight_code = true
|
||||
# highlight_theme = "css"
|
||||
|
||||
[extra]
|
||||
# Put all your custom variables here
|
3
content/docs/_index.md
Normal file
3
content/docs/_index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
+++
|
||||
page_template = "docs-page.html"
|
||||
+++
|
397
content/docs/api/index.md
Normal file
397
content/docs/api/index.md
Normal file
|
@ -0,0 +1,397 @@
|
|||
+++
|
||||
title = "api"
|
||||
+++
|
||||
|
||||
list of all api paths for client-server api
|
||||
|
||||
each path is versioned on its own
|
||||
|
||||
### endpoint summary
|
||||
|
||||
WARNING: although a good portion of the api is taken by or inspired by
|
||||
matrix, these endpoints aren't the same! Please read this documentation
|
||||
rather than [spec.matrix.org](https://spec.matrix.org).
|
||||
|
||||
NOTE: The api doesn't necessarily follow REST conventions.
|
||||
|
||||
NOTE: This is the PLANNED api, and isn't what is currently
|
||||
implemented. The endpoints and structs may change.
|
||||
|
||||
#### client
|
||||
|
||||
prefix = `/_jackwagon/client`
|
||||
|
||||
The main apis for clients to interact with servers. Authentication will be outsourced to oidc.
|
||||
|
||||
but QUERY isn't widely supported :(
|
||||
|
||||
| method | path | description |
|
||||
|--------|---------------------------------------------------|----------------------------------------------------|
|
||||
| QUERY | /v1/info | Get info about this server |
|
||||
| QUERY | /v1/sync | Receive new events; see [syncing](/docs/syncing) |
|
||||
| QUERY | /v1/threads | Query for threads, get inbox |
|
||||
| PUT | /v1/threads/participation | Set thread participation |
|
||||
| POST | /v1/ack | Mark messages as read |
|
||||
| GET | /v1/users/me | Get info about this user (profile) (whoami) |
|
||||
| GET | /v1/users/{userId} | Get info about a user |
|
||||
| PATCH | /v1/users/me | Set info about this user |
|
||||
| PATCH | /v1/users/{userId} | Set info about a user |
|
||||
| PUT | /v1/account-data/{key} | Write account data |
|
||||
| GET | /v1/account-data/{key} | Read account data |
|
||||
| POST | /v1/rooms/create | Create a room |
|
||||
| PUT | /v1/rooms/membership | Change room membership for yourself and others |
|
||||
| POST | /v1/rooms/{roomId}/send/{eventType} | Send an event |
|
||||
| POST | /v1/rooms/{roomId}/ephemeral/{eventType} | Send an ephemeral event |
|
||||
| PUT | /v1/rooms/{roomId}/state/{eventType}/{stateKey} | Send a state event, mutating the state of a room |
|
||||
| GET | /v1/rooms/{roomId}/state/{eventType?}/{stateKey?} | Read state from a room |
|
||||
| QUERY | /v1/rooms/{roomId}/events | Fetch events |
|
||||
| QUERY | /v1/rooms/{roomId}/relations | Fetch relations |
|
||||
| QUERY | /v1/rooms/{roomId}/aliases | Get aliases |
|
||||
| PUT | /v1/rooms/{roomId}/aliases | Set aliases |
|
||||
| PUT | /v1/rooms/{roomId}/account-data/{key} | Write account data |
|
||||
| GET | /v1/rooms/{roomId}/account-data/{key} | Read account data |
|
||||
| POST | /v1/aliases/resolve | Resolve aliases to room ids |
|
||||
| QUERY | /v1/spaces/{roomId}/hierarchy | Fetch space children |
|
||||
| QUERY | /v1/search/users | Search for users |
|
||||
| QUERY | /v1/search/messages | Search for messages |
|
||||
| QUERY | /v1/search/threads | Search for threads |
|
||||
| QUERY | /v1/search/rooms | Search for rooms |
|
||||
| ????? | /v1/voip/???? | Anything needed for voip |
|
||||
| POST | /v1/send-to-device | Send a message directly to a device |
|
||||
| POST | /v1/keys/upload | Upload/publish encryption keys |
|
||||
| POST | /v1/keys/upload-cross-keys | Upload/publish cross-signing encryption keys |
|
||||
| POST | /v1/keys/upload-cross-sigs | Upload/publish cross-signing encryption signatures |
|
||||
| POST | /v1/keys/query | Get another user's encryption keys |
|
||||
| POST | /v1/keys/claim | Claim another user's encryption keys |
|
||||
| PATCH | /v1/key-backup | Add/remove keys from the key backup |
|
||||
| QUERY | /v1/key-backup | Retrieve keys from the key backup |
|
||||
| GET | /v1/key-backup/version | Get the latest version |
|
||||
| POST | /v1/key-backup/version | Create a new version |
|
||||
| GET | /v1/key-backup/version/{version} | Get a version |
|
||||
| PUT | /v1/key-backup/version/{version} | Update a version |
|
||||
| DELETE | /v1/key-backup/version/{version} | Delete a version |
|
||||
|
||||
```rust
|
||||
// post /v1/rooms/threads
|
||||
struct GetThreads {
|
||||
room_ids: Array<RoomId>,
|
||||
from: Option<string>,
|
||||
filter: Option<ThreadFilter>,
|
||||
sort: Option<ThreadSort>,
|
||||
}
|
||||
|
||||
struct ThreadFilter {
|
||||
/// add muted threads
|
||||
muted: bool,
|
||||
|
||||
/// remove unwatched threads
|
||||
watching: bool,
|
||||
|
||||
/// remove read threads
|
||||
unread: bool,
|
||||
}
|
||||
|
||||
enum ThreadSort {
|
||||
/// Newest posts first.
|
||||
#[default]
|
||||
New,
|
||||
|
||||
/// Oldest posts first. Only used for inbox.
|
||||
Old,
|
||||
|
||||
/// Sort by the number of +1s (in a time frame)
|
||||
Votes(Option<Date>),
|
||||
|
||||
/// Sort by the number of replies/comments (in a time frame)
|
||||
Comments(Option<Date>),
|
||||
|
||||
/// Sort by the last reply/comment timestamp
|
||||
Activity,
|
||||
|
||||
/// Sort by the "hot algorithm". By far the most complex. Meant for inbox and large communities.
|
||||
Hot,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
post /v1/rooms/membership
|
||||
{
|
||||
rooms: Array<{
|
||||
room: RoomId | RoomAlias,
|
||||
user_id?: UserId,
|
||||
membership?: "join" | "leave" | "invite" | "knock" | "ban",
|
||||
forget?: boolean, // standalone or when membership = leave?
|
||||
}>
|
||||
}
|
||||
|
||||
/events
|
||||
{
|
||||
event_id?: EventId,
|
||||
limit_before?: number,
|
||||
limit_after?: number,
|
||||
}
|
||||
|
||||
/relations
|
||||
{
|
||||
event_id: EventId,
|
||||
rel_type?: string,
|
||||
event_types?: Array<string>,
|
||||
depth?: number, // recursion depth?
|
||||
|
||||
// pagination
|
||||
limit?: number,
|
||||
after?: string,
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
/// The standard "text" type. Used for all user-facing text.
|
||||
struct Text(Vec<TextBlock>);
|
||||
|
||||
struct TextBlock {
|
||||
/// The content itself.
|
||||
// NOTE: should this be `any` instead of string?
|
||||
body: String,
|
||||
|
||||
/// The language of the text (ie. en, en_US, i forget the name for this convention)
|
||||
lang: Option<String>,
|
||||
|
||||
/// The mime type of the body. May be `text/plain` or `text/html`
|
||||
// NOTE: maybe should it be `text/jackwagon+html` or `text/html+jackwagon`?
|
||||
type: Option<String>,
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
this: "is an example object",
|
||||
text1: [{ body: "the most basic text block"}],
|
||||
text2: [
|
||||
{ body: "now with *formatting*" },
|
||||
{ body: "now with <em>formatting</em>", type: "text/html" },
|
||||
],
|
||||
text3: [
|
||||
{ body: "this may not be exposed in ui, but localization can exist (mostly for bots?)" },
|
||||
{ body: "(pretend i can speak another language)", lang: "another_LANG" },
|
||||
],
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
#### federation
|
||||
|
||||
prefix = `/_jackwagon/federation`
|
||||
|
||||
This is how servers federate with each other.
|
||||
|
||||
| method | path | description |
|
||||
|--------|------ |------ |
|
||||
| QUERY | /v1/info | Get public info about this server |
|
||||
| POST | /v1/send | Send new events (PDUs) |
|
||||
| POST | /v1/make-join | Ask a server to create a join event, since ours doesn't have room state yet |
|
||||
| POST | /v1/make-invite | Same as above for inviting other users |
|
||||
| POST | /v1/make-leave | Same as above for leave (rejecting invites, specifically) |
|
||||
| POST | /v1/make-knock | Same as above for knock |
|
||||
| POST | /v1/send-join | Send the join |
|
||||
| POST | /v1/send-invite | Send the invite |
|
||||
| POST | /v1/send-leave | Send the leave |
|
||||
| POST | /v1/send-knock | Send the knock |
|
||||
| GET | /v1/state/{roomId}/{eventId} | Get a snapshot of room state at a specific event id |
|
||||
| GET | /v1/state-ids/{roomId}/{eventId} | Like /state, but only the ids instead of full events |
|
||||
| GET | /v1/event/{roomId}/{eventId} | Get an event |
|
||||
| GET | /v1/event-auth/{roomId}/{eventId} | Get the full auth chain of an event |
|
||||
| POST | /v1/backfill/{roomId} | Fetch a range of history |
|
||||
| POST | /v1/get-missing-events/{roomId} | Fetch some specific events |
|
||||
| POST | /v1/keys/server | Fetch the server's signing key |
|
||||
| POST | /v1/keys/query | Ask the server for another server's signing key |
|
||||
| GET | /v1/hierarchy/{roomId} | Get a space's hierarchy |
|
||||
| GET | /v1/query/alias/{roomAlias} | Resolve a room alias |
|
||||
| GET | /v1/query/user/{userId} | Get a user's profile |
|
||||
| GET | /v1/query/devices/{userId} | Get a user's devices |
|
||||
|
||||
#### media
|
||||
|
||||
prefix = `/_jackwagon/media`
|
||||
|
||||
Specifically for media, which is transferred out of band. I really want
|
||||
to rework the `blobs` api, since I know it's possible to decentralize
|
||||
media but still allow streaming it with pubkeys...
|
||||
|
||||
| method | path | description |
|
||||
|--------|------ |------------- |
|
||||
| POST | /v1/blobs | Upload a blob |
|
||||
| GET | /v1/blobs/:id | Download a blob |
|
||||
| DELETE | /v1/blobs/:id | Delete a blob (only uploader and admins can use) |
|
||||
| GET | /v1/blobs/:id/thumbnail | Download a blob's server generate thumbnail |
|
||||
| POST | /v1/url | Generate a url preview |
|
||||
|
||||
#### appservice
|
||||
|
||||
prefix = `/_jackwagon/appservice`
|
||||
|
||||
APIs for appservices. Notably, this api exists *on the appservice*,
|
||||
not the homeserver and is used for the homeserver to communicate to
|
||||
the appservice.
|
||||
|
||||
Appservices can pass `?user_id` on any client-server api endpoint to
|
||||
masquerade as that user. They can also use `?timestamp` to set a custom
|
||||
origin_server_ts when sending events (though it doesn't rewrite the dag!).
|
||||
|
||||
| method | path | description |
|
||||
|--------|--------------------------|--------------------------------------------|
|
||||
| GET | /v1/health | A health check for the application service |
|
||||
| PUT | /v1/transactions/{txnId} | Receive some events |
|
||||
| GET | /v1/users/{userId} | Get or create a user's profile |
|
||||
| GET | /v1/aliases/{roomAlias} | Get or create a room |
|
||||
|
||||
#### admin
|
||||
|
||||
prefix = `/_jackwagon/admin`
|
||||
|
||||
APIs for administrating a server.
|
||||
|
||||
| method | path | description |
|
||||
|--------|-------------------------------------|-----------------------------------------------------|
|
||||
| GET | /v1/stats | Get statistics of the server |
|
||||
| GET | /v1/appservices | Get a list of appservices |
|
||||
| PUT | /v1/appservices/:id | Register or update an appservice |
|
||||
| GET | /v1/appservices/:id | Get an appservice's config |
|
||||
| DELETE | /v1/appservices/:id | Delete an appservice |
|
||||
| GET | /v1/users | Get a list of all known (or only ?local=true) users |
|
||||
| GET | /v1/users/{userId} | Get a user |
|
||||
| PUT | /v1/users/{userId} | Update a user |
|
||||
| GET | /v1/rooms | Get a list of rooms |
|
||||
| GET | /v1/rooms/{roomId} | Get a room |
|
||||
| PUT | /v1/rooms/{roomId} | Update a room |
|
||||
| POST | /v1/send-notice | Send a server notice |
|
||||
| POST | /v1/debug/get-pdu/{eventId} | Get a pdu |
|
||||
| POST | /v1/debug/get-auth/{eventId} | Get a pdu's auth chain |
|
||||
| POST | /v1/debug/get-extremities/{eventId} | Get a pdu's forward extremities |
|
||||
| POST | /v1/debug/sign | Sign json |
|
||||
| POST | /v1/debug/verify | Verify a pdu |
|
||||
|
||||
this should probably be migrated out of REST for bulk deactivation
|
||||
|
||||
```ts
|
||||
type AppserviceConfig = {
|
||||
description: string, // a human readable description
|
||||
as_token: string, // secret token the appservice uses to authenticate itself
|
||||
hs_token: string, // secret token the homeserver uses to authenticate itself
|
||||
url: string, // the url the homeserver uses to communicate with the appservice
|
||||
media: string, // the url the homeserver uses to communicate with the appservice
|
||||
namespaces: {
|
||||
users?: Array<{ regex: string }>,
|
||||
aliases?: Array<{ regex: string }>,
|
||||
}
|
||||
}
|
||||
|
||||
// quarantined users cannot dm people among other restrictions
|
||||
type UserConfig = {
|
||||
profile: UserProfile,
|
||||
state: "active" | "quarantined" | "readonly" | "disabled",
|
||||
};
|
||||
|
||||
// quarantined rooms cannot be joined by any new users (but existing members can stay), and will not show up in /hierarchy
|
||||
// exiled rooms cannot be interacted with at all, and any joined users will leave. the server may purge the state.
|
||||
type RoomConfig = {
|
||||
state: "active" | "quarantined" | "exiled",
|
||||
};
|
||||
```
|
||||
|
||||
### primitives
|
||||
|
||||
| name | format | description |
|
||||
|-|-|-|
|
||||
|room id|!opaque:domain.tld|A globally unique identifier for a room. The domain part is purely to ensure uniqueness, and is meaningless otherwise.|
|
||||
|user id|@name:domain.tld|A globally unique identifier for a user.|
|
||||
|event id|$opaque|A globally unique identifier for an event.|
|
||||
|
||||
### event struct
|
||||
|
||||
| field | type | description |
|
||||
|-|-|-|
|
||||
| event_id | event id | a unique identifier for this event |
|
||||
| room_id? | room id | the room this event originated from* |
|
||||
| content | any | arbitrary content; in practice, this probably matches the event type |
|
||||
| sender | user id | the sender of this event |
|
||||
| state_key? | string | if this is a state event |
|
||||
| type | string | the type of this event |
|
||||
| origin_server_ts | number | the timestamp at the server this event originated from |
|
||||
| unsigned | unsigned data | anything extra the server calculated |
|
||||
|
||||
*only present in some endpoints
|
||||
|
||||
### standard types
|
||||
|
||||
```ts
|
||||
// Text
|
||||
// a string is shorthand for a single TextPart of type text/plain
|
||||
type Text = string | Array<TextPart>;
|
||||
type TextPart = {
|
||||
body: string,
|
||||
lang?: string,
|
||||
type?: "text/plain" | "text/html" | string,
|
||||
};
|
||||
|
||||
type File = {
|
||||
url: /mxc/,
|
||||
info: {
|
||||
alt: Text,
|
||||
type: number, // mime type
|
||||
size: number, // in bytes
|
||||
width?: number, // type = image, video
|
||||
height?: number, // type = image, video
|
||||
duration?: number, // type = audio, video
|
||||
},
|
||||
};
|
||||
|
||||
// Events sent to clients
|
||||
type ClientEvent = {
|
||||
event_id: EventId,
|
||||
room_id?: RoomId, // removed in /sync, since the room id is sent anyway
|
||||
content: any,
|
||||
sender: UserId,
|
||||
state_key?: string,
|
||||
type: string,
|
||||
origin_server_ts: number,
|
||||
unsigned: UnsignedData,
|
||||
};
|
||||
|
||||
type ServerEvent = {
|
||||
auth_events: Array<EventId>,
|
||||
content: any,
|
||||
hashes: any,
|
||||
origin_server_ts: number,
|
||||
prev_events: Array<EventId>,
|
||||
room_id: RoomId,
|
||||
sender: UserId,
|
||||
signatures: any,
|
||||
state_key?: string,
|
||||
type: string,
|
||||
// unsigned: UnsignedData,
|
||||
};
|
||||
|
||||
type UnsignedData = {
|
||||
prev_content?: any,
|
||||
redacted_because?: ClientEvent,
|
||||
transaction_id?: string,
|
||||
relations: {
|
||||
"m.thread": {
|
||||
count: number,
|
||||
participation: "participation",
|
||||
},
|
||||
"m.replace": {
|
||||
latest_event: ClientEvent,
|
||||
},
|
||||
"m.plus": {
|
||||
count: number,
|
||||
},
|
||||
"m.annotation": {
|
||||
annotations: Array<ClientEvent>,
|
||||
count: number,
|
||||
},
|
||||
},
|
||||
unreads: Unreads,
|
||||
// per-event account data?
|
||||
// account_data: any,
|
||||
};
|
||||
```
|
15
content/docs/data.md
Normal file
15
content/docs/data.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
+++
|
||||
title = "data"
|
||||
+++
|
||||
|
||||
list of data structures
|
||||
|
||||
## events
|
||||
|
||||
`m.message`
|
||||
|
||||
```js
|
||||
{
|
||||
"m.text": "bar"
|
||||
}
|
||||
```
|
33
content/docs/design.md
Normal file
33
content/docs/design.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
+++
|
||||
title = "design"
|
||||
+++
|
||||
|
||||
there are some design choices
|
||||
|
||||
## faq
|
||||
|
||||
### no main instance
|
||||
|
||||
i'm a programmer not a moderator and don't want to deal with that
|
||||
|
||||
yeah it will hurt adoption, but the goal isn't to amass a ton of users
|
||||
|
||||
## ui
|
||||
|
||||
### why thread only
|
||||
|
||||
it strikes a nice balance between async and synchronous.
|
||||
|
||||
i *may* add fully async tree-style reply style rooms.
|
||||
|
||||
fully synchronous chat is not planned.
|
||||
|
||||
## technical
|
||||
|
||||
### the api is not restful
|
||||
|
||||
it uses batching
|
||||
|
||||
### why long polling
|
||||
|
||||
http/3
|
12
content/docs/hello.md
Normal file
12
content/docs/hello.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
+++
|
||||
title = "hello"
|
||||
+++
|
||||
|
||||
welcome to the project, here's documentation
|
||||
|
||||
- user docs for new people
|
||||
- advanced docs for power users, mods/admins
|
||||
- operator docs for running a server
|
||||
- developer docs for developing bots and working with the api
|
||||
- specification
|
||||
- useful links to repos
|
70
content/docs/install.md
Normal file
70
content/docs/install.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
+++
|
||||
title = "install"
|
||||
+++
|
||||
|
||||
## docker compose
|
||||
|
||||
it's planned to be modular and composed of multiple parts
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
# This is the main jackwagon server
|
||||
backend:
|
||||
# image: matrixconduit/matrix-conduit:latest
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
CREATED: '2021-03-16T08:18:27Z'
|
||||
VERSION: '0.1.0'
|
||||
LOCAL: 'false'
|
||||
GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8448:6167
|
||||
volumes:
|
||||
- db:/var/lib/matrix-conduit/
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit/
|
||||
CONDUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUIT_PORT: 6167
|
||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
||||
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
CONDUIT_CONFIG: '' # Ignore this
|
||||
|
||||
# the main frontend
|
||||
frontend:
|
||||
image: vectorim/element-web:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8009:80
|
||||
|
||||
# handle turn/voip, you can also run this on other servers
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3478:3478
|
||||
- 3478:3478/udp
|
||||
- 5349:5349
|
||||
- 5349:5349/udp
|
||||
- 49152-65535:49152-65535/udp
|
||||
|
||||
# database
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: example
|
||||
|
||||
volumes:
|
||||
db:
|
||||
```
|
70
content/docs/syncing.md
Normal file
70
content/docs/syncing.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
+++
|
||||
title = "syncing"
|
||||
+++
|
||||
|
||||
how to sync
|
||||
|
||||
call `POST /v1/sync` (or `QUERY /v1/sync`?) in a loop
|
||||
|
||||
## goals
|
||||
|
||||
1. Be able to incrementally update a local offline copy
|
||||
2. Be able to fetch only what is needed
|
||||
|
||||
- query and update state, threads, members
|
||||
|
||||
todo: write out
|
||||
|
||||
```ts
|
||||
type Query = {
|
||||
state: Array<[string, string]>, // (event_type, state_key)
|
||||
subscribe: boolean,
|
||||
threads: {
|
||||
limit: number,
|
||||
timeline_limit: number,
|
||||
},
|
||||
};
|
||||
|
||||
type Request = {
|
||||
conn_id: string,
|
||||
pos: string,
|
||||
delta_token: string,
|
||||
|
||||
lists: Record<string, null | {
|
||||
limit: number,
|
||||
filters: {
|
||||
types: Array<string>,
|
||||
purposes: Array<string>,
|
||||
spaces: Array<RoomId>,
|
||||
tombstoned: Array<RoomId>,
|
||||
},
|
||||
} & Query>,
|
||||
|
||||
rooms: Record<RoomId, null | Query>,
|
||||
|
||||
extensions: {
|
||||
presence: {
|
||||
enabled: true,
|
||||
},
|
||||
account_data: {
|
||||
enabled: true,
|
||||
types?: Array<string>,
|
||||
rooms?: Array<RoomId>,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Reponse = {
|
||||
lists: Record<string, null | {
|
||||
count: number,
|
||||
}>,
|
||||
|
||||
rooms: Record<RoomId, {
|
||||
initial?: true,
|
||||
state: Array<StateEvent>,
|
||||
threads: Array<Event>,
|
||||
timeline: Array<Event>,
|
||||
}>,
|
||||
}
|
||||
```
|
||||
|
1
public/fonts.css
Normal file
1
public/fonts.css
Normal file
|
@ -0,0 +1 @@
|
|||
@font-face{font-family:"Atkinson Hyperlegible";font-style:normal;font-weight:normal;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Regular.ttf)}@font-face{font-family:"Atkinson Hyperlegible";font-style:normal;font-weight:bold;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Bold.ttf)}@font-face{font-family:"Atkinson Hyperlegible";font-style:italic;font-weight:normal;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Italic.ttf)}@font-face{font-family:"Atkinson Hyperlegible";font-style:italic;font-weight:bold;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf)}@font-face{font-family:"Iosevka Zesty";font-style:normal;font-weight:normal;font-display:swap;src:url(/fonts/iosevka/iosevka-zesty.ttf)}
|
BIN
public/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Bold.ttf
Normal file
BIN
public/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Bold.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
93
public/fonts/atkinson-hyperlegible/OFL.txt
Normal file
93
public/fonts/atkinson-hyperlegible/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2020 Braille Institute of America, Inc.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
public/fonts/iosevka/iosevka-zesty-bold-extended-italic.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-bold-extended-italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty-bold-extended.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-bold-extended.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty-bold-italic.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-bold-italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty-bold.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty-extended-italic.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-extended-italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty-extended.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-extended.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty-italic.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty-italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/iosevka/iosevka-zesty.ttf
Normal file
BIN
public/fonts/iosevka/iosevka-zesty.ttf
Normal file
Binary file not shown.
BIN
public/psyqui.mp4
Normal file
BIN
public/psyqui.mp4
Normal file
Binary file not shown.
1
public/style.css
Normal file
1
public/style.css
Normal file
|
@ -0,0 +1 @@
|
|||
@font-face{font-family:"Atkinson Hyperlegible";font-style:normal;font-weight:normal;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Regular.ttf)}@font-face{font-family:"Atkinson Hyperlegible";font-style:normal;font-weight:bold;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Bold.ttf)}@font-face{font-family:"Atkinson Hyperlegible";font-style:italic;font-weight:normal;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Italic.ttf)}@font-face{font-family:"Atkinson Hyperlegible";font-style:italic;font-weight:bold;font-display:swap;src:url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf)}@font-face{font-family:"Iosevka Zesty";font-style:normal;font-weight:normal;font-display:swap;src:url(/fonts/iosevka/iosevka-zesty.ttf)}:root{font:16px/1.5 "Atkinson Hyperlegible",Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--background-1: #24262b;--background-2: #1e2024;--background-3: #191b1d;--background-4: #17181a;--foreground-1: #eae8efdd;--foreground-2: #eae8efb4}*{box-sizing:border-box;margin:0;padding:0}body{background:var(--background-1);color:var(--foreground-1);margin:0}section,main{padding:2rem max(50vw - 350px,16px)}#blurb{width:50%;padding:2rem;padding-left:max(50vw - 350px,16px);background:var(--background-2)}#blurb>h1{text-shadow:1px 1px #822eba,2px 2px rgba(130,46,186,.8),3px 3px rgba(130,46,186,.6),4px 4px rgba(130,46,186,.4),5px 5px rgba(130,46,186,.2);margin-bottom:2rem}#blurb a{padding:8px 16px;background:#555;color:var(--foreground-1);text-decoration:none;border-radius:2px;cursor:pointer}#video{display:flex;gap:1rem;overflow-x:auto;margin-left:40%;width:60%;height:350px;padding:2rem;background:var(--background-3)}#video>video{height:100%}a{color:#bd7aea}#about{display:flex;gap:16px}#about>div{flex:1}ol,ul{padding-left:1rem}@media (max-width: 750px){#blurb,#video{width:100%;padding:2rem;margin-left:0}#about{flex-direction:column}}svg{position:fixed;bottom:0}pre{padding:4px;border-radius:4px;background:var(--background-3);overflow-x:auto}#line{animation:move 999999s}@keyframes move{from{stroke-dashoffset:0}to{stroke-dashoffset:10000000}}nav{display:flex;flex-direction:column;float:left;width:256px;padding:8px;background:var(--background-2)}table{border:solid var(--background-3) 1px;width:100%;border-collapse:collapse}table tr{background:var(--background-2)}table tr:nth-child(even){background:var(--background-3)}table thead tr{background:var(--background-4)}table td,table th{padding:4px;text-align:left}
|
39
sass/fonts.scss
Normal file
39
sass/fonts.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
src: url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Regular.ttf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
font-display: swap;
|
||||
src: url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Bold.ttf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
src: url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-Italic.ttf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
font-display: swap;
|
||||
src: url(/fonts/atkinson-hyperlegible/AtkinsonHyperlegible-BoldItalic.ttf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Iosevka Zesty";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
src: url(/fonts/iosevka/iosevka-zesty.ttf);
|
||||
}
|
155
sass/style.scss
Normal file
155
sass/style.scss
Normal file
|
@ -0,0 +1,155 @@
|
|||
@import "./fonts.scss";
|
||||
|
||||
:root {
|
||||
font: 16px/1.5 "Atkinson Hyperlegible", Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
--background-1: #24262b;
|
||||
--background-2: #1e2024;
|
||||
--background-3: #191b1d;
|
||||
--background-4: #17181a;
|
||||
--foreground-1: #eae8efdd;
|
||||
--foreground-2: #eae8efb4;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background-1);
|
||||
color: var(--foreground-1);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
section, main {
|
||||
padding: 2rem max(calc(50vw - 350px), 16px);
|
||||
}
|
||||
|
||||
#blurb {
|
||||
width: 50%;
|
||||
padding: 2rem;
|
||||
padding-left: max(calc(50vw - 350px), 16px);
|
||||
background: var(--background-2);
|
||||
}
|
||||
|
||||
#blurb > h1 {
|
||||
text-shadow:
|
||||
1px 1px #822ebaff,
|
||||
2px 2px #822ebacc,
|
||||
3px 3px #822eba99,
|
||||
4px 4px #822eba66,
|
||||
5px 5px #822eba33;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
#blurb a {
|
||||
padding: 8px 16px;
|
||||
background: #555;
|
||||
color: var(--foreground-1);
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#video {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
overflow-x: auto;
|
||||
margin-left: 40%;
|
||||
width: 60%;
|
||||
height: 350px;
|
||||
padding: 2rem;
|
||||
background: var(--background-3);
|
||||
}
|
||||
|
||||
#video > video {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #bd7aea;
|
||||
}
|
||||
|
||||
#about {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
#about > div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
#blurb, #video {
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#about {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
background: var(--background-3);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#line {
|
||||
animation: move 999999s;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
from { stroke-dashoffset: 0; }
|
||||
to { stroke-dashoffset: 10000000; }
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
float: left;
|
||||
width: 256px;
|
||||
padding: 8px;
|
||||
background: var(--background-2);
|
||||
}
|
||||
|
||||
table {
|
||||
border: solid var(--background-3) 1px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
& tr {
|
||||
background: var(--background-2);
|
||||
|
||||
&:nth-child(even) {
|
||||
background: var(--background-3);
|
||||
}
|
||||
}
|
||||
|
||||
& thead tr {
|
||||
background: var(--background-4);
|
||||
}
|
||||
|
||||
& td, th {
|
||||
padding: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
|
@ -1 +0,0 @@
|
|||
# Chapter 1
|
1
static/fonts
Symbolic link
1
static/fonts
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../frontend-web/src/assets/fonts/
|
BIN
static/psyqui.mp4
Normal file
BIN
static/psyqui.mp4
Normal file
Binary file not shown.
6
templates/404.html
Normal file
6
templates/404.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% block content %}
|
||||
<main>
|
||||
<h1>404</h1>
|
||||
<p>not found - <a href="/">back to home</a></p>
|
||||
</main>
|
||||
{% endblock %}
|
13
templates/base.html
Normal file
13
templates/base.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>{% block title %}unnamed{% endblock %}</title>
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
20
templates/docs-page.html
Normal file
20
templates/docs-page.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ page.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% set section = get_section(path="docs/_index.md") %}
|
||||
|
||||
<nav>
|
||||
<b>pages</b>
|
||||
{% for page in section.pages %}
|
||||
<a href="{{page.path}}">{{page.title}}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
<main>
|
||||
<h1>{{ page.title }}</h1>
|
||||
{{ page.content | safe }}
|
||||
</main>
|
||||
{% endblock %}
|
56
templates/index.html
Normal file
56
templates/index.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}home{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
</header>
|
||||
<section id="blurb">
|
||||
<h1>jackwagon is federated e2ee thread only chats done right</h1>
|
||||
<a href="#" style="background:#822eba">try it out</a> <a href="/docs/hello">read docs</a>
|
||||
</section>
|
||||
<section id="video">
|
||||
<video src="./psyqui.mp4" controls></video>
|
||||
</section>
|
||||
<section id="about">
|
||||
<div>
|
||||
<h2>why</h2>
|
||||
<ul>
|
||||
<li>Like zulip or discord forum channels, but but taken to its logical conclusion</li>
|
||||
<li>Federated/decentralized and built to last</li>
|
||||
<li>Aiming to be a complete and usable system out of the box, with minimal setup or maintenence</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2>why not</h2>
|
||||
<ul>
|
||||
<li>It kinda does its own thing and won't easily replace $APP</li>
|
||||
<li>It's yet another platform that you'll have to convince your friends to use</li>
|
||||
<li>Extremely early alpha that probably shouldn't even been released</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2>other info</h2>
|
||||
<ul>
|
||||
<li>Forked from <a href="https://matrix.org">matrix</a> and <a href="https://conduit.rs">conduit</a> (but incompatible!)</li>
|
||||
<li>Free and open source software</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<svg width="100%" height="10px">
|
||||
<line id="line" stroke-dasharray="
|
||||
10, 10, 30, 10, 30, 10, 30, 30,
|
||||
10, 10, 30, 30,
|
||||
30, 10, 10, 10, 30, 10, 10, 30,
|
||||
30, 10, 10, 10, 30, 30,
|
||||
10, 10, 30, 10, 30, 30,
|
||||
10, 10, 30, 30,
|
||||
30, 10, 30, 10, 10, 30,
|
||||
30, 10, 30, 10, 30, 30,
|
||||
30, 10, 10, 30"
|
||||
x1="0" y1="5px" x2="100%" y2="5px"
|
||||
style="stroke: var(--background-2)" stroke-width="10"
|
||||
stroke-dashoffset="0"
|
||||
/>
|
||||
</svg>
|
||||
{% endblock %}
|
Loading…
Reference in a new issue