atproto pastebin service: https://plonk.li

Compare changes

Choose any two refs to compare.

+65 -2
flake.nix
··· 12 12 nixpkgsFor = forAllSystems (system: 13 13 import nixpkgs { 14 14 inherit system; 15 - overlays = [self.overlay.default]; 15 + overlays = [self.overlays.default]; 16 16 }); 17 17 in { 18 - overlay.default = final: prev: let 18 + overlays.default = final: prev: let 19 19 pname = "plonk"; 20 20 version = "0.1.0"; 21 21 in { ··· 23 23 buildNpmPackage { 24 24 inherit pname version; 25 25 src = ./.; 26 + nativeBuildInputs = [makeBinaryWrapper]; 26 27 packageJson = ./package.json; 27 28 buildPhase = "npm run build"; 29 + installPhase = '' 30 + runHook preInstall 31 + 32 + mkdir -p $out/bin 33 + cp -r ./* $out/ 34 + 35 + makeBinaryWrapper ${nodejs}/bin/node $out/bin/$pname \ 36 + --prefix PATH : ${lib.makeBinPath [nodejs]} \ 37 + --add-flags "$out/dist/index.js" 38 + ''; 39 + 28 40 npmDepsHash = "sha256-qGCbaFAHd/s9hOTWMjHCam6Kf6pU6IWPybfwYh0sOwc="; 29 41 }; 30 42 }; ··· 48 60 }); 49 61 50 62 formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra); 63 + 64 + nixosModules.default = { 65 + config, 66 + pkgs, 67 + lib, 68 + ... 69 + }: 70 + with lib; { 71 + options = { 72 + services.plonk = { 73 + enable = mkOption { 74 + type = types.bool; 75 + default = false; 76 + description = "Enable plonk"; 77 + }; 78 + port = mkOption { 79 + type = types.int; 80 + default = 3000; 81 + description = "Port to run plonk on"; 82 + }; 83 + cookie_secret = mkOption { 84 + type = types.str; 85 + default = "00000000000000000000000000000000"; 86 + description = "Cookie secret"; 87 + }; 88 + }; 89 + }; 90 + 91 + config = mkIf config.services.plonk.enable { 92 + nixpkgs.overlays = [self.overlays.default]; 93 + systemd.services.plonk = { 94 + description = "plonk service"; 95 + wantedBy = ["multi-user.target"]; 96 + 97 + serviceConfig = { 98 + ListenStream = "0.0.0.0:${toString config.services.plonk.port}"; 99 + ExecStart = "${pkgs.plonk}/bin/plonk"; 100 + Restart = "always"; 101 + }; 102 + 103 + environment = { 104 + PLONK_PORT = "${toString config.services.plonk.port}"; 105 + PLONK_NODE_ENV = "production"; 106 + PLONK_HOST = "localhost"; 107 + PLONK_PUBLIC_URL = "https://plonk.li"; 108 + PLONK_DB_PATH = "plonk.db"; 109 + PLONK_COOKIE_SECRET = config.services.plonk.cookie_secret; 110 + }; 111 + }; 112 + }; 113 + }; 51 114 }; 52 115 }
+35
lexicons/comment.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "li.plonk.comment", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "content", 12 + "createdAt", 13 + "post" 14 + ], 15 + "properties": { 16 + "content": { 17 + "type": "string", 18 + "maxLength": 100000, 19 + "maxGraphemes": 10000, 20 + "description": "comment body" 21 + }, 22 + "createdAt": { 23 + "type": "string", 24 + "format": "datetime", 25 + "description": "comment creation timestamp" 26 + }, 27 + "post": { 28 + "type": "ref", 29 + "ref": "com.atproto.repo.strongRef" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + }
+67
lexicons/getRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.getRecord", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a single record from a repository. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "repo", 12 + "collection", 13 + "rkey" 14 + ], 15 + "properties": { 16 + "repo": { 17 + "type": "string", 18 + "format": "at-identifier", 19 + "description": "The handle or DID of the repo." 20 + }, 21 + "collection": { 22 + "type": "string", 23 + "format": "nsid", 24 + "description": "The NSID of the record collection." 25 + }, 26 + "rkey": { 27 + "type": "string", 28 + "description": "The Record Key." 29 + }, 30 + "cid": { 31 + "type": "string", 32 + "format": "cid", 33 + "description": "The CID of the version of the record. If not specified, then return the most recent version." 34 + } 35 + } 36 + }, 37 + "output": { 38 + "encoding": "application/json", 39 + "schema": { 40 + "type": "object", 41 + "required": [ 42 + "uri", 43 + "value" 44 + ], 45 + "properties": { 46 + "uri": { 47 + "type": "string", 48 + "format": "at-uri" 49 + }, 50 + "cid": { 51 + "type": "string", 52 + "format": "cid" 53 + }, 54 + "value": { 55 + "type": "unknown" 56 + } 57 + } 58 + } 59 + }, 60 + "errors": [ 61 + { 62 + "name": "RecordNotFound" 63 + } 64 + ] 65 + } 66 + } 67 + }
+93
lexicons/listRecords.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.listRecords", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "List a range of records in a repository, matching a specific collection. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "repo", 12 + "collection" 13 + ], 14 + "properties": { 15 + "repo": { 16 + "type": "string", 17 + "format": "at-identifier", 18 + "description": "The handle or DID of the repo." 19 + }, 20 + "collection": { 21 + "type": "string", 22 + "format": "nsid", 23 + "description": "The NSID of the record type." 24 + }, 25 + "limit": { 26 + "type": "integer", 27 + "minimum": 1, 28 + "maximum": 100, 29 + "default": 50, 30 + "description": "The number of records to return." 31 + }, 32 + "cursor": { 33 + "type": "string" 34 + }, 35 + "rkeyStart": { 36 + "type": "string", 37 + "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)" 38 + }, 39 + "rkeyEnd": { 40 + "type": "string", 41 + "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)" 42 + }, 43 + "reverse": { 44 + "type": "boolean", 45 + "description": "Flag to reverse the order of the returned records." 46 + } 47 + } 48 + }, 49 + "output": { 50 + "encoding": "application/json", 51 + "schema": { 52 + "type": "object", 53 + "required": [ 54 + "records" 55 + ], 56 + "properties": { 57 + "cursor": { 58 + "type": "string" 59 + }, 60 + "records": { 61 + "type": "array", 62 + "items": { 63 + "type": "ref", 64 + "ref": "#record" 65 + } 66 + } 67 + } 68 + } 69 + } 70 + }, 71 + "record": { 72 + "type": "object", 73 + "required": [ 74 + "uri", 75 + "cid", 76 + "value" 77 + ], 78 + "properties": { 79 + "uri": { 80 + "type": "string", 81 + "format": "at-uri" 82 + }, 83 + "cid": { 84 + "type": "string", 85 + "format": "cid" 86 + }, 87 + "value": { 88 + "type": "unknown" 89 + } 90 + } 91 + } 92 + } 93 + }
+1 -1
lexicons/paste.json
··· 1 1 { 2 2 "lexicon": 1, 3 - "id": "ovh.plonk.paste", 3 + "id": "li.plonk.paste", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record",
+4
package.json
··· 49 49 "!src/**/__tests__/**", 50 50 "!src/**/*.test.*" 51 51 ], 52 + "loader": { 53 + ".pug": "copy", 54 + ".woff2": "copy" 55 + }, 52 56 "splitting": false, 53 57 "sourcemap": true, 54 58 "clean": true
src/assets/NerdIosevka-Regular.woff2

This is a binary file and will not be displayed.

+4 -4
src/auth/client.ts
··· 4 4 import { SessionStore, StateStore } from "./storage"; 5 5 6 6 export const createClient = async (db: Database) => { 7 - const publicUrl = env.PUBLIC_URL; 8 - const url = publicUrl || `http://127.0.0.1:${env.PORT}`; 7 + const publicUrl = env.PLONK_PUBLIC_URL; 8 + const url = publicUrl || `http://127.0.0.1:${env.PLONK_PORT}`; 9 9 const enc = encodeURIComponent; 10 10 return new NodeOAuthClient({ 11 11 clientMetadata: { 12 - client_name: "AT Protocol Express App", 12 + client_name: "plonk.li", 13 13 client_id: publicUrl 14 14 ? `${url}/client-metadata.json` 15 - : `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`, 15 + : `http://${env.PLONK_HOST}?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`, 16 16 client_uri: url, 17 17 redirect_uris: [`${url}/oauth/callback`], 18 18 scope: "atproto transition:generic",
+2 -2
src/db.ts
··· 47 47 indexedAt: string; 48 48 pasteUri: string; 49 49 pasteCid: string; 50 - } 50 + }; 51 51 52 52 type AuthSessionJson = string; 53 53 type AuthStateJson = string; ··· 115 115 async down(db: Kysely<unknown>) { 116 116 await db.schema.dropTable("comments").execute(); 117 117 }, 118 - } 118 + }; 119 119 120 120 function generateShortString(length: number): string { 121 121 return randomBytes(length).toString("base64url").substring(0, length);
+2 -2
src/index.ts
··· 32 32 ) {} 33 33 34 34 static async create() { 35 - const db: Database = createDb(env.DB_PATH); 35 + const db: Database = createDb(env.PLONK_DB_PATH); 36 36 await migrateToLatest(db); 37 37 const idResolver = createIdResolver(); 38 38 const ctx: Ctx = { ··· 55 55 app.use(router); 56 56 app.use((_req, res) => res.sendStatus(404)); 57 57 58 - const server = app.listen(env.PORT); 58 + const server = app.listen(env.PLONK_PORT); 59 59 60 60 return new Server(app, server, ctx); 61 61 }
+8 -8
src/ingester.ts
··· 2 2 import { IdResolver } from "@atproto/identity"; 3 3 import { Firehose } from "@atproto/sync"; 4 4 import type { Database } from "#/db"; 5 - import * as Paste from "#/lexicons/types/ovh/plonk/paste"; 6 - import * as Comment from "#/lexicons/types/ovh/plonk/comment"; 5 + import * as Paste from "#/lexicons/types/li/plonk/paste"; 6 + import * as Comment from "#/lexicons/types/li/plonk/comment"; 7 7 8 8 export function createIngester(db: Database, idResolver: IdResolver) { 9 9 const logger = pino({ name: "firehose ingestion" }); ··· 17 17 18 18 // If the write is a valid status update 19 19 if ( 20 - evt.collection === "ovh.plonk.paste" && 20 + evt.collection === "li.plonk.paste" && 21 21 Paste.isRecord(record) && 22 22 Paste.validateRecord(record).success 23 23 ) { ··· 43 43 ) 44 44 .execute(); 45 45 } else if ( 46 - evt.collection === "ovh.plonk.comment" && 46 + evt.collection === "li.plonk.comment" && 47 47 Comment.isRecord(record) && 48 48 Comment.validateRecord(record).success 49 49 ) { ··· 70 70 } 71 71 } else if ( 72 72 evt.event === "delete" && 73 - evt.collection === "ovh.plonk.paste" 73 + evt.collection === "li.plonk.paste" 74 74 ) { 75 75 // Remove the status from our SQLite 76 76 await db ··· 79 79 .execute(); 80 80 } else if ( 81 81 evt.event === "delete" && 82 - evt.collection === "ovh.plonk.comment" 82 + evt.collection === "li.plonk.comment" 83 83 ) { 84 84 // Remove the status from our SQLite 85 85 await db 86 86 .deleteFrom("comment") 87 87 .where("uri", "=", evt.uri.toString()) 88 88 .execute(); 89 - } 89 + } 90 90 }, 91 91 onError: (err) => { 92 92 logger.error({ err }, "error on firehose ingestion"); 93 93 }, 94 - filterCollections: ["ovh.plonk.paste"], 94 + filterCollections: ["li.plonk.paste"], 95 95 excludeIdentity: true, 96 96 excludeAccount: true, 97 97 });
+105 -105
src/lexicons/index.ts
··· 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 4 import { 5 - createServer as createXrpcServer, 6 - Server as XrpcServer, 7 - Options as XrpcOptions, 8 - AuthVerifier, 9 - StreamAuthVerifier, 10 - } from '@atproto/xrpc-server' 11 - import { schemas } from './lexicons' 12 - import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' 13 - import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' 5 + createServer as createXrpcServer, 6 + Server as XrpcServer, 7 + Options as XrpcOptions, 8 + AuthVerifier, 9 + StreamAuthVerifier, 10 + } from "@atproto/xrpc-server"; 11 + import { schemas } from "./lexicons"; 12 + import * as ComAtprotoRepoGetRecord from "./types/com/atproto/repo/getRecord"; 13 + import * as ComAtprotoRepoListRecords from "./types/com/atproto/repo/listRecords"; 14 14 15 15 export function createServer(options?: XrpcOptions): Server { 16 - return new Server(options) 16 + return new Server(options); 17 17 } 18 18 19 19 export class Server { 20 - xrpc: XrpcServer 21 - ovh: OvhNS 22 - com: ComNS 23 - app: AppNS 20 + xrpc: XrpcServer; 21 + li: LiNS; 22 + com: ComNS; 23 + app: AppNS; 24 24 25 - constructor(options?: XrpcOptions) { 26 - this.xrpc = createXrpcServer(schemas, options) 27 - this.ovh = new OvhNS(this) 28 - this.com = new ComNS(this) 29 - this.app = new AppNS(this) 30 - } 25 + constructor(options?: XrpcOptions) { 26 + this.xrpc = createXrpcServer(schemas, options); 27 + this.li = new LiNS(this); 28 + this.com = new ComNS(this); 29 + this.app = new AppNS(this); 30 + } 31 31 } 32 32 33 - export class OvhNS { 34 - _server: Server 35 - plonk: OvhPlonkNS 33 + export class LiNS { 34 + _server: Server; 35 + plonk: LiPlonkNS; 36 36 37 - constructor(server: Server) { 38 - this._server = server 39 - this.plonk = new OvhPlonkNS(server) 40 - } 37 + constructor(server: Server) { 38 + this._server = server; 39 + this.plonk = new LiPlonkNS(server); 40 + } 41 41 } 42 42 43 - export class OvhPlonkNS { 44 - _server: Server 43 + export class LiPlonkNS { 44 + _server: Server; 45 45 46 - constructor(server: Server) { 47 - this._server = server 48 - } 46 + constructor(server: Server) { 47 + this._server = server; 48 + } 49 49 } 50 50 51 51 export class ComNS { 52 - _server: Server 53 - atproto: ComAtprotoNS 52 + _server: Server; 53 + atproto: ComAtprotoNS; 54 54 55 - constructor(server: Server) { 56 - this._server = server 57 - this.atproto = new ComAtprotoNS(server) 58 - } 55 + constructor(server: Server) { 56 + this._server = server; 57 + this.atproto = new ComAtprotoNS(server); 58 + } 59 59 } 60 60 61 61 export class ComAtprotoNS { 62 - _server: Server 63 - repo: ComAtprotoRepoNS 62 + _server: Server; 63 + repo: ComAtprotoRepoNS; 64 64 65 - constructor(server: Server) { 66 - this._server = server 67 - this.repo = new ComAtprotoRepoNS(server) 68 - } 65 + constructor(server: Server) { 66 + this._server = server; 67 + this.repo = new ComAtprotoRepoNS(server); 68 + } 69 69 } 70 70 71 71 export class ComAtprotoRepoNS { 72 - _server: Server 72 + _server: Server; 73 73 74 - constructor(server: Server) { 75 - this._server = server 76 - } 74 + constructor(server: Server) { 75 + this._server = server; 76 + } 77 77 78 - getRecord<AV extends AuthVerifier>( 79 - cfg: ConfigOf< 80 - AV, 81 - ComAtprotoRepoGetRecord.Handler<ExtractAuth<AV>>, 82 - ComAtprotoRepoGetRecord.HandlerReqCtx<ExtractAuth<AV>> 83 - >, 84 - ) { 85 - const nsid = 'com.atproto.repo.getRecord' // @ts-ignore 86 - return this._server.xrpc.method(nsid, cfg) 87 - } 78 + getRecord<AV extends AuthVerifier>( 79 + cfg: ConfigOf< 80 + AV, 81 + ComAtprotoRepoGetRecord.Handler<ExtractAuth<AV>>, 82 + ComAtprotoRepoGetRecord.HandlerReqCtx<ExtractAuth<AV>> 83 + >, 84 + ) { 85 + const nsid = "com.atproto.repo.getRecord"; // @ts-ignore 86 + return this._server.xrpc.method(nsid, cfg); 87 + } 88 88 89 - listRecords<AV extends AuthVerifier>( 90 - cfg: ConfigOf< 91 - AV, 92 - ComAtprotoRepoListRecords.Handler<ExtractAuth<AV>>, 93 - ComAtprotoRepoListRecords.HandlerReqCtx<ExtractAuth<AV>> 94 - >, 95 - ) { 96 - const nsid = 'com.atproto.repo.listRecords' // @ts-ignore 97 - return this._server.xrpc.method(nsid, cfg) 98 - } 89 + listRecords<AV extends AuthVerifier>( 90 + cfg: ConfigOf< 91 + AV, 92 + ComAtprotoRepoListRecords.Handler<ExtractAuth<AV>>, 93 + ComAtprotoRepoListRecords.HandlerReqCtx<ExtractAuth<AV>> 94 + >, 95 + ) { 96 + const nsid = "com.atproto.repo.listRecords"; // @ts-ignore 97 + return this._server.xrpc.method(nsid, cfg); 98 + } 99 99 } 100 100 101 101 export class AppNS { 102 - _server: Server 103 - bsky: AppBskyNS 102 + _server: Server; 103 + bsky: AppBskyNS; 104 104 105 - constructor(server: Server) { 106 - this._server = server 107 - this.bsky = new AppBskyNS(server) 108 - } 105 + constructor(server: Server) { 106 + this._server = server; 107 + this.bsky = new AppBskyNS(server); 108 + } 109 109 } 110 110 111 111 export class AppBskyNS { 112 - _server: Server 113 - actor: AppBskyActorNS 112 + _server: Server; 113 + actor: AppBskyActorNS; 114 114 115 - constructor(server: Server) { 116 - this._server = server 117 - this.actor = new AppBskyActorNS(server) 118 - } 115 + constructor(server: Server) { 116 + this._server = server; 117 + this.actor = new AppBskyActorNS(server); 118 + } 119 119 } 120 120 121 121 export class AppBskyActorNS { 122 - _server: Server 122 + _server: Server; 123 123 124 - constructor(server: Server) { 125 - this._server = server 126 - } 124 + constructor(server: Server) { 125 + this._server = server; 126 + } 127 127 } 128 128 129 129 type SharedRateLimitOpts<T> = { 130 - name: string 131 - calcKey?: (ctx: T) => string 132 - calcPoints?: (ctx: T) => number 133 - } 130 + name: string; 131 + calcKey?: (ctx: T) => string; 132 + calcPoints?: (ctx: T) => number; 133 + }; 134 134 type RouteRateLimitOpts<T> = { 135 - durationMs: number 136 - points: number 137 - calcKey?: (ctx: T) => string 138 - calcPoints?: (ctx: T) => number 139 - } 140 - type HandlerOpts = { blobLimit?: number } 141 - type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T> 135 + durationMs: number; 136 + points: number; 137 + calcKey?: (ctx: T) => string; 138 + calcPoints?: (ctx: T) => number; 139 + }; 140 + type HandlerOpts = { blobLimit?: number }; 141 + type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>; 142 142 type ConfigOf<Auth, Handler, ReqCtx> = 143 - | Handler 144 - | { 145 - auth?: Auth 146 - opts?: HandlerOpts 147 - rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[] 148 - handler: Handler 149 - } 143 + | Handler 144 + | { 145 + auth?: Auth; 146 + opts?: HandlerOpts; 147 + rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]; 148 + handler: Handler; 149 + }; 150 150 type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract< 151 - Awaited<ReturnType<AV>>, 152 - { credentials: unknown } 153 - > 151 + Awaited<ReturnType<AV>>, 152 + { credentials: unknown } 153 + >;
+491 -491
src/lexicons/lexicons.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { LexiconDoc, Lexicons } from '@atproto/lexicon' 4 + import { LexiconDoc, Lexicons } from "@atproto/lexicon"; 5 5 6 6 export const schemaDict = { 7 - OvhPlonkComment: { 8 - lexicon: 1, 9 - id: 'ovh.plonk.comment', 10 - defs: { 11 - main: { 12 - type: 'record', 13 - key: 'tid', 14 - record: { 15 - type: 'object', 16 - required: ['content', 'createdAt', 'post'], 17 - properties: { 18 - content: { 19 - type: 'string', 20 - maxLength: 100000, 21 - maxGraphemes: 10000, 22 - description: 'comment body', 23 - }, 24 - createdAt: { 25 - type: 'string', 26 - format: 'datetime', 27 - description: 'comment creation timestamp', 28 - }, 29 - post: { 30 - type: 'ref', 31 - ref: 'lex:com.atproto.repo.strongRef', 32 - }, 33 - }, 34 - }, 35 - }, 36 - }, 37 - }, 38 - ComAtprotoLabelDefs: { 39 - lexicon: 1, 40 - id: 'com.atproto.label.defs', 41 - defs: { 42 - label: { 43 - type: 'object', 44 - description: 45 - 'Metadata tag on an atproto resource (eg, repo or record).', 46 - required: ['src', 'uri', 'val', 'cts'], 47 - properties: { 48 - ver: { 49 - type: 'integer', 50 - description: 'The AT Protocol version of the label object.', 51 - }, 52 - src: { 53 - type: 'string', 54 - format: 'did', 55 - description: 'DID of the actor who created this label.', 56 - }, 57 - uri: { 58 - type: 'string', 59 - format: 'uri', 60 - description: 61 - 'AT URI of the record, repository (account), or other resource that this label applies to.', 62 - }, 63 - cid: { 64 - type: 'string', 65 - format: 'cid', 66 - description: 67 - "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 68 - }, 69 - val: { 70 - type: 'string', 71 - maxLength: 128, 72 - description: 73 - 'The short string name of the value or type of this label.', 74 - }, 75 - neg: { 76 - type: 'boolean', 77 - description: 78 - 'If true, this is a negation label, overwriting a previous label.', 79 - }, 80 - cts: { 81 - type: 'string', 82 - format: 'datetime', 83 - description: 'Timestamp when this label was created.', 84 - }, 85 - exp: { 86 - type: 'string', 87 - format: 'datetime', 88 - description: 89 - 'Timestamp at which this label expires (no longer applies).', 90 - }, 91 - sig: { 92 - type: 'bytes', 93 - description: 'Signature of dag-cbor encoded label.', 94 - }, 95 - }, 96 - }, 97 - selfLabels: { 98 - type: 'object', 99 - description: 100 - 'Metadata tags on an atproto record, published by the author within the record.', 101 - required: ['values'], 102 - properties: { 103 - values: { 104 - type: 'array', 105 - items: { 106 - type: 'ref', 107 - ref: 'lex:com.atproto.label.defs#selfLabel', 108 - }, 109 - maxLength: 10, 110 - }, 111 - }, 112 - }, 113 - selfLabel: { 114 - type: 'object', 115 - description: 116 - 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', 117 - required: ['val'], 118 - properties: { 119 - val: { 120 - type: 'string', 121 - maxLength: 128, 122 - description: 123 - 'The short string name of the value or type of this label.', 124 - }, 125 - }, 126 - }, 127 - labelValueDefinition: { 128 - type: 'object', 129 - description: 130 - 'Declares a label value and its expected interpretations and behaviors.', 131 - required: ['identifier', 'severity', 'blurs', 'locales'], 132 - properties: { 133 - identifier: { 134 - type: 'string', 135 - description: 136 - "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 137 - maxLength: 100, 138 - maxGraphemes: 100, 139 - }, 140 - severity: { 141 - type: 'string', 142 - description: 143 - "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 144 - knownValues: ['inform', 'alert', 'none'], 145 - }, 146 - blurs: { 147 - type: 'string', 148 - description: 149 - "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 150 - knownValues: ['content', 'media', 'none'], 151 - }, 152 - defaultSetting: { 153 - type: 'string', 154 - description: 'The default setting for this label.', 155 - knownValues: ['ignore', 'warn', 'hide'], 156 - default: 'warn', 157 - }, 158 - adultOnly: { 159 - type: 'boolean', 160 - description: 161 - 'Does the user need to have adult content enabled in order to configure this label?', 162 - }, 163 - locales: { 164 - type: 'array', 165 - items: { 166 - type: 'ref', 167 - ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', 168 - }, 169 - }, 170 - }, 171 - }, 172 - labelValueDefinitionStrings: { 173 - type: 'object', 174 - description: 175 - 'Strings which describe the label in the UI, localized into a specific language.', 176 - required: ['lang', 'name', 'description'], 177 - properties: { 178 - lang: { 179 - type: 'string', 180 - description: 181 - 'The code of the language these strings are written in.', 182 - format: 'language', 183 - }, 184 - name: { 185 - type: 'string', 186 - description: 'A short human-readable name for the label.', 187 - maxGraphemes: 64, 188 - maxLength: 640, 189 - }, 190 - description: { 191 - type: 'string', 192 - description: 193 - 'A longer description of what the label means and why it might be applied.', 194 - maxGraphemes: 10000, 195 - maxLength: 100000, 196 - }, 197 - }, 198 - }, 199 - labelValue: { 200 - type: 'string', 201 - knownValues: [ 202 - '!hide', 203 - '!no-promote', 204 - '!warn', 205 - '!no-unauthenticated', 206 - 'dmca-violation', 207 - 'doxxing', 208 - 'porn', 209 - 'sexual', 210 - 'nudity', 211 - 'nsfl', 212 - 'gore', 213 - ], 214 - }, 215 - }, 216 - }, 217 - ComAtprotoRepoGetRecord: { 218 - lexicon: 1, 219 - id: 'com.atproto.repo.getRecord', 220 - defs: { 221 - main: { 222 - type: 'query', 223 - description: 224 - 'Get a single record from a repository. Does not require auth.', 225 - parameters: { 226 - type: 'params', 227 - required: ['repo', 'collection', 'rkey'], 228 - properties: { 229 - repo: { 230 - type: 'string', 231 - format: 'at-identifier', 232 - description: 'The handle or DID of the repo.', 233 - }, 234 - collection: { 235 - type: 'string', 236 - format: 'nsid', 237 - description: 'The NSID of the record collection.', 238 - }, 239 - rkey: { 240 - type: 'string', 241 - description: 'The Record Key.', 242 - }, 243 - cid: { 244 - type: 'string', 245 - format: 'cid', 246 - description: 247 - 'The CID of the version of the record. If not specified, then return the most recent version.', 248 - }, 249 - }, 250 - }, 251 - output: { 252 - encoding: 'application/json', 253 - schema: { 254 - type: 'object', 255 - required: ['uri', 'value'], 256 - properties: { 257 - uri: { 258 - type: 'string', 259 - format: 'at-uri', 260 - }, 261 - cid: { 262 - type: 'string', 263 - format: 'cid', 264 - }, 265 - value: { 266 - type: 'unknown', 267 - }, 268 - }, 269 - }, 270 - }, 271 - errors: [ 272 - { 273 - name: 'RecordNotFound', 274 - }, 275 - ], 276 - }, 277 - }, 278 - }, 279 - ComAtprotoRepoListRecords: { 280 - lexicon: 1, 281 - id: 'com.atproto.repo.listRecords', 282 - defs: { 283 - main: { 284 - type: 'query', 285 - description: 286 - 'List a range of records in a repository, matching a specific collection. Does not require auth.', 287 - parameters: { 288 - type: 'params', 289 - required: ['repo', 'collection'], 290 - properties: { 291 - repo: { 292 - type: 'string', 293 - format: 'at-identifier', 294 - description: 'The handle or DID of the repo.', 295 - }, 296 - collection: { 297 - type: 'string', 298 - format: 'nsid', 299 - description: 'The NSID of the record type.', 300 - }, 301 - limit: { 302 - type: 'integer', 303 - minimum: 1, 304 - maximum: 100, 305 - default: 50, 306 - description: 'The number of records to return.', 307 - }, 308 - cursor: { 309 - type: 'string', 310 - }, 311 - rkeyStart: { 312 - type: 'string', 313 - description: 314 - 'DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)', 315 - }, 316 - rkeyEnd: { 317 - type: 'string', 318 - description: 319 - 'DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)', 320 - }, 321 - reverse: { 322 - type: 'boolean', 323 - description: 'Flag to reverse the order of the returned records.', 324 - }, 325 - }, 326 - }, 327 - output: { 328 - encoding: 'application/json', 329 - schema: { 330 - type: 'object', 331 - required: ['records'], 332 - properties: { 333 - cursor: { 334 - type: 'string', 335 - }, 336 - records: { 337 - type: 'array', 338 - items: { 339 - type: 'ref', 340 - ref: 'lex:com.atproto.repo.listRecords#record', 341 - }, 342 - }, 343 - }, 344 - }, 345 - }, 346 - }, 347 - record: { 348 - type: 'object', 349 - required: ['uri', 'cid', 'value'], 350 - properties: { 351 - uri: { 352 - type: 'string', 353 - format: 'at-uri', 354 - }, 355 - cid: { 356 - type: 'string', 357 - format: 'cid', 358 - }, 359 - value: { 360 - type: 'unknown', 361 - }, 362 - }, 363 - }, 364 - }, 365 - }, 366 - OvhPlonkPaste: { 367 - lexicon: 1, 368 - id: 'ovh.plonk.paste', 369 - defs: { 370 - main: { 371 - type: 'record', 372 - key: 'tid', 373 - record: { 374 - type: 'object', 375 - required: ['code', 'shortUrl', 'lang', 'title', 'createdAt'], 376 - properties: { 377 - code: { 378 - type: 'string', 379 - minLength: 1, 380 - maxGraphemes: 65536, 381 - maxLength: 65536, 382 - }, 383 - shortUrl: { 384 - type: 'string', 385 - minLength: 2, 386 - maxGraphemes: 10, 387 - maxLength: 10, 388 - }, 389 - lang: { 390 - type: 'string', 391 - minLength: 1, 392 - maxGraphemes: 20, 393 - maxLength: 20, 394 - }, 395 - title: { 396 - type: 'string', 397 - minLength: 1, 398 - maxGraphemes: 100, 399 - maxLength: 100, 400 - }, 401 - createdAt: { 402 - type: 'string', 403 - format: 'datetime', 404 - }, 405 - }, 406 - }, 407 - }, 408 - }, 409 - }, 410 - AppBskyActorProfile: { 411 - lexicon: 1, 412 - id: 'app.bsky.actor.profile', 413 - defs: { 414 - main: { 415 - type: 'record', 416 - description: 'A declaration of a Bluesky account profile.', 417 - key: 'literal:self', 418 - record: { 419 - type: 'object', 420 - properties: { 421 - displayName: { 422 - type: 'string', 423 - maxGraphemes: 64, 424 - maxLength: 640, 425 - }, 426 - description: { 427 - type: 'string', 428 - description: 'Free-form profile description text.', 429 - maxGraphemes: 256, 430 - maxLength: 2560, 431 - }, 432 - avatar: { 433 - type: 'blob', 434 - description: 435 - "Small image to be displayed next to posts from account. AKA, 'profile picture'", 436 - accept: ['image/png', 'image/jpeg'], 437 - maxSize: 1000000, 438 - }, 439 - banner: { 440 - type: 'blob', 441 - description: 442 - 'Larger horizontal image to display behind profile view.', 443 - accept: ['image/png', 'image/jpeg'], 444 - maxSize: 1000000, 445 - }, 446 - labels: { 447 - type: 'union', 448 - description: 449 - 'Self-label values, specific to the Bluesky application, on the overall account.', 450 - refs: ['lex:com.atproto.label.defs#selfLabels'], 451 - }, 452 - joinedViaStarterPack: { 453 - type: 'ref', 454 - ref: 'lex:com.atproto.repo.strongRef', 455 - }, 456 - createdAt: { 457 - type: 'string', 458 - format: 'datetime', 459 - }, 460 - }, 461 - }, 462 - }, 463 - }, 464 - }, 465 - ComAtprotoRepoStrongRef: { 466 - lexicon: 1, 467 - id: 'com.atproto.repo.strongRef', 468 - description: 'A URI with a content-hash fingerprint.', 469 - defs: { 470 - main: { 471 - type: 'object', 472 - required: ['uri', 'cid'], 473 - properties: { 474 - uri: { 475 - type: 'string', 476 - format: 'at-uri', 477 - }, 478 - cid: { 479 - type: 'string', 480 - format: 'cid', 481 - }, 482 - }, 483 - }, 484 - }, 485 - }, 486 - } 487 - export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] 488 - export const lexicons: Lexicons = new Lexicons(schemas) 7 + LiPlonkComment: { 8 + lexicon: 1, 9 + id: "li.plonk.comment", 10 + defs: { 11 + main: { 12 + type: "record", 13 + key: "tid", 14 + record: { 15 + type: "object", 16 + required: ["content", "createdAt", "post"], 17 + properties: { 18 + content: { 19 + type: "string", 20 + maxLength: 100000, 21 + maxGraphemes: 10000, 22 + description: "comment body", 23 + }, 24 + createdAt: { 25 + type: "string", 26 + format: "datetime", 27 + description: "comment creation timestamp", 28 + }, 29 + post: { 30 + type: "ref", 31 + ref: "lex:com.atproto.repo.strongRef", 32 + }, 33 + }, 34 + }, 35 + }, 36 + }, 37 + }, 38 + ComAtprotoLabelDefs: { 39 + lexicon: 1, 40 + id: "com.atproto.label.defs", 41 + defs: { 42 + label: { 43 + type: "object", 44 + description: 45 + "Metadata tag on an atproto resource (eg, repo or record).", 46 + required: ["src", "uri", "val", "cts"], 47 + properties: { 48 + ver: { 49 + type: "integer", 50 + description: "The AT Protocol version of the label object.", 51 + }, 52 + src: { 53 + type: "string", 54 + format: "did", 55 + description: "DID of the actor who created this label.", 56 + }, 57 + uri: { 58 + type: "string", 59 + format: "uri", 60 + description: 61 + "AT URI of the record, repository (account), or other resource that this label applies to.", 62 + }, 63 + cid: { 64 + type: "string", 65 + format: "cid", 66 + description: 67 + "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 68 + }, 69 + val: { 70 + type: "string", 71 + maxLength: 128, 72 + description: 73 + "The short string name of the value or type of this label.", 74 + }, 75 + neg: { 76 + type: "boolean", 77 + description: 78 + "If true, this is a negation label, overwriting a previous label.", 79 + }, 80 + cts: { 81 + type: "string", 82 + format: "datetime", 83 + description: "Timestamp when this label was created.", 84 + }, 85 + exp: { 86 + type: "string", 87 + format: "datetime", 88 + description: 89 + "Timestamp at which this label expires (no longer applies).", 90 + }, 91 + sig: { 92 + type: "bytes", 93 + description: "Signature of dag-cbor encoded label.", 94 + }, 95 + }, 96 + }, 97 + selfLabels: { 98 + type: "object", 99 + description: 100 + "Metadata tags on an atproto record, published by the author within the record.", 101 + required: ["values"], 102 + properties: { 103 + values: { 104 + type: "array", 105 + items: { 106 + type: "ref", 107 + ref: "lex:com.atproto.label.defs#selfLabel", 108 + }, 109 + maxLength: 10, 110 + }, 111 + }, 112 + }, 113 + selfLabel: { 114 + type: "object", 115 + description: 116 + "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", 117 + required: ["val"], 118 + properties: { 119 + val: { 120 + type: "string", 121 + maxLength: 128, 122 + description: 123 + "The short string name of the value or type of this label.", 124 + }, 125 + }, 126 + }, 127 + labelValueDefinition: { 128 + type: "object", 129 + description: 130 + "Declares a label value and its expected interpretations and behaviors.", 131 + required: ["identifier", "severity", "blurs", "locales"], 132 + properties: { 133 + identifier: { 134 + type: "string", 135 + description: 136 + "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 137 + maxLength: 100, 138 + maxGraphemes: 100, 139 + }, 140 + severity: { 141 + type: "string", 142 + description: 143 + "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 144 + knownValues: ["inform", "alert", "none"], 145 + }, 146 + blurs: { 147 + type: "string", 148 + description: 149 + "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 150 + knownValues: ["content", "media", "none"], 151 + }, 152 + defaultSetting: { 153 + type: "string", 154 + description: "The default setting for this label.", 155 + knownValues: ["ignore", "warn", "hide"], 156 + default: "warn", 157 + }, 158 + adultOnly: { 159 + type: "boolean", 160 + description: 161 + "Does the user need to have adult content enabled in order to configure this label?", 162 + }, 163 + locales: { 164 + type: "array", 165 + items: { 166 + type: "ref", 167 + ref: "lex:com.atproto.label.defs#labelValueDefinitionStrings", 168 + }, 169 + }, 170 + }, 171 + }, 172 + labelValueDefinitionStrings: { 173 + type: "object", 174 + description: 175 + "Strings which describe the label in the UI, localized into a specific language.", 176 + required: ["lang", "name", "description"], 177 + properties: { 178 + lang: { 179 + type: "string", 180 + description: 181 + "The code of the language these strings are written in.", 182 + format: "language", 183 + }, 184 + name: { 185 + type: "string", 186 + description: "A short human-readable name for the label.", 187 + maxGraphemes: 64, 188 + maxLength: 640, 189 + }, 190 + description: { 191 + type: "string", 192 + description: 193 + "A longer description of what the label means and why it might be applied.", 194 + maxGraphemes: 10000, 195 + maxLength: 100000, 196 + }, 197 + }, 198 + }, 199 + labelValue: { 200 + type: "string", 201 + knownValues: [ 202 + "!hide", 203 + "!no-promote", 204 + "!warn", 205 + "!no-unauthenticated", 206 + "dmca-violation", 207 + "doxxing", 208 + "porn", 209 + "sexual", 210 + "nudity", 211 + "nsfl", 212 + "gore", 213 + ], 214 + }, 215 + }, 216 + }, 217 + ComAtprotoRepoGetRecord: { 218 + lexicon: 1, 219 + id: "com.atproto.repo.getRecord", 220 + defs: { 221 + main: { 222 + type: "query", 223 + description: 224 + "Get a single record from a repository. Does not require auth.", 225 + parameters: { 226 + type: "params", 227 + required: ["repo", "collection", "rkey"], 228 + properties: { 229 + repo: { 230 + type: "string", 231 + format: "at-identifier", 232 + description: "The handle or DID of the repo.", 233 + }, 234 + collection: { 235 + type: "string", 236 + format: "nsid", 237 + description: "The NSID of the record collection.", 238 + }, 239 + rkey: { 240 + type: "string", 241 + description: "The Record Key.", 242 + }, 243 + cid: { 244 + type: "string", 245 + format: "cid", 246 + description: 247 + "The CID of the version of the record. If not specified, then return the most recent version.", 248 + }, 249 + }, 250 + }, 251 + output: { 252 + encoding: "application/json", 253 + schema: { 254 + type: "object", 255 + required: ["uri", "value"], 256 + properties: { 257 + uri: { 258 + type: "string", 259 + format: "at-uri", 260 + }, 261 + cid: { 262 + type: "string", 263 + format: "cid", 264 + }, 265 + value: { 266 + type: "unknown", 267 + }, 268 + }, 269 + }, 270 + }, 271 + errors: [ 272 + { 273 + name: "RecordNotFound", 274 + }, 275 + ], 276 + }, 277 + }, 278 + }, 279 + ComAtprotoRepoListRecords: { 280 + lexicon: 1, 281 + id: "com.atproto.repo.listRecords", 282 + defs: { 283 + main: { 284 + type: "query", 285 + description: 286 + "List a range of records in a repository, matching a specific collection. Does not require auth.", 287 + parameters: { 288 + type: "params", 289 + required: ["repo", "collection"], 290 + properties: { 291 + repo: { 292 + type: "string", 293 + format: "at-identifier", 294 + description: "The handle or DID of the repo.", 295 + }, 296 + collection: { 297 + type: "string", 298 + format: "nsid", 299 + description: "The NSID of the record type.", 300 + }, 301 + limit: { 302 + type: "integer", 303 + minimum: 1, 304 + maximum: 100, 305 + default: 50, 306 + description: "The number of records to return.", 307 + }, 308 + cursor: { 309 + type: "string", 310 + }, 311 + rkeyStart: { 312 + type: "string", 313 + description: 314 + "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)", 315 + }, 316 + rkeyEnd: { 317 + type: "string", 318 + description: 319 + "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)", 320 + }, 321 + reverse: { 322 + type: "boolean", 323 + description: "Flag to reverse the order of the returned records.", 324 + }, 325 + }, 326 + }, 327 + output: { 328 + encoding: "application/json", 329 + schema: { 330 + type: "object", 331 + required: ["records"], 332 + properties: { 333 + cursor: { 334 + type: "string", 335 + }, 336 + records: { 337 + type: "array", 338 + items: { 339 + type: "ref", 340 + ref: "lex:com.atproto.repo.listRecords#record", 341 + }, 342 + }, 343 + }, 344 + }, 345 + }, 346 + }, 347 + record: { 348 + type: "object", 349 + required: ["uri", "cid", "value"], 350 + properties: { 351 + uri: { 352 + type: "string", 353 + format: "at-uri", 354 + }, 355 + cid: { 356 + type: "string", 357 + format: "cid", 358 + }, 359 + value: { 360 + type: "unknown", 361 + }, 362 + }, 363 + }, 364 + }, 365 + }, 366 + LiPlonkPaste: { 367 + lexicon: 1, 368 + id: "li.plonk.paste", 369 + defs: { 370 + main: { 371 + type: "record", 372 + key: "tid", 373 + record: { 374 + type: "object", 375 + required: ["code", "shortUrl", "lang", "title", "createdAt"], 376 + properties: { 377 + code: { 378 + type: "string", 379 + minLength: 1, 380 + maxGraphemes: 65536, 381 + maxLength: 65536, 382 + }, 383 + shortUrl: { 384 + type: "string", 385 + minLength: 2, 386 + maxGraphemes: 10, 387 + maxLength: 10, 388 + }, 389 + lang: { 390 + type: "string", 391 + minLength: 1, 392 + maxGraphemes: 20, 393 + maxLength: 20, 394 + }, 395 + title: { 396 + type: "string", 397 + minLength: 1, 398 + maxGraphemes: 100, 399 + maxLength: 100, 400 + }, 401 + createdAt: { 402 + type: "string", 403 + format: "datetime", 404 + }, 405 + }, 406 + }, 407 + }, 408 + }, 409 + }, 410 + AppBskyActorProfile: { 411 + lexicon: 1, 412 + id: "app.bsky.actor.profile", 413 + defs: { 414 + main: { 415 + type: "record", 416 + description: "A declaration of a Bluesky account profile.", 417 + key: "literal:self", 418 + record: { 419 + type: "object", 420 + properties: { 421 + displayName: { 422 + type: "string", 423 + maxGraphemes: 64, 424 + maxLength: 640, 425 + }, 426 + description: { 427 + type: "string", 428 + description: "Free-form profile description text.", 429 + maxGraphemes: 256, 430 + maxLength: 2560, 431 + }, 432 + avatar: { 433 + type: "blob", 434 + description: 435 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 436 + accept: ["image/png", "image/jpeg"], 437 + maxSize: 1000000, 438 + }, 439 + banner: { 440 + type: "blob", 441 + description: 442 + "Larger horizontal image to display behind profile view.", 443 + accept: ["image/png", "image/jpeg"], 444 + maxSize: 1000000, 445 + }, 446 + labels: { 447 + type: "union", 448 + description: 449 + "Self-label values, specific to the Bluesky application, on the overall account.", 450 + refs: ["lex:com.atproto.label.defs#selfLabels"], 451 + }, 452 + joinedViaStarterPack: { 453 + type: "ref", 454 + ref: "lex:com.atproto.repo.strongRef", 455 + }, 456 + createdAt: { 457 + type: "string", 458 + format: "datetime", 459 + }, 460 + }, 461 + }, 462 + }, 463 + }, 464 + }, 465 + ComAtprotoRepoStrongRef: { 466 + lexicon: 1, 467 + id: "com.atproto.repo.strongRef", 468 + description: "A URI with a content-hash fingerprint.", 469 + defs: { 470 + main: { 471 + type: "object", 472 + required: ["uri", "cid"], 473 + properties: { 474 + uri: { 475 + type: "string", 476 + format: "at-uri", 477 + }, 478 + cid: { 479 + type: "string", 480 + format: "cid", 481 + }, 482 + }, 483 + }, 484 + }, 485 + }, 486 + }; 487 + export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]; 488 + export const lexicons: Lexicons = new Lexicons(schemas); 489 489 export const ids = { 490 - OvhPlonkComment: 'ovh.plonk.comment', 491 - ComAtprotoLabelDefs: 'com.atproto.label.defs', 492 - ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', 493 - ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', 494 - OvhPlonkPaste: 'ovh.plonk.paste', 495 - AppBskyActorProfile: 'app.bsky.actor.profile', 496 - ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', 497 - } 490 + LiPlonkComment: "li.plonk.comment", 491 + ComAtprotoLabelDefs: "com.atproto.label.defs", 492 + ComAtprotoRepoGetRecord: "com.atproto.repo.getRecord", 493 + ComAtprotoRepoListRecords: "com.atproto.repo.listRecords", 494 + LiPlonkPaste: "li.plonk.paste", 495 + AppBskyActorProfile: "app.bsky.actor.profile", 496 + ComAtprotoRepoStrongRef: "com.atproto.repo.strongRef", 497 + };
+26 -26
src/lexicons/types/app/bsky/actor/profile.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { lexicons } from '../../../../lexicons' 6 - import { isObj, hasProp } from '../../../../util' 7 - import { CID } from 'multiformats/cid' 8 - import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' 9 - import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' 4 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../../lexicons"; 6 + import { isObj, hasProp } from "../../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + import * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs"; 9 + import * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef"; 10 10 11 11 export interface Record { 12 - displayName?: string 13 - /** Free-form profile description text. */ 14 - description?: string 15 - /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 16 - avatar?: BlobRef 17 - /** Larger horizontal image to display behind profile view. */ 18 - banner?: BlobRef 19 - labels?: 20 - | ComAtprotoLabelDefs.SelfLabels 21 - | { $type: string; [k: string]: unknown } 22 - joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main 23 - createdAt?: string 24 - [k: string]: unknown 12 + displayName?: string; 13 + /** Free-form profile description text. */ 14 + description?: string; 15 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 16 + avatar?: BlobRef; 17 + /** Larger horizontal image to display behind profile view. */ 18 + banner?: BlobRef; 19 + labels?: 20 + | ComAtprotoLabelDefs.SelfLabels 21 + | { $type: string; [k: string]: unknown }; 22 + joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main; 23 + createdAt?: string; 24 + [k: string]: unknown; 25 25 } 26 26 27 27 export function isRecord(v: unknown): v is Record { 28 - return ( 29 - isObj(v) && 30 - hasProp(v, '$type') && 31 - (v.$type === 'app.bsky.actor.profile#main' || 32 - v.$type === 'app.bsky.actor.profile') 33 - ) 28 + return ( 29 + isObj(v) && 30 + hasProp(v, "$type") && 31 + (v.$type === "app.bsky.actor.profile#main" || 32 + v.$type === "app.bsky.actor.profile") 33 + ); 34 34 } 35 35 36 36 export function validateRecord(v: unknown): ValidationResult { 37 - return lexicons.validate('app.bsky.actor.profile#main', v) 37 + return lexicons.validate("app.bsky.actor.profile#main", v); 38 38 }
+94 -94
src/lexicons/types/com/atproto/label/defs.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { lexicons } from '../../../../lexicons' 6 - import { isObj, hasProp } from '../../../../util' 7 - import { CID } from 'multiformats/cid' 4 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../../lexicons"; 6 + import { isObj, hasProp } from "../../../../util"; 7 + import { CID } from "multiformats/cid"; 8 8 9 9 /** Metadata tag on an atproto resource (eg, repo or record). */ 10 10 export interface Label { 11 - /** The AT Protocol version of the label object. */ 12 - ver?: number 13 - /** DID of the actor who created this label. */ 14 - src: string 15 - /** AT URI of the record, repository (account), or other resource that this label applies to. */ 16 - uri: string 17 - /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ 18 - cid?: string 19 - /** The short string name of the value or type of this label. */ 20 - val: string 21 - /** If true, this is a negation label, overwriting a previous label. */ 22 - neg?: boolean 23 - /** Timestamp when this label was created. */ 24 - cts: string 25 - /** Timestamp at which this label expires (no longer applies). */ 26 - exp?: string 27 - /** Signature of dag-cbor encoded label. */ 28 - sig?: Uint8Array 29 - [k: string]: unknown 11 + /** The AT Protocol version of the label object. */ 12 + ver?: number; 13 + /** DID of the actor who created this label. */ 14 + src: string; 15 + /** AT URI of the record, repository (account), or other resource that this label applies to. */ 16 + uri: string; 17 + /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ 18 + cid?: string; 19 + /** The short string name of the value or type of this label. */ 20 + val: string; 21 + /** If true, this is a negation label, overwriting a previous label. */ 22 + neg?: boolean; 23 + /** Timestamp when this label was created. */ 24 + cts: string; 25 + /** Timestamp at which this label expires (no longer applies). */ 26 + exp?: string; 27 + /** Signature of dag-cbor encoded label. */ 28 + sig?: Uint8Array; 29 + [k: string]: unknown; 30 30 } 31 31 32 32 export function isLabel(v: unknown): v is Label { 33 - return ( 34 - isObj(v) && 35 - hasProp(v, '$type') && 36 - v.$type === 'com.atproto.label.defs#label' 37 - ) 33 + return ( 34 + isObj(v) && 35 + hasProp(v, "$type") && 36 + v.$type === "com.atproto.label.defs#label" 37 + ); 38 38 } 39 39 40 40 export function validateLabel(v: unknown): ValidationResult { 41 - return lexicons.validate('com.atproto.label.defs#label', v) 41 + return lexicons.validate("com.atproto.label.defs#label", v); 42 42 } 43 43 44 44 /** Metadata tags on an atproto record, published by the author within the record. */ 45 45 export interface SelfLabels { 46 - values: SelfLabel[] 47 - [k: string]: unknown 46 + values: SelfLabel[]; 47 + [k: string]: unknown; 48 48 } 49 49 50 50 export function isSelfLabels(v: unknown): v is SelfLabels { 51 - return ( 52 - isObj(v) && 53 - hasProp(v, '$type') && 54 - v.$type === 'com.atproto.label.defs#selfLabels' 55 - ) 51 + return ( 52 + isObj(v) && 53 + hasProp(v, "$type") && 54 + v.$type === "com.atproto.label.defs#selfLabels" 55 + ); 56 56 } 57 57 58 58 export function validateSelfLabels(v: unknown): ValidationResult { 59 - return lexicons.validate('com.atproto.label.defs#selfLabels', v) 59 + return lexicons.validate("com.atproto.label.defs#selfLabels", v); 60 60 } 61 61 62 62 /** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */ 63 63 export interface SelfLabel { 64 - /** The short string name of the value or type of this label. */ 65 - val: string 66 - [k: string]: unknown 64 + /** The short string name of the value or type of this label. */ 65 + val: string; 66 + [k: string]: unknown; 67 67 } 68 68 69 69 export function isSelfLabel(v: unknown): v is SelfLabel { 70 - return ( 71 - isObj(v) && 72 - hasProp(v, '$type') && 73 - v.$type === 'com.atproto.label.defs#selfLabel' 74 - ) 70 + return ( 71 + isObj(v) && 72 + hasProp(v, "$type") && 73 + v.$type === "com.atproto.label.defs#selfLabel" 74 + ); 75 75 } 76 76 77 77 export function validateSelfLabel(v: unknown): ValidationResult { 78 - return lexicons.validate('com.atproto.label.defs#selfLabel', v) 78 + return lexicons.validate("com.atproto.label.defs#selfLabel", v); 79 79 } 80 80 81 81 /** Declares a label value and its expected interpretations and behaviors. */ 82 82 export interface LabelValueDefinition { 83 - /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ 84 - identifier: string 85 - /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */ 86 - severity: 'inform' | 'alert' | 'none' | (string & {}) 87 - /** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */ 88 - blurs: 'content' | 'media' | 'none' | (string & {}) 89 - /** The default setting for this label. */ 90 - defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {}) 91 - /** Does the user need to have adult content enabled in order to configure this label? */ 92 - adultOnly?: boolean 93 - locales: LabelValueDefinitionStrings[] 94 - [k: string]: unknown 83 + /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ 84 + identifier: string; 85 + /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */ 86 + severity: "inform" | "alert" | "none" | (string & {}); 87 + /** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */ 88 + blurs: "content" | "media" | "none" | (string & {}); 89 + /** The default setting for this label. */ 90 + defaultSetting: "ignore" | "warn" | "hide" | (string & {}); 91 + /** Does the user need to have adult content enabled in order to configure this label? */ 92 + adultOnly?: boolean; 93 + locales: LabelValueDefinitionStrings[]; 94 + [k: string]: unknown; 95 95 } 96 96 97 97 export function isLabelValueDefinition(v: unknown): v is LabelValueDefinition { 98 - return ( 99 - isObj(v) && 100 - hasProp(v, '$type') && 101 - v.$type === 'com.atproto.label.defs#labelValueDefinition' 102 - ) 98 + return ( 99 + isObj(v) && 100 + hasProp(v, "$type") && 101 + v.$type === "com.atproto.label.defs#labelValueDefinition" 102 + ); 103 103 } 104 104 105 105 export function validateLabelValueDefinition(v: unknown): ValidationResult { 106 - return lexicons.validate('com.atproto.label.defs#labelValueDefinition', v) 106 + return lexicons.validate("com.atproto.label.defs#labelValueDefinition", v); 107 107 } 108 108 109 109 /** Strings which describe the label in the UI, localized into a specific language. */ 110 110 export interface LabelValueDefinitionStrings { 111 - /** The code of the language these strings are written in. */ 112 - lang: string 113 - /** A short human-readable name for the label. */ 114 - name: string 115 - /** A longer description of what the label means and why it might be applied. */ 116 - description: string 117 - [k: string]: unknown 111 + /** The code of the language these strings are written in. */ 112 + lang: string; 113 + /** A short human-readable name for the label. */ 114 + name: string; 115 + /** A longer description of what the label means and why it might be applied. */ 116 + description: string; 117 + [k: string]: unknown; 118 118 } 119 119 120 120 export function isLabelValueDefinitionStrings( 121 - v: unknown, 121 + v: unknown, 122 122 ): v is LabelValueDefinitionStrings { 123 - return ( 124 - isObj(v) && 125 - hasProp(v, '$type') && 126 - v.$type === 'com.atproto.label.defs#labelValueDefinitionStrings' 127 - ) 123 + return ( 124 + isObj(v) && 125 + hasProp(v, "$type") && 126 + v.$type === "com.atproto.label.defs#labelValueDefinitionStrings" 127 + ); 128 128 } 129 129 130 130 export function validateLabelValueDefinitionStrings( 131 - v: unknown, 131 + v: unknown, 132 132 ): ValidationResult { 133 - return lexicons.validate( 134 - 'com.atproto.label.defs#labelValueDefinitionStrings', 135 - v, 136 - ) 133 + return lexicons.validate( 134 + "com.atproto.label.defs#labelValueDefinitionStrings", 135 + v, 136 + ); 137 137 } 138 138 139 139 export type LabelValue = 140 - | '!hide' 141 - | '!no-promote' 142 - | '!warn' 143 - | '!no-unauthenticated' 144 - | 'dmca-violation' 145 - | 'doxxing' 146 - | 'porn' 147 - | 'sexual' 148 - | 'nudity' 149 - | 'nsfl' 150 - | 'gore' 151 - | (string & {}) 140 + | "!hide" 141 + | "!no-promote" 142 + | "!warn" 143 + | "!no-unauthenticated" 144 + | "dmca-violation" 145 + | "doxxing" 146 + | "porn" 147 + | "sexual" 148 + | "nudity" 149 + | "nsfl" 150 + | "gore" 151 + | (string & {});
+35 -35
src/lexicons/types/com/atproto/repo/getRecord.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import express from 'express' 5 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 6 - import { lexicons } from '../../../../lexicons' 7 - import { isObj, hasProp } from '../../../../util' 8 - import { CID } from 'multiformats/cid' 9 - import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' 4 + import express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 11 export interface QueryParams { 12 - /** The handle or DID of the repo. */ 13 - repo: string 14 - /** The NSID of the record collection. */ 15 - collection: string 16 - /** The Record Key. */ 17 - rkey: string 18 - /** The CID of the version of the record. If not specified, then return the most recent version. */ 19 - cid?: string 12 + /** The handle or DID of the repo. */ 13 + repo: string; 14 + /** The NSID of the record collection. */ 15 + collection: string; 16 + /** The Record Key. */ 17 + rkey: string; 18 + /** The CID of the version of the record. If not specified, then return the most recent version. */ 19 + cid?: string; 20 20 } 21 21 22 - export type InputSchema = undefined 22 + export type InputSchema = undefined; 23 23 24 24 export interface OutputSchema { 25 - uri: string 26 - cid?: string 27 - value: {} 28 - [k: string]: unknown 25 + uri: string; 26 + cid?: string; 27 + value: {}; 28 + [k: string]: unknown; 29 29 } 30 30 31 - export type HandlerInput = undefined 31 + export type HandlerInput = undefined; 32 32 33 33 export interface HandlerSuccess { 34 - encoding: 'application/json' 35 - body: OutputSchema 36 - headers?: { [key: string]: string } 34 + encoding: "application/json"; 35 + body: OutputSchema; 36 + headers?: { [key: string]: string }; 37 37 } 38 38 39 39 export interface HandlerError { 40 - status: number 41 - message?: string 42 - error?: 'RecordNotFound' 40 + status: number; 41 + message?: string; 42 + error?: "RecordNotFound"; 43 43 } 44 44 45 - export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough 45 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 46 46 export type HandlerReqCtx<HA extends HandlerAuth = never> = { 47 - auth: HA 48 - params: QueryParams 49 - input: HandlerInput 50 - req: express.Request 51 - res: express.Response 52 - } 47 + auth: HA; 48 + params: QueryParams; 49 + input: HandlerInput; 50 + req: express.Request; 51 + res: express.Response; 52 + }; 53 53 export type Handler<HA extends HandlerAuth = never> = ( 54 - ctx: HandlerReqCtx<HA>, 55 - ) => Promise<HandlerOutput> | HandlerOutput 54 + ctx: HandlerReqCtx<HA>, 55 + ) => Promise<HandlerOutput> | HandlerOutput;
+48 -48
src/lexicons/types/com/atproto/repo/listRecords.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import express from 'express' 5 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 6 - import { lexicons } from '../../../../lexicons' 7 - import { isObj, hasProp } from '../../../../util' 8 - import { CID } from 'multiformats/cid' 9 - import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' 4 + import express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 11 export interface QueryParams { 12 - /** The handle or DID of the repo. */ 13 - repo: string 14 - /** The NSID of the record type. */ 15 - collection: string 16 - /** The number of records to return. */ 17 - limit: number 18 - cursor?: string 19 - /** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */ 20 - rkeyStart?: string 21 - /** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */ 22 - rkeyEnd?: string 23 - /** Flag to reverse the order of the returned records. */ 24 - reverse?: boolean 12 + /** The handle or DID of the repo. */ 13 + repo: string; 14 + /** The NSID of the record type. */ 15 + collection: string; 16 + /** The number of records to return. */ 17 + limit: number; 18 + cursor?: string; 19 + /** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */ 20 + rkeyStart?: string; 21 + /** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */ 22 + rkeyEnd?: string; 23 + /** Flag to reverse the order of the returned records. */ 24 + reverse?: boolean; 25 25 } 26 26 27 - export type InputSchema = undefined 27 + export type InputSchema = undefined; 28 28 29 29 export interface OutputSchema { 30 - cursor?: string 31 - records: Record[] 32 - [k: string]: unknown 30 + cursor?: string; 31 + records: Record[]; 32 + [k: string]: unknown; 33 33 } 34 34 35 - export type HandlerInput = undefined 35 + export type HandlerInput = undefined; 36 36 37 37 export interface HandlerSuccess { 38 - encoding: 'application/json' 39 - body: OutputSchema 40 - headers?: { [key: string]: string } 38 + encoding: "application/json"; 39 + body: OutputSchema; 40 + headers?: { [key: string]: string }; 41 41 } 42 42 43 43 export interface HandlerError { 44 - status: number 45 - message?: string 44 + status: number; 45 + message?: string; 46 46 } 47 47 48 - export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough 48 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 49 49 export type HandlerReqCtx<HA extends HandlerAuth = never> = { 50 - auth: HA 51 - params: QueryParams 52 - input: HandlerInput 53 - req: express.Request 54 - res: express.Response 55 - } 50 + auth: HA; 51 + params: QueryParams; 52 + input: HandlerInput; 53 + req: express.Request; 54 + res: express.Response; 55 + }; 56 56 export type Handler<HA extends HandlerAuth = never> = ( 57 - ctx: HandlerReqCtx<HA>, 58 - ) => Promise<HandlerOutput> | HandlerOutput 57 + ctx: HandlerReqCtx<HA>, 58 + ) => Promise<HandlerOutput> | HandlerOutput; 59 59 60 60 export interface Record { 61 - uri: string 62 - cid: string 63 - value: {} 64 - [k: string]: unknown 61 + uri: string; 62 + cid: string; 63 + value: {}; 64 + [k: string]: unknown; 65 65 } 66 66 67 67 export function isRecord(v: unknown): v is Record { 68 - return ( 69 - isObj(v) && 70 - hasProp(v, '$type') && 71 - v.$type === 'com.atproto.repo.listRecords#record' 72 - ) 68 + return ( 69 + isObj(v) && 70 + hasProp(v, "$type") && 71 + v.$type === "com.atproto.repo.listRecords#record" 72 + ); 73 73 } 74 74 75 75 export function validateRecord(v: unknown): ValidationResult { 76 - return lexicons.validate('com.atproto.repo.listRecords#record', v) 76 + return lexicons.validate("com.atproto.repo.listRecords#record", v); 77 77 }
+14 -14
src/lexicons/types/com/atproto/repo/strongRef.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { lexicons } from '../../../../lexicons' 6 - import { isObj, hasProp } from '../../../../util' 7 - import { CID } from 'multiformats/cid' 4 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../../lexicons"; 6 + import { isObj, hasProp } from "../../../../util"; 7 + import { CID } from "multiformats/cid"; 8 8 9 9 export interface Main { 10 - uri: string 11 - cid: string 12 - [k: string]: unknown 10 + uri: string; 11 + cid: string; 12 + [k: string]: unknown; 13 13 } 14 14 15 15 export function isMain(v: unknown): v is Main { 16 - return ( 17 - isObj(v) && 18 - hasProp(v, '$type') && 19 - (v.$type === 'com.atproto.repo.strongRef#main' || 20 - v.$type === 'com.atproto.repo.strongRef') 21 - ) 16 + return ( 17 + isObj(v) && 18 + hasProp(v, "$type") && 19 + (v.$type === "com.atproto.repo.strongRef#main" || 20 + v.$type === "com.atproto.repo.strongRef") 21 + ); 22 22 } 23 23 24 24 export function validateMain(v: unknown): ValidationResult { 25 - return lexicons.validate('com.atproto.repo.strongRef#main', v) 25 + return lexicons.validate("com.atproto.repo.strongRef#main", v); 26 26 }
+29
src/lexicons/types/li/plonk/comment.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../lexicons"; 6 + import { isObj, hasProp } from "../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef"; 9 + 10 + export interface Record { 11 + /** comment body */ 12 + content: string; 13 + /** comment creation timestamp */ 14 + createdAt: string; 15 + post: ComAtprotoRepoStrongRef.Main; 16 + [k: string]: unknown; 17 + } 18 + 19 + export function isRecord(v: unknown): v is Record { 20 + return ( 21 + isObj(v) && 22 + hasProp(v, "$type") && 23 + (v.$type === "li.plonk.comment#main" || v.$type === "li.plonk.comment") 24 + ); 25 + } 26 + 27 + export function validateRecord(v: unknown): ValidationResult { 28 + return lexicons.validate("li.plonk.comment#main", v); 29 + }
+28
src/lexicons/types/li/plonk/paste.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../lexicons"; 6 + import { isObj, hasProp } from "../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + 9 + export interface Record { 10 + code: string; 11 + shortUrl: string; 12 + lang: string; 13 + title: string; 14 + createdAt: string; 15 + [k: string]: unknown; 16 + } 17 + 18 + export function isRecord(v: unknown): v is Record { 19 + return ( 20 + isObj(v) && 21 + hasProp(v, "$type") && 22 + (v.$type === "li.plonk.paste#main" || v.$type === "li.plonk.paste") 23 + ); 24 + } 25 + 26 + export function validateRecord(v: unknown): ValidationResult { 27 + return lexicons.validate("li.plonk.paste#main", v); 28 + }
-29
src/lexicons/types/ovh/plonk/comment.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { lexicons } from '../../../lexicons' 6 - import { isObj, hasProp } from '../../../util' 7 - import { CID } from 'multiformats/cid' 8 - import * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef' 9 - 10 - export interface Record { 11 - /** comment body */ 12 - content: string 13 - /** comment creation timestamp */ 14 - createdAt: string 15 - post: ComAtprotoRepoStrongRef.Main 16 - [k: string]: unknown 17 - } 18 - 19 - export function isRecord(v: unknown): v is Record { 20 - return ( 21 - isObj(v) && 22 - hasProp(v, '$type') && 23 - (v.$type === 'ovh.plonk.comment#main' || v.$type === 'ovh.plonk.comment') 24 - ) 25 - } 26 - 27 - export function validateRecord(v: unknown): ValidationResult { 28 - return lexicons.validate('ovh.plonk.comment#main', v) 29 - }
-28
src/lexicons/types/ovh/plonk/paste.ts
··· 1 - /** 2 - * GENERATED CODE - DO NOT MODIFY 3 - */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { lexicons } from '../../../lexicons' 6 - import { isObj, hasProp } from '../../../util' 7 - import { CID } from 'multiformats/cid' 8 - 9 - export interface Record { 10 - code: string 11 - shortUrl: string 12 - lang: string 13 - title: string 14 - createdAt: string 15 - [k: string]: unknown 16 - } 17 - 18 - export function isRecord(v: unknown): v is Record { 19 - return ( 20 - isObj(v) && 21 - hasProp(v, '$type') && 22 - (v.$type === 'ovh.plonk.paste#main' || v.$type === 'ovh.plonk.paste') 23 - ) 24 - } 25 - 26 - export function validateRecord(v: unknown): ValidationResult { 27 - return lexicons.validate('ovh.plonk.paste#main', v) 28 - }
+4 -4
src/lexicons/util.ts
··· 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 4 export function isObj(v: unknown): v is Record<string, unknown> { 5 - return typeof v === 'object' && v !== null 5 + return typeof v === "object" && v !== null; 6 6 } 7 7 8 8 export function hasProp<K extends PropertyKey>( 9 - data: object, 10 - prop: K, 9 + data: object, 10 + prop: K, 11 11 ): data is Record<K, unknown> { 12 - return prop in data 12 + return prop in data; 13 13 }
+6 -6
src/lib.ts
··· 4 4 dotenv.config(); 5 5 6 6 export const env = cleanEnv(process.env, { 7 - NODE_ENV: str({ 7 + PLONK_NODE_ENV: str({ 8 8 devDefault: testOnly("test"), 9 9 choices: ["development", "production", "test"], 10 10 }), 11 - HOST: host({ devDefault: testOnly("localhost") }), 12 - PORT: port({ devDefault: testOnly(3000) }), 13 - PUBLIC_URL: str({}), 14 - DB_PATH: str({ devDefault: ":memory:" }), 15 - COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000001" }), 11 + PLONK_HOST: host({ devDefault: testOnly("localhost") }), 12 + PLONK_PORT: port({ devDefault: testOnly(3000) }), 13 + PLONK_PUBLIC_URL: str({}), 14 + PLONK_DB_PATH: str({ devDefault: ":memory:" }), 15 + PLONK_COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000000" }), 16 16 });
+9
src/mixins/footer.pug
··· 1 + mixin footer() 2 + hr 3 + div.footer 4 + div.left-side 5 + div.right-side 6 + p 7 + | made by 8 + a(href="https://bsky.app/profile/oppi.li") @oppi.li 9 +
+1 -1
src/mixins/head.pug
··· 2 2 head 3 3 meta(name="viewport" content="width=device-width, initial-scale=1.0") 4 4 meta(charset='UTF-8') 5 - title #{title} 5 + title #{title} · plonk.li 6 6 link(rel="stylesheet", href="/styles.css") 7 7 link(rel="preconnect" href="https://rsms.me/") 8 8 link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
+15
src/mixins/header.pug
··· 1 + mixin header(ownDid, didHandleMap) 2 + div.header 3 + div.left-side 4 + div.right-side 5 + p 6 + a(href="/") home 7 + | &nbsp;·&nbsp; 8 + if ownDid 9 + a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks 10 + | &nbsp;·&nbsp; 11 + a(href="/logout") logout 12 + else 13 + a(href="/login") login 14 + |&nbsp;to get plonkin' 15 +
+3 -2
src/mixins/post.pug
··· 1 1 mixin post(paste, handle, did) 2 2 div.post 3 3 p 4 - a(href=`/p/${paste.shortUrl}`) 4 + a(href=`/p/${paste.shortUrl}`).post-link 5 5 | #{paste.title} 6 6 p.post-info 7 7 | by ··· 13 13 | #{paste.lang} 14 14 | · 15 15 | #{paste.code.split('\n').length} loc 16 - 16 + | ·&nbsp; 17 + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
+4
src/mixins/utils.pug
··· 2 2 function randInt(min, max) { 3 3 return Math.floor(Math.random() * (max - min + 1)) + min; 4 4 } 5 + - 6 + function pluralize(count, noun) { 7 + return count==1?noun:`${noun}s`; 8 + } 5 9 - 6 10 function timeDifference(current, previous) { 7 11 if (!current || !previous) {
+89 -5
src/public/styles.css
··· 1 - @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap'); 1 + @font-face { 2 + font-family: 'NerdIosevka'; 3 + src: url('../assets/NerdIosevka-Regular.woff2') format('woff2'); 4 + font-weight: normal; 5 + font-style: monospace; 6 + } 2 7 3 8 :root { 4 9 /* Light mode colors */ ··· 28 33 } 29 34 30 35 * { 31 - font-family: 'IBM Plex Mono', monospace; 36 + font-family: 'NerdIosevka', monospace; 37 + font-size: 0.9rem; 32 38 } 33 39 34 40 body { ··· 52 58 } 53 59 54 60 pre { 55 - background-color: var(--bg-color-muted); 56 61 padding: 1rem; 57 62 overflow-x: auto; 58 63 } 59 64 65 + .comment-body { 66 + background-color: var(--bg-color); 67 + padding: 0; 68 + margin-top: 0.1rem; 69 + } 70 + 71 + .comment-info { 72 + margin-bottom: 0; 73 + } 74 + 60 75 input, textarea, select, button { 61 76 border: none; 62 77 padding: 1rem; ··· 93 108 hr { 94 109 border: none; 95 110 border-top: 1px solid var(--bg-color-muted); 96 - padding: 1rem; 97 111 } 98 112 99 113 .post-form { ··· 127 141 align-self: flex-end; 128 142 } 129 143 144 + .post-link { 145 + color: var(--text-color); 146 + text-decoration: none; 147 + } 148 + .post-link:hover { 149 + text-decoration: underline; 150 + } 151 + .post-link:visited { 152 + color: var(--text-color-muted); 153 + } 154 + 155 + .post-info { 156 + margin-top: 0; 157 + } 158 + 159 + .post-info, .comment-info { 160 + color: var(--text-color-muted); 161 + } 162 + .post-info a, .comment-info a { 163 + color: var(--text-color-muted); 164 + text-decoration: none; 165 + } 166 + .post-info a:visited, .comment-info a:visited { 167 + color: var(--text-color-muted); 168 + } 169 + .post-info a:hover, .comment-info a:hover { 170 + text-decoration: underline; 171 + } 172 + 130 173 .timeline, .comments { 131 174 display: flex; 132 175 flex-direction: column; 133 176 gap: 1rem; 177 + padding-bottom: 1rem; 134 178 } 135 179 136 180 .login-input-title { ··· 141 185 flex: 1 142 186 } 143 187 144 - .header { 188 + .header, .footer { 145 189 display: flex; 146 190 flex-direction: row; 147 191 justify-content: space-between; 148 192 align-items: center; 149 193 } 194 + 195 + select { 196 + -webkit-appearance: none; 197 + -moz-appearance: none; 198 + text-indent: 1px; 199 + text-overflow: ''; 200 + } 201 + 202 + .code-line { 203 + display: flex; 204 + } 205 + 206 + .code-line-num { 207 + white-space: pre; 208 + -webkit-user-select: none; 209 + user-select: none; 210 + margin-right: 0.4em; 211 + padding: 0 0.4em 0 0.4em; 212 + color: var(--text-color-muted); 213 + text-align: right; 214 + } 215 + 216 + .code-line-content { 217 + color: var(--text-color); 218 + } 219 + 220 + .header, .footer { 221 + color: var(--text-color); 222 + } 223 + 224 + .header a, .header a:visited, 225 + .footer a, .footer a:visited { 226 + color: var(--link-color); 227 + text-decoration: none; 228 + } 229 + 230 + .header a:hover, 231 + .footer a:hover { 232 + text-decoration: underline; 233 + }
+148 -101
src/routes.ts
··· 6 6 import { isValidHandle, AtUri } from "@atproto/syntax"; 7 7 import { IncomingMessage, ServerResponse } from "node:http"; 8 8 import { Agent } from "@atproto/api"; 9 - import { getPds, DidResolver } from "@atproto/identity"; 10 9 import { TID } from "@atproto/common"; 11 - import { Agent } from "@atproto/api"; 12 10 import { newShortUrl } from "#/db"; 13 11 14 - import * as Paste from "#/lexicons/types/ovh/plonk/paste"; 15 - import * as Comment from "#/lexicons/types/ovh/plonk/comment"; 16 - import { ComAtprotoRepoNS } from "#/lexicons"; 12 + import * as Paste from "#/lexicons/types/li/plonk/paste"; 13 + import * as Comment from "#/lexicons/types/li/plonk/comment"; 17 14 18 15 type Session = { 19 16 did: string; 20 17 }; 21 18 22 - async function getSessionAgent( 19 + async function getSession( 23 20 req: IncomingMessage, 24 21 res: ServerResponse<IncomingMessage>, 25 - ctx: Ctx, 26 22 ) { 27 - const session = await getIronSession<Session>(req, res, { 23 + return await getIronSession<Session>(req, res, { 28 24 cookieName: "plonk-id", 29 - password: env.COOKIE_SECRET, 25 + password: env.PLONK_COOKIE_SECRET, 26 + cookieOptions: { 27 + secure: env.PLONK_NODE_ENV === "production", 28 + }, 30 29 }); 30 + } 31 + 32 + async function getSessionAgent( 33 + req: IncomingMessage, 34 + res: ServerResponse<IncomingMessage>, 35 + ctx: Ctx, 36 + ) { 37 + const session = await getSession(req, res); 31 38 if (!session.did) return null; 32 39 try { 33 40 const oauthSession = await ctx.oauthClient.restore(session.did); ··· 42 49 export const createRouter = (ctx: Ctx) => { 43 50 const router = express.Router(); 44 51 45 - // Static assets 46 - router.use( 47 - "/public", 48 - express.static(path.join(__dirname, "pages", "public")), 49 - ); 50 - 52 + router.use("/assets", express.static(path.join(__dirname, "assets"))); 51 53 // OAuth metadata 52 54 router.get("/client-metadata.json", async (_req, res) => { 53 55 return res.json(ctx.oauthClient.clientMetadata); ··· 57 59 const params = new URLSearchParams(req.originalUrl.split("?")[1]); 58 60 try { 59 61 const { session } = await ctx.oauthClient.callback(params); 60 - const clientSession = await getIronSession<Session>(req, res, { 61 - cookieName: "plonk-id", 62 - password: env.COOKIE_SECRET, 63 - }); 64 - ctx.logger.info(clientSession.did, "client session did"); 62 + const clientSession = await getSession(req, res); 65 63 //assert(!clientSession.did, "session already exists"); 66 64 clientSession.did = session.did; 67 65 await clientSession.save(); ··· 98 96 }); 99 97 100 98 router.get("/logout", async (req, res) => { 101 - const session = await getIronSession<Session>(req, res, { 102 - cookieName: "plonk-id", 103 - password: env.COOKIE_SECRET, 104 - }); 99 + const session = await getSession(req, res); 105 100 session.destroy(); 106 101 return res.redirect("/"); 107 102 }); ··· 110 105 const agent = await getSessionAgent(req, res, ctx); 111 106 const pastes = await ctx.db 112 107 .selectFrom("paste") 113 - .selectAll() 114 - .orderBy("indexedAt", "desc") 108 + .leftJoin("comment", "comment.pasteUri", "paste.uri") 109 + .select([ 110 + "paste.uri", 111 + "paste.shortUrl", 112 + "paste.authorDid", 113 + "paste.code", 114 + "paste.lang", 115 + "paste.title", 116 + "paste.createdAt", 117 + "paste.indexedAt as pasteIndexedAt", 118 + ctx.db.fn.count("comment.uri").as("commentCount") 119 + ]) 120 + .groupBy("paste.uri") 121 + .orderBy("pasteIndexedAt", "desc") 115 122 .limit(25) 116 123 .execute(); 117 124 118 125 // Map user DIDs to their domain-name handles 119 126 const didHandleMap = await ctx.resolver.resolveDidsToHandles( 120 - pastes.map((s) => s.authorDid).concat(agent? [agent.assertDid]:[]), 127 + pastes.map((s) => s.authorDid).concat(agent ? [agent.assertDid] : []), 121 128 ); 122 129 123 130 if (!agent) { ··· 133 140 134 141 router.get("/u/:authorDid", async (req, res) => { 135 142 const { authorDid } = req.params; 136 - const resolver = new DidResolver({}); 137 - const didDocument = await resolver.resolve(authorDid); 138 - if (!didDocument) { 139 - return res.status(404); 140 - } 141 - const pds = getPds(didDocument); 142 - if (!pds) { 143 - return res.status(404); 144 - } 145 - const agent = new Agent(pds); 146 - const response = await agent.com.atproto.repo.listRecords({ 147 - repo: authorDid, 148 - collection: 'ovh.plonk.paste', 149 - limit: 99, 150 - }); 151 - const pastes = response.data.records; 152 - let didHandleMap = {}; 143 + const pastes = await ctx.db 144 + .selectFrom("paste") 145 + .leftJoin("comment", "comment.pasteUri", "paste.uri") 146 + .select([ 147 + "paste.uri", 148 + "paste.shortUrl", 149 + "paste.authorDid as pasteAuthorDid", 150 + "paste.code", 151 + "paste.lang", 152 + "paste.title", 153 + "paste.createdAt as pasteCreatedAt", 154 + "paste.indexedAt as pasteIndexedAt", 155 + ctx.db.fn.count("comment.uri").as("commentCount") 156 + ]) 157 + .groupBy("paste.uri") 158 + .where("pasteAuthorDid", "=", authorDid) 159 + .orderBy("pasteCreatedAt", "desc") 160 + .execute(); 161 + let didHandleMap: Record<string, string> = {}; 153 162 didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid); 154 - return res.render("user", { pastes, authorDid, didHandleMap }); 163 + const ownAgent = await getSessionAgent(req, res, ctx); 164 + if (!ownAgent) { 165 + return res.render("user", { pastes, authorDid, didHandleMap }); 166 + } else { 167 + const ownDid = ownAgent.assertDid; 168 + didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid); 169 + return res.render("user", { pastes, authorDid, ownDid, didHandleMap }); 170 + } 155 171 }); 156 172 157 173 router.get("/p/:shortUrl", async (req, res) => { 158 174 const { shortUrl } = req.params; 159 175 const ret = await ctx.db 160 176 .selectFrom("paste") 177 + .leftJoin("comment", "comment.pasteUri", "paste.uri") 178 + .select([ 179 + "paste.uri as pasteUri", 180 + "comment.pasteCid as pasteCid", 181 + "paste.authorDid as pasteAuthorDid", 182 + "paste.code as pasteCode", 183 + "paste.lang as pasteLang", 184 + "paste.title as pasteTitle", 185 + "paste.createdAt as pasteCreatedAt", 186 + "comment.uri as commentUri", 187 + "comment.authorDid as commentAuthorDid", 188 + "comment.body as commentBody", 189 + "comment.createdAt as commentCreatedAt", 190 + ]) 161 191 .where("shortUrl", "=", shortUrl) 162 - .select(["authorDid", "uri"]) 163 - .executeTakeFirst(); 164 - if (!ret) { 165 - return res.status(404); 166 - } 167 - var comments = await ctx.db 168 - .selectFrom("comment") 169 - .selectAll() 170 - .where("pasteUri", '=', ret.uri) 171 192 .execute(); 172 - const { authorDid: did, uri } = ret; 173 - const didHandleMap = await ctx.resolver.resolveDidsToHandles( 174 - comments.map((c) => c.authorDid).concat([did]), 175 - ) 176 - const resolver = new DidResolver({}); 177 - const didDocument = await resolver.resolve(did); 178 - if (!didDocument) { 193 + if (ret.length === 0) { 179 194 return res.status(404); 180 195 } 181 - const pds = getPds(didDocument); 182 - if (!pds) { 183 - return res.status(404); 184 - } 185 - const agent = new Agent(pds); 186 - const aturi = new AtUri(uri); 187 - const response = await agent.com.atproto.repo.getRecord({ 188 - repo: aturi.hostname, 189 - collection: aturi.collection, 190 - rkey: aturi.rkey 191 - }); 196 + const { 197 + pasteAuthorDid, 198 + pasteUri, 199 + pasteCode, 200 + pasteLang, 201 + pasteTitle, 202 + pasteCreatedAt, 203 + } = ret[0]; 204 + let didHandleMap = await ctx.resolver.resolveDidsToHandles( 205 + [ret[0].pasteAuthorDid].concat( 206 + ret.flatMap((row) => 207 + row.commentAuthorDid ? [row.commentAuthorDid] : [], 208 + ), 209 + ), 210 + ); 192 211 193 - const paste = 194 - Paste.isRecord(response.data.value) && 195 - Paste.validateRecord(response.data.value).success 196 - ? response.data.value 197 - : {}; 212 + const paste = { 213 + uri: pasteUri, 214 + code: pasteCode, 215 + title: pasteTitle, 216 + lang: pasteLang, 217 + shortUrl, 218 + createdAt: pasteCreatedAt, 219 + authorDid: pasteAuthorDid, 220 + }; 198 221 199 - return res.render("paste", { paste, authorDid: did, uri: response.data.uri, didHandleMap, shortUrl, comments }); 222 + const comments = ret 223 + .filter((row) => row.commentUri) 224 + .map((row) => { 225 + return { 226 + uri: row.commentUri, 227 + authorDid: row.commentAuthorDid, 228 + body: row.commentBody, 229 + createdAt: row.commentCreatedAt, 230 + }; 231 + }); 232 + 233 + const ownAgent = await getSessionAgent(req, res, ctx); 234 + if (!ownAgent) { 235 + return res.render("paste", { 236 + paste, 237 + didHandleMap, 238 + comments, 239 + }); 240 + } else { 241 + const ownDid = ownAgent.assertDid; 242 + didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid); 243 + return res.render("paste", { 244 + paste, 245 + ownDid, 246 + didHandleMap, 247 + comments, 248 + }); 249 + } 200 250 }); 201 251 202 252 router.get("/p/:shortUrl/raw", async (req, res) => { 203 - res.redirect(`/r/${req.params.shortUrl}`) 253 + res.redirect(`/r/${req.params.shortUrl}`); 204 254 }); 205 255 router.get("/r/:shortUrl", async (req, res) => { 206 256 const { shortUrl } = req.params; ··· 220 270 router.get("/reset", async (req, res) => { 221 271 const agent = await getSessionAgent(req, res, ctx); 222 272 if (!agent) { 223 - return res.redirect('/'); 273 + return res.redirect("/"); 224 274 } 225 275 const response = await agent.com.atproto.repo.listRecords({ 226 276 repo: agent.assertDid, 227 - collection: 'ovh.plonk.paste', 277 + collection: "li.plonk.paste", 228 278 limit: 10, 229 279 }); 230 280 const vals = response.data.records; ··· 236 286 rkey: aturl.rkey, 237 287 }); 238 288 } 239 - return res.redirect('/'); 289 + return res.redirect("/"); 240 290 }); 241 291 242 292 router.post("/paste", async (req, res) => { ··· 251 301 const rkey = TID.nextStr(); 252 302 const shortUrl = await newShortUrl(ctx.db); 253 303 const record = { 254 - $type: "ovh.plonk.paste", 304 + $type: "li.plonk.paste", 255 305 code: req.body?.code, 256 306 lang: req.body?.lang, 257 307 shortUrl, ··· 270 320 try { 271 321 const res = await agent.com.atproto.repo.putRecord({ 272 322 repo: agent.assertDid, 273 - collection: "ovh.plonk.paste", 323 + collection: "li.plonk.paste", 274 324 rkey, 275 325 record, 276 326 validate: false, ··· 299 349 indexedAt: new Date().toISOString(), 300 350 }) 301 351 .execute(); 302 - ctx.logger.info(res, "wrote back to db"); 303 352 return res.redirect(`/p/${shortUrl}`); 304 353 } catch (err) { 305 354 ctx.logger.warn( ··· 320 369 .type("html") 321 370 .send("<h1>Error: Session required</h1>"); 322 371 } 323 - 372 + 324 373 const pasteUri = req.params.paste; 325 374 const aturi = new AtUri(pasteUri); 326 375 const pasteResponse = await agent.com.atproto.repo.getRecord({ 327 376 repo: aturi.hostname, 328 377 collection: aturi.collection, 329 - rkey: aturi.rkey 378 + rkey: aturi.rkey, 330 379 }); 331 380 const pasteCid = pasteResponse.data.cid; 332 381 if (!pasteCid) { 333 - return res 334 - .status(401) 335 - .type("html") 336 - .send("invalid paste"); 382 + return res.status(401).type("html").send("invalid paste"); 337 383 } 338 384 339 385 const rkey = TID.nextStr(); 340 386 const record = { 341 - $type: "ovh.plonk.comment", 387 + $type: "li.plonk.comment", 342 388 content: req.body?.comment, 343 389 post: { 344 390 uri: pasteUri, 345 - cid: pasteCid 391 + cid: pasteCid, 346 392 }, 347 393 createdAt: new Date().toISOString(), 348 394 }; ··· 358 404 try { 359 405 const res = await agent.com.atproto.repo.putRecord({ 360 406 repo: agent.assertDid, 361 - collection: "ovh.plonk.comment", 407 + collection: "li.plonk.comment", 362 408 rkey, 363 409 record, 364 410 validate: false, ··· 385 431 indexedAt: new Date().toISOString(), 386 432 }) 387 433 .execute(); 388 - ctx.logger.info(res, "wrote back to db"); 389 - const originalPaste = await ctx.db.selectFrom('paste').selectAll().where('uri', '=', pasteUri).executeTakeFirst(); 390 - return res.redirect(`/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`); 434 + const originalPaste = await ctx.db 435 + .selectFrom("paste") 436 + .selectAll() 437 + .where("uri", "=", pasteUri) 438 + .executeTakeFirst(); 439 + return res.redirect( 440 + `/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`, 441 + ); 391 442 } catch (err) { 392 443 ctx.logger.warn( 393 444 { err }, ··· 400 451 401 452 return router; 402 453 }; 403 - 404 - // https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5 405 - // at://did:plc:3ft67n4xnawzq4qi7mcksxj5/ovh.plonk.paste/3lcs3lnslbk2d 406 - // https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5&collection=ovh.plonk.paste&rkey=3lcqt7newvc2c
+6 -14
src/views/index.pug
··· 1 1 include ../mixins/mkPost 2 2 include ../mixins/head 3 + include ../mixins/header 4 + include ../mixins/footer 3 5 include ../mixins/utils 4 6 include ../mixins/post 5 7 ··· 18 20 "c", 19 21 "c#", 20 22 "c++", 23 + "cobol", 21 24 ].toSorted()) 22 25 doctype html 23 26 html 24 27 +head("timeline") 25 28 body 26 29 main#content 27 - div.header 28 - div.left-side 29 - if ownDid 30 - | logged in as @#{didHandleMap[ownDid]} 31 - div.right-side 32 - if ownDid 33 - p 34 - a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks 35 - | &nbsp;·&nbsp; 36 - a(href="/logout") logout 37 - else 38 - p 39 - a(href="/login") login 40 - |&nbsp;to get plonkin' 30 + +header(ownDid, didHandleMap) 41 31 42 32 if ownDid 43 33 +mkPost() ··· 46 36 each paste in pastes 47 37 - var handle = didHandleMap[paste.authorDid] 48 38 +post(paste, handle, paste.authorDid) 39 + 40 + +footer()
+5 -8
src/views/login.pug
··· 1 + include ../mixins/head 2 + include ../mixins/footer 3 + 1 4 doctype html 2 5 html 3 - head 4 - meta(name="viewport" content="width=device-width, initial-scale=1.0") 5 - meta(charset='UTF-8') 6 - title login 7 - link(rel="stylesheet", href="/styles.css") 8 - link(rel="preconnect" href="https://rsms.me/") 9 - link(rel="stylesheet" href="https://rsms.me/inter/inter.css") 10 - script(src="https://cdn.dashjs.org/latest/dash.all.min.js") 6 + +head("login") 11 7 body 12 8 main#content 13 9 h1 login ··· 15 11 div.login-row 16 12 input(type="text" name="handle" placeholder="enter handle" required).login-input-title 17 13 button(type="submit").login-submit-button login 14 + +footer()
+40 -19
src/views/paste.pug
··· 1 1 - var now = new Date() 2 2 include ../mixins/head 3 + include ../mixins/header 4 + include ../mixins/footer 3 5 include ../mixins/utils 4 6 doctype html 5 7 html 6 - +head(paste.title) 8 + +head(`${paste.title} · ${didHandleMap[paste.authorDid]}`) 7 9 body 8 10 main#content 11 + +header(ownDid, didHandleMap) 9 12 h1 #{paste.title} 10 - p 11 - | by @#{didHandleMap[authorDid]} · 13 + p.post-info 14 + | @#{didHandleMap[paste.authorDid]} · 12 15 | #{timeDifference(now, Date.parse(paste.createdAt))} ago · 13 16 | #{paste.lang} · 14 17 | #{paste.code.split('\n').length} loc · 15 - a(href=`/r/${shortUrl}`) raw 18 + a(href=`/r/${paste.shortUrl}`) raw 19 + | &nbsp;· 20 + | #{comments.length} #{pluralize(comments.length, 'comment')} 16 21 pre 17 - | #{paste.code} 22 + code 23 + - var lines = paste.code.split(/\r?\n|\r|\n/g) 24 + - var tot_chars = lines.length.toString().length 25 + each line, idx in lines 26 + span.code-line 27 + span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`) 28 + | #{idx + 1} 29 + span.code-line-content #{line} 18 30 hr 19 31 20 - div.comments 21 - each comment in comments 22 - div.comment(id=`${encodeURIComponent(comment.uri)}`) 23 - p 24 - | by @#{didHandleMap[comment.authorDid]} · 25 - | #{timeDifference(now, Date.parse(paste.createdAt))} ago 26 - p 27 - | #{comment.body} 28 - hr 32 + if comments.length != 0 33 + h1(id="comments") comments 34 + div.comments 35 + each comment in comments 36 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 37 + p.comment-info 38 + a(href=`/u/${comment.authorDid}`) 39 + | @#{didHandleMap[comment.authorDid]} 40 + | &nbsp;· 41 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 42 + pre.comment-body #{comment.body} 43 + 44 + if ownDid 45 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 46 + div.post-row 47 + textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 29 48 30 - form(action=`/${encodeURIComponent(uri)}/comment` method="post").post-form 31 - div.post-row 32 - textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 49 + div.post-submit-row 50 + button(type="submit").post-input-submit zonk! 51 + else 52 + p 53 + a(href="/login") login 54 + |&nbsp;to post a comment 33 55 34 - div.post-submit-row 35 - button(type="submit").post-input-submit zonk! 56 + +footer()
+6 -1
src/views/user.pug
··· 1 1 - var now = new Date() 2 2 - var handle = didHandleMap[authorDid] 3 3 include ../mixins/head 4 + include ../mixins/header 5 + include ../mixins/footer 4 6 include ../mixins/utils 5 7 include ../mixins/post 6 8 doctype html ··· 8 10 +head(handle) 9 11 body 10 12 main#content 13 + +header(ownDid, didHandleMap) 11 14 h1 plonks by @#{handle} 12 15 div.timeline 13 16 each paste in pastes 14 - +post(paste.value, handle, authorDid) 17 + +post(paste, handle, authorDid) 18 + 19 + +footer()
-12
tsup.config.ts
··· 1 - import { defineConfig } from 'tsup'; 2 - 3 - export default defineConfig({ 4 - entry: ['src/index.ts'], 5 - outDir: 'dist', 6 - clean: true, 7 - format: 'esm', 8 - target: 'node18', 9 - dts: true, 10 - minify: true, 11 - sourcemap: true, 12 - });
+463
x
··· 1 + From 489bb110616aa4da596aed7ae0a048c919ed333e Mon Sep 17 00:00:00 2001 2 + From: Akshay <nerdy@peppe.rs> 3 + Date: Thu, 26 Dec 2024 19:19:37 +0000 4 + Subject: [PATCH 1/3] add footer, comments, linenrs etc 5 + 6 + --- 7 + src/mixins/footer.pug | 9 +++++++++ 8 + src/mixins/post.pug | 3 ++- 9 + src/mixins/utils.pug | 4 ++++ 10 + src/public/styles.css | 21 +++++++++++++++++++-- 11 + src/routes.ts | 33 +++++++++++++++++++++++++++++---- 12 + src/views/index.pug | 3 +++ 13 + src/views/paste.pug | 28 +++++++++++++++++++++------- 14 + 7 files changed, 87 insertions(+), 14 deletions(-) 15 + create mode 100644 src/mixins/footer.pug 16 + 17 + diff --git a/src/mixins/footer.pug b/src/mixins/footer.pug 18 + new file mode 100644 19 + index 0000000..be71086 20 + --- /dev/null 21 + +++ b/src/mixins/footer.pug 22 + @@ -0,0 +1,9 @@ 23 + +mixin footer() 24 + + hr 25 + + div.footer 26 + + div.left-side 27 + + div.right-side 28 + + p 29 + + | made by 30 + + a(href="https://bsky.app/profile/oppi.li") @oppi.li 31 + + 32 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 33 + index 77d78aa..e98bcf8 100644 34 + --- a/src/mixins/post.pug 35 + +++ b/src/mixins/post.pug 36 + @@ -13,4 +13,5 @@ mixin post(paste, handle, did) 37 + | #{paste.lang} 38 + | · 39 + | #{paste.code.split('\n').length} loc 40 + - 41 + + | ·&nbsp; 42 + + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')} 43 + diff --git a/src/mixins/utils.pug b/src/mixins/utils.pug 44 + index 857bddd..08507d3 100644 45 + --- a/src/mixins/utils.pug 46 + +++ b/src/mixins/utils.pug 47 + @@ -2,6 +2,10 @@ 48 + function randInt(min, max) { 49 + return Math.floor(Math.random() * (max - min + 1)) + min; 50 + } 51 + +- 52 + + function pluralize(count, noun) { 53 + + return count==1?noun:`${noun}s`; 54 + + } 55 + - 56 + function timeDifference(current, previous) { 57 + if (!current || !previous) { 58 + diff --git a/src/public/styles.css b/src/public/styles.css 59 + index f88b533..6f80f5f 100644 60 + --- a/src/public/styles.css 61 + +++ b/src/public/styles.css 62 + @@ -104,7 +104,6 @@ textarea { 63 + hr { 64 + border: none; 65 + border-top: 1px solid var(--bg-color-muted); 66 + - padding: 1rem; 67 + } 68 + 69 + .post-form { 70 + @@ -152,7 +151,7 @@ hr { 71 + flex: 1 72 + } 73 + 74 + -.header { 75 + +.header, .footer { 76 + display: flex; 77 + flex-direction: row; 78 + justify-content: space-between; 79 + @@ -165,3 +164,21 @@ select { 80 + text-indent: 1px; 81 + text-overflow: ''; 82 + } 83 + + 84 + +.code-line { 85 + + display: flex; 86 + +} 87 + + 88 + +.code-line-num { 89 + + white-space: pre; 90 + + -webkit-user-select: none; 91 + + user-select: none; 92 + + margin-right: 0.4em; 93 + + padding: 0 0.4em 0 0.4em; 94 + + color: var(--text-color-muted); 95 + + text-align: right; 96 + +} 97 + + 98 + +.code-line-content { 99 + + color: var(--text-color); 100 + +} 101 + diff --git a/src/routes.ts b/src/routes.ts 102 + index 70f931d..17fa00e 100644 103 + --- a/src/routes.ts 104 + +++ b/src/routes.ts 105 + @@ -105,8 +105,20 @@ export const createRouter = (ctx: Ctx) => { 106 + const agent = await getSessionAgent(req, res, ctx); 107 + const pastes = await ctx.db 108 + .selectFrom("paste") 109 + - .selectAll() 110 + - .orderBy("indexedAt", "desc") 111 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 112 + + .select([ 113 + + "paste.uri", 114 + + "paste.shortUrl", 115 + + "paste.authorDid", 116 + + "paste.code", 117 + + "paste.lang", 118 + + "paste.title", 119 + + "paste.createdAt", 120 + + "paste.indexedAt as pasteIndexedAt", 121 + + ctx.db.fn.count("comment.uri").as("commentCount") 122 + + ]) 123 + + .groupBy("paste.uri") 124 + + .orderBy("pasteIndexedAt", "desc") 125 + .limit(25) 126 + .execute(); 127 + 128 + @@ -130,8 +142,21 @@ export const createRouter = (ctx: Ctx) => { 129 + const { authorDid } = req.params; 130 + const pastes = await ctx.db 131 + .selectFrom("paste") 132 + - .selectAll() 133 + - .where("authorDid", "=", authorDid) 134 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 135 + + .select([ 136 + + "paste.uri", 137 + + "paste.shortUrl", 138 + + "paste.authorDid as pasteAuthorDid", 139 + + "paste.code", 140 + + "paste.lang", 141 + + "paste.title", 142 + + "paste.createdAt as pasteCreatedAt", 143 + + "paste.indexedAt as pasteIndexedAt", 144 + + ctx.db.fn.count("comment.uri").as("commentCount") 145 + + ]) 146 + + .groupBy("paste.uri") 147 + + .where("pasteAuthorDid", "=", authorDid) 148 + + .orderBy("pasteCreatedAt", "desc") 149 + .execute(); 150 + let didHandleMap: Record<string, string> = {}; 151 + didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid); 152 + diff --git a/src/views/index.pug b/src/views/index.pug 153 + index 3443deb..e04403d 100644 154 + --- a/src/views/index.pug 155 + +++ b/src/views/index.pug 156 + @@ -1,6 +1,7 @@ 157 + include ../mixins/mkPost 158 + include ../mixins/head 159 + include ../mixins/header 160 + +include ../mixins/footer 161 + include ../mixins/utils 162 + include ../mixins/post 163 + 164 + @@ -19,6 +20,7 @@ include ../mixins/post 165 + "c", 166 + "c#", 167 + "c++", 168 + + "cobol", 169 + ].toSorted()) 170 + doctype html 171 + html 172 + @@ -34,3 +36,4 @@ html 173 + each paste in pastes 174 + - var handle = didHandleMap[paste.authorDid] 175 + +post(paste, handle, paste.authorDid) 176 + + +footer() 177 + diff --git a/src/views/paste.pug b/src/views/paste.pug 178 + index 29516d3..f8a0906 100644 179 + --- a/src/views/paste.pug 180 + +++ b/src/views/paste.pug 181 + @@ -15,12 +15,21 @@ html 182 + | #{paste.lang} · 183 + | #{paste.code.split('\n').length} loc · 184 + a(href=`/r/${paste.shortUrl}`) raw 185 + + | &nbsp;·&nbsp; 186 + + | #{comments.length} #{pluralize(comments.length, 'comment')} 187 + pre 188 + - | #{paste.code} 189 + + code 190 + + - var lines = paste.code.split(/\r?\n|\r|\n/g) 191 + + - var tot_chars = lines.length.toString().length 192 + + each line, idx in lines 193 + + span.code-line 194 + + span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`) 195 + + | #{idx + 1} 196 + + span.code-line-content #{line} 197 + hr 198 + 199 + if comments.length != 0 200 + - h1 comments 201 + + h1(id="comments") comments 202 + div.comments 203 + each comment in comments 204 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 205 + @@ -33,9 +42,14 @@ html 206 + pre.comment-body #{comment.body} 207 + hr 208 + 209 + - form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 210 + - div.post-row 211 + - textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 212 + + if ownDid 213 + + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 214 + + div.post-row 215 + + textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 216 + 217 + - div.post-submit-row 218 + - button(type="submit").post-input-submit zonk! 219 + + div.post-submit-row 220 + + button(type="submit").post-input-submit zonk! 221 + + else 222 + + p 223 + + a(href="/login") login 224 + + |&nbsp;to post a comment 225 + -- 226 + 2.47.0 227 + 228 + From 436f4d5e8912c50068d8c70e623a23ce2ca9e6e7 Mon Sep 17 00:00:00 2001 229 + From: Akshay <nerdy@peppe.rs> 230 + Date: Thu, 26 Dec 2024 21:12:07 +0000 231 + Subject: [PATCH 2/3] footer everywhere 232 + 233 + --- 234 + src/views/index.pug | 1 + 235 + src/views/login.pug | 2 ++ 236 + src/views/paste.pug | 6 ++++-- 237 + src/views/user.pug | 3 +++ 238 + 4 files changed, 10 insertions(+), 2 deletions(-) 239 + 240 + diff --git a/src/views/index.pug b/src/views/index.pug 241 + index e04403d..bc9085a 100644 242 + --- a/src/views/index.pug 243 + +++ b/src/views/index.pug 244 + @@ -36,4 +36,5 @@ html 245 + each paste in pastes 246 + - var handle = didHandleMap[paste.authorDid] 247 + +post(paste, handle, paste.authorDid) 248 + + 249 + +footer() 250 + diff --git a/src/views/login.pug b/src/views/login.pug 251 + index 55aa048..b5a35e0 100644 252 + --- a/src/views/login.pug 253 + +++ b/src/views/login.pug 254 + @@ -1,4 +1,5 @@ 255 + include ../mixins/head 256 + +include ../mixins/footer 257 + 258 + doctype html 259 + html 260 + @@ -10,3 +11,4 @@ html 261 + div.login-row 262 + input(type="text" name="handle" placeholder="enter handle" required).login-input-title 263 + button(type="submit").login-submit-button login 264 + + +footer() 265 + diff --git a/src/views/paste.pug b/src/views/paste.pug 266 + index f8a0906..653c02e 100644 267 + --- a/src/views/paste.pug 268 + +++ b/src/views/paste.pug 269 + @@ -1,6 +1,7 @@ 270 + - var now = new Date() 271 + include ../mixins/head 272 + include ../mixins/header 273 + +include ../mixins/footer 274 + include ../mixins/utils 275 + doctype html 276 + html 277 + @@ -40,7 +41,6 @@ html 278 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 279 + p 280 + pre.comment-body #{comment.body} 281 + - hr 282 + 283 + if ownDid 284 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 285 + @@ -49,7 +49,9 @@ html 286 + 287 + div.post-submit-row 288 + button(type="submit").post-input-submit zonk! 289 + - else 290 + + else 291 + p 292 + a(href="/login") login 293 + |&nbsp;to post a comment 294 + + 295 + + +footer() 296 + diff --git a/src/views/user.pug b/src/views/user.pug 297 + index 3523e16..b2b2743 100644 298 + --- a/src/views/user.pug 299 + +++ b/src/views/user.pug 300 + @@ -2,6 +2,7 @@ 301 + - var handle = didHandleMap[authorDid] 302 + include ../mixins/head 303 + include ../mixins/header 304 + +include ../mixins/footer 305 + include ../mixins/utils 306 + include ../mixins/post 307 + doctype html 308 + @@ -14,3 +15,5 @@ html 309 + div.timeline 310 + each paste in pastes 311 + +post(paste, handle, authorDid) 312 + + 313 + + +footer() 314 + -- 315 + 2.47.0 316 + 317 + From a717f0702e818515e34187cde86c15c00d2e66ba Mon Sep 17 00:00:00 2001 318 + From: Akshay <nerdy@peppe.rs> 319 + Date: Thu, 26 Dec 2024 23:14:37 +0000 320 + Subject: [PATCH 3/3] stylin for links 321 + 322 + --- 323 + src/mixins/post.pug | 2 +- 324 + src/public/styles.css | 51 ++++++++++++++++++++++++++++++++++++++++++- 325 + src/views/paste.pug | 9 ++++---- 326 + 3 files changed, 55 insertions(+), 7 deletions(-) 327 + 328 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 329 + index e98bcf8..51af1ff 100644 330 + --- a/src/mixins/post.pug 331 + +++ b/src/mixins/post.pug 332 + @@ -1,7 +1,7 @@ 333 + mixin post(paste, handle, did) 334 + div.post 335 + p 336 + - a(href=`/p/${paste.shortUrl}`) 337 + + a(href=`/p/${paste.shortUrl}`).post-link 338 + | #{paste.title} 339 + p.post-info 340 + | by 341 + diff --git a/src/public/styles.css b/src/public/styles.css 342 + index 6f80f5f..b153f92 100644 343 + --- a/src/public/styles.css 344 + +++ b/src/public/styles.css 345 + @@ -58,7 +58,6 @@ a:visited { 346 + } 347 + 348 + pre { 349 + - background-color: var(--bg-color-muted); 350 + padding: 1rem; 351 + overflow-x: auto; 352 + } 353 + @@ -66,6 +65,11 @@ pre { 354 + .comment-body { 355 + background-color: var(--bg-color); 356 + padding: 0; 357 + + margin-top: 0.1rem; 358 + +} 359 + + 360 + +.comment-info { 361 + + margin-bottom: 0; 362 + } 363 + 364 + input, textarea, select, button { 365 + @@ -137,10 +141,40 @@ hr { 366 + align-self: flex-end; 367 + } 368 + 369 + +.post-link { 370 + + color: var(--text-color); 371 + + text-decoration: none; 372 + +} 373 + +.post-link:hover { 374 + + text-decoration: underline; 375 + +} 376 + +.post-link:visited { 377 + + color: var(--text-color-muted); 378 + +} 379 + + 380 + +.post-info { 381 + + margin-top: 0; 382 + +} 383 + + 384 + +.post-info, .comment-info { 385 + + color: var(--text-color-muted); 386 + +} 387 + +.post-info a, .comment-info a { 388 + + color: var(--text-color-muted); 389 + + text-decoration: none; 390 + +} 391 + +.post-info a:visited, .comment-info a:visited { 392 + + color: var(--text-color-muted); 393 + +} 394 + +.post-info a:hover, .comment-info a:hover { 395 + + text-decoration: underline; 396 + +} 397 + + 398 + .timeline, .comments { 399 + display: flex; 400 + flex-direction: column; 401 + gap: 1rem; 402 + + padding-bottom: 1rem; 403 + } 404 + 405 + .login-input-title { 406 + @@ -182,3 +216,18 @@ select { 407 + .code-line-content { 408 + color: var(--text-color); 409 + } 410 + + 411 + +.header, .footer { 412 + + color: var(--text-color); 413 + +} 414 + + 415 + +.header a, .header a:visited, 416 + +.footer a, .footer a:visited { 417 + + color: var(--link-color); 418 + + text-decoration: none; 419 + +} 420 + + 421 + +.header a:hover, 422 + +.footer a:hover { 423 + + text-decoration: underline; 424 + +} 425 + diff --git a/src/views/paste.pug b/src/views/paste.pug 426 + index 653c02e..3014107 100644 427 + --- a/src/views/paste.pug 428 + +++ b/src/views/paste.pug 429 + @@ -10,13 +10,13 @@ html 430 + main#content 431 + +header(ownDid, didHandleMap) 432 + h1 #{paste.title} 433 + - p 434 + + p.post-info 435 + | @#{didHandleMap[paste.authorDid]} · 436 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago · 437 + | #{paste.lang} · 438 + | #{paste.code.split('\n').length} loc · 439 + a(href=`/r/${paste.shortUrl}`) raw 440 + - | &nbsp;·&nbsp; 441 + + | &nbsp;· 442 + | #{comments.length} #{pluralize(comments.length, 'comment')} 443 + pre 444 + code 445 + @@ -34,13 +34,12 @@ html 446 + div.comments 447 + each comment in comments 448 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 449 + - p 450 + + p.comment-info 451 + a(href=`/u/${comment.authorDid}`) 452 + | @#{didHandleMap[comment.authorDid]} 453 + | &nbsp;· 454 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 455 + - p 456 + - pre.comment-body #{comment.body} 457 + + pre.comment-body #{comment.body} 458 + 459 + if ownDid 460 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 461 + -- 462 + 2.47.0 463 +
+465
y
··· 1 + From 6df2fc36269c7114fb845fa0ac667e08f6e7d09b Mon Sep 17 00:00:00 2001 2 + From: Akshay <nerdy@peppe.rs> 3 + Date: Thu, 26 Dec 2024 19:19:37 +0000 4 + Subject: [PATCH 1/3] add footer, comments, linenrs etc 5 + 6 + --- 7 + src/mixins/footer.pug | 9 +++++++++ 8 + src/mixins/post.pug | 3 ++- 9 + src/mixins/utils.pug | 4 ++++ 10 + src/public/styles.css | 21 +++++++++++++++++++-- 11 + src/routes.ts | 33 +++++++++++++++++++++++++++++---- 12 + src/views/index.pug | 3 +++ 13 + src/views/paste.pug | 28 +++++++++++++++++++++------- 14 + 7 files changed, 87 insertions(+), 14 deletions(-) 15 + create mode 100644 src/mixins/footer.pug 16 + 17 + diff --git a/src/mixins/footer.pug b/src/mixins/footer.pug 18 + new file mode 100644 19 + index 0000000..be71086 20 + --- /dev/null 21 + +++ b/src/mixins/footer.pug 22 + @@ -0,0 +1,9 @@ 23 + +mixin footer() 24 + + hr 25 + + div.footer 26 + + div.left-side 27 + + div.right-side 28 + + p 29 + + | made by 30 + + a(href="https://bsky.app/profile/oppi.li") @oppi.li 31 + + 32 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 33 + index 77d78aa..e98bcf8 100644 34 + --- a/src/mixins/post.pug 35 + +++ b/src/mixins/post.pug 36 + @@ -13,4 +13,5 @@ mixin post(paste, handle, did) 37 + | #{paste.lang} 38 + | · 39 + | #{paste.code.split('\n').length} loc 40 + - 41 + + | ·&nbsp; 42 + + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')} 43 + diff --git a/src/mixins/utils.pug b/src/mixins/utils.pug 44 + index 857bddd..08507d3 100644 45 + --- a/src/mixins/utils.pug 46 + +++ b/src/mixins/utils.pug 47 + @@ -2,6 +2,10 @@ 48 + function randInt(min, max) { 49 + return Math.floor(Math.random() * (max - min + 1)) + min; 50 + } 51 + +- 52 + + function pluralize(count, noun) { 53 + + return count==1?noun:`${noun}s`; 54 + + } 55 + - 56 + function timeDifference(current, previous) { 57 + if (!current || !previous) { 58 + diff --git a/src/public/styles.css b/src/public/styles.css 59 + index f88b533..6f80f5f 100644 60 + --- a/src/public/styles.css 61 + +++ b/src/public/styles.css 62 + @@ -104,7 +104,6 @@ textarea { 63 + hr { 64 + border: none; 65 + border-top: 1px solid var(--bg-color-muted); 66 + - padding: 1rem; 67 + } 68 + 69 + .post-form { 70 + @@ -152,7 +151,7 @@ hr { 71 + flex: 1 72 + } 73 + 74 + -.header { 75 + +.header, .footer { 76 + display: flex; 77 + flex-direction: row; 78 + justify-content: space-between; 79 + @@ -165,3 +164,21 @@ select { 80 + text-indent: 1px; 81 + text-overflow: ''; 82 + } 83 + + 84 + +.code-line { 85 + + display: flex; 86 + +} 87 + + 88 + +.code-line-num { 89 + + white-space: pre; 90 + + -webkit-user-select: none; 91 + + user-select: none; 92 + + margin-right: 0.4em; 93 + + padding: 0 0.4em 0 0.4em; 94 + + color: var(--text-color-muted); 95 + + text-align: right; 96 + +} 97 + + 98 + +.code-line-content { 99 + + color: var(--text-color); 100 + +} 101 + diff --git a/src/routes.ts b/src/routes.ts 102 + index 70f931d..17fa00e 100644 103 + --- a/src/routes.ts 104 + +++ b/src/routes.ts 105 + @@ -105,8 +105,20 @@ export const createRouter = (ctx: Ctx) => { 106 + const agent = await getSessionAgent(req, res, ctx); 107 + const pastes = await ctx.db 108 + .selectFrom("paste") 109 + - .selectAll() 110 + - .orderBy("indexedAt", "desc") 111 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 112 + + .select([ 113 + + "paste.uri", 114 + + "paste.shortUrl", 115 + + "paste.authorDid", 116 + + "paste.code", 117 + + "paste.lang", 118 + + "paste.title", 119 + + "paste.createdAt", 120 + + "paste.indexedAt as pasteIndexedAt", 121 + + ctx.db.fn.count("comment.uri").as("commentCount") 122 + + ]) 123 + + .groupBy("paste.uri") 124 + + .orderBy("pasteIndexedAt", "desc") 125 + .limit(25) 126 + .execute(); 127 + 128 + @@ -130,8 +142,21 @@ export const createRouter = (ctx: Ctx) => { 129 + const { authorDid } = req.params; 130 + const pastes = await ctx.db 131 + .selectFrom("paste") 132 + - .selectAll() 133 + - .where("authorDid", "=", authorDid) 134 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 135 + + .select([ 136 + + "paste.uri", 137 + + "paste.shortUrl", 138 + + "paste.authorDid as pasteAuthorDid", 139 + + "paste.code", 140 + + "paste.lang", 141 + + "paste.title", 142 + + "paste.createdAt as pasteCreatedAt", 143 + + "paste.indexedAt as pasteIndexedAt", 144 + + ctx.db.fn.count("comment.uri").as("commentCount") 145 + + ]) 146 + + .groupBy("paste.uri") 147 + + .where("pasteAuthorDid", "=", authorDid) 148 + + .orderBy("pasteCreatedAt", "desc") 149 + .execute(); 150 + let didHandleMap: Record<string, string> = {}; 151 + didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid); 152 + diff --git a/src/views/index.pug b/src/views/index.pug 153 + index 3443deb..e04403d 100644 154 + --- a/src/views/index.pug 155 + +++ b/src/views/index.pug 156 + @@ -1,6 +1,7 @@ 157 + include ../mixins/mkPost 158 + include ../mixins/head 159 + include ../mixins/header 160 + +include ../mixins/footer 161 + include ../mixins/utils 162 + include ../mixins/post 163 + 164 + @@ -19,6 +20,7 @@ include ../mixins/post 165 + "c", 166 + "c#", 167 + "c++", 168 + + "cobol", 169 + ].toSorted()) 170 + doctype html 171 + html 172 + @@ -34,3 +36,4 @@ html 173 + each paste in pastes 174 + - var handle = didHandleMap[paste.authorDid] 175 + +post(paste, handle, paste.authorDid) 176 + + +footer() 177 + diff --git a/src/views/paste.pug b/src/views/paste.pug 178 + index 29516d3..f8a0906 100644 179 + --- a/src/views/paste.pug 180 + +++ b/src/views/paste.pug 181 + @@ -15,12 +15,21 @@ html 182 + | #{paste.lang} · 183 + | #{paste.code.split('\n').length} loc · 184 + a(href=`/r/${paste.shortUrl}`) raw 185 + + | &nbsp;·&nbsp; 186 + + | #{comments.length} #{pluralize(comments.length, 'comment')} 187 + pre 188 + - | #{paste.code} 189 + + code 190 + + - var lines = paste.code.split(/\r?\n|\r|\n/g) 191 + + - var tot_chars = lines.length.toString().length 192 + + each line, idx in lines 193 + + span.code-line 194 + + span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`) 195 + + | #{idx + 1} 196 + + span.code-line-content #{line} 197 + hr 198 + 199 + if comments.length != 0 200 + - h1 comments 201 + + h1(id="comments") comments 202 + div.comments 203 + each comment in comments 204 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 205 + @@ -33,9 +42,14 @@ html 206 + pre.comment-body #{comment.body} 207 + hr 208 + 209 + - form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 210 + - div.post-row 211 + - textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 212 + + if ownDid 213 + + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 214 + + div.post-row 215 + + textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 216 + 217 + - div.post-submit-row 218 + - button(type="submit").post-input-submit zonk! 219 + + div.post-submit-row 220 + + button(type="submit").post-input-submit zonk! 221 + + else 222 + + p 223 + + a(href="/login") login 224 + + |&nbsp;to post a comment 225 + -- 226 + 2.47.0 227 + 228 + 229 + From ccd3269be5d287b627c8badc96bd0ce5f0d3a0bf Mon Sep 17 00:00:00 2001 230 + From: Akshay <nerdy@peppe.rs> 231 + Date: Thu, 26 Dec 2024 21:12:07 +0000 232 + Subject: [PATCH 2/3] footer everywhere 233 + 234 + --- 235 + src/views/index.pug | 1 + 236 + src/views/login.pug | 2 ++ 237 + src/views/paste.pug | 6 ++++-- 238 + src/views/user.pug | 3 +++ 239 + 4 files changed, 10 insertions(+), 2 deletions(-) 240 + 241 + diff --git a/src/views/index.pug b/src/views/index.pug 242 + index e04403d..bc9085a 100644 243 + --- a/src/views/index.pug 244 + +++ b/src/views/index.pug 245 + @@ -36,4 +36,5 @@ html 246 + each paste in pastes 247 + - var handle = didHandleMap[paste.authorDid] 248 + +post(paste, handle, paste.authorDid) 249 + + 250 + +footer() 251 + diff --git a/src/views/login.pug b/src/views/login.pug 252 + index 55aa048..b5a35e0 100644 253 + --- a/src/views/login.pug 254 + +++ b/src/views/login.pug 255 + @@ -1,4 +1,5 @@ 256 + include ../mixins/head 257 + +include ../mixins/footer 258 + 259 + doctype html 260 + html 261 + @@ -10,3 +11,4 @@ html 262 + div.login-row 263 + input(type="text" name="handle" placeholder="enter handle" required).login-input-title 264 + button(type="submit").login-submit-button login 265 + + +footer() 266 + diff --git a/src/views/paste.pug b/src/views/paste.pug 267 + index f8a0906..653c02e 100644 268 + --- a/src/views/paste.pug 269 + +++ b/src/views/paste.pug 270 + @@ -1,6 +1,7 @@ 271 + - var now = new Date() 272 + include ../mixins/head 273 + include ../mixins/header 274 + +include ../mixins/footer 275 + include ../mixins/utils 276 + doctype html 277 + html 278 + @@ -40,7 +41,6 @@ html 279 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 280 + p 281 + pre.comment-body #{comment.body} 282 + - hr 283 + 284 + if ownDid 285 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 286 + @@ -49,7 +49,9 @@ html 287 + 288 + div.post-submit-row 289 + button(type="submit").post-input-submit zonk! 290 + - else 291 + + else 292 + p 293 + a(href="/login") login 294 + |&nbsp;to post a comment 295 + + 296 + + +footer() 297 + diff --git a/src/views/user.pug b/src/views/user.pug 298 + index 3523e16..b2b2743 100644 299 + --- a/src/views/user.pug 300 + +++ b/src/views/user.pug 301 + @@ -2,6 +2,7 @@ 302 + - var handle = didHandleMap[authorDid] 303 + include ../mixins/head 304 + include ../mixins/header 305 + +include ../mixins/footer 306 + include ../mixins/utils 307 + include ../mixins/post 308 + doctype html 309 + @@ -14,3 +15,5 @@ html 310 + div.timeline 311 + each paste in pastes 312 + +post(paste, handle, authorDid) 313 + + 314 + + +footer() 315 + -- 316 + 2.47.0 317 + 318 + 319 + From 0d1ee81ee0630ca852f51a9a49293fb7d0fc7a67 Mon Sep 17 00:00:00 2001 320 + From: Akshay <nerdy@peppe.rs> 321 + Date: Thu, 26 Dec 2024 23:14:37 +0000 322 + Subject: [PATCH 3/3] stylin for links 323 + 324 + --- 325 + src/mixins/post.pug | 2 +- 326 + src/public/styles.css | 51 ++++++++++++++++++++++++++++++++++++++++++- 327 + src/views/paste.pug | 9 ++++---- 328 + 3 files changed, 55 insertions(+), 7 deletions(-) 329 + 330 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 331 + index e98bcf8..51af1ff 100644 332 + --- a/src/mixins/post.pug 333 + +++ b/src/mixins/post.pug 334 + @@ -1,7 +1,7 @@ 335 + mixin post(paste, handle, did) 336 + div.post 337 + p 338 + - a(href=`/p/${paste.shortUrl}`) 339 + + a(href=`/p/${paste.shortUrl}`).post-link 340 + | #{paste.title} 341 + p.post-info 342 + | by 343 + diff --git a/src/public/styles.css b/src/public/styles.css 344 + index 6f80f5f..b153f92 100644 345 + --- a/src/public/styles.css 346 + +++ b/src/public/styles.css 347 + @@ -58,7 +58,6 @@ a:visited { 348 + } 349 + 350 + pre { 351 + - background-color: var(--bg-color-muted); 352 + padding: 1rem; 353 + overflow-x: auto; 354 + } 355 + @@ -66,6 +65,11 @@ pre { 356 + .comment-body { 357 + background-color: var(--bg-color); 358 + padding: 0; 359 + + margin-top: 0.1rem; 360 + +} 361 + + 362 + +.comment-info { 363 + + margin-bottom: 0; 364 + } 365 + 366 + input, textarea, select, button { 367 + @@ -137,10 +141,40 @@ hr { 368 + align-self: flex-end; 369 + } 370 + 371 + +.post-link { 372 + + color: var(--text-color); 373 + + text-decoration: none; 374 + +} 375 + +.post-link:hover { 376 + + text-decoration: underline; 377 + +} 378 + +.post-link:visited { 379 + + color: var(--text-color-muted); 380 + +} 381 + + 382 + +.post-info { 383 + + margin-top: 0; 384 + +} 385 + + 386 + +.post-info, .comment-info { 387 + + color: var(--text-color-muted); 388 + +} 389 + +.post-info a, .comment-info a { 390 + + color: var(--text-color-muted); 391 + + text-decoration: none; 392 + +} 393 + +.post-info a:visited, .comment-info a:visited { 394 + + color: var(--text-color-muted); 395 + +} 396 + +.post-info a:hover, .comment-info a:hover { 397 + + text-decoration: underline; 398 + +} 399 + + 400 + .timeline, .comments { 401 + display: flex; 402 + flex-direction: column; 403 + gap: 1rem; 404 + + padding-bottom: 1rem; 405 + } 406 + 407 + .login-input-title { 408 + @@ -182,3 +216,18 @@ select { 409 + .code-line-content { 410 + color: var(--text-color); 411 + } 412 + + 413 + +.header, .footer { 414 + + color: var(--text-color); 415 + +} 416 + + 417 + +.header a, .header a:visited, 418 + +.footer a, .footer a:visited { 419 + + color: var(--link-color); 420 + + text-decoration: none; 421 + +} 422 + + 423 + +.header a:hover, 424 + +.footer a:hover { 425 + + text-decoration: underline; 426 + +} 427 + diff --git a/src/views/paste.pug b/src/views/paste.pug 428 + index 653c02e..3014107 100644 429 + --- a/src/views/paste.pug 430 + +++ b/src/views/paste.pug 431 + @@ -10,13 +10,13 @@ html 432 + main#content 433 + +header(ownDid, didHandleMap) 434 + h1 #{paste.title} 435 + - p 436 + + p.post-info 437 + | @#{didHandleMap[paste.authorDid]} · 438 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago · 439 + | #{paste.lang} · 440 + | #{paste.code.split('\n').length} loc · 441 + a(href=`/r/${paste.shortUrl}`) raw 442 + - | &nbsp;·&nbsp; 443 + + | &nbsp;· 444 + | #{comments.length} #{pluralize(comments.length, 'comment')} 445 + pre 446 + code 447 + @@ -34,13 +34,12 @@ html 448 + div.comments 449 + each comment in comments 450 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 451 + - p 452 + + p.comment-info 453 + a(href=`/u/${comment.authorDid}`) 454 + | @#{didHandleMap[comment.authorDid]} 455 + | &nbsp;· 456 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 457 + - p 458 + - pre.comment-body #{comment.body} 459 + + pre.comment-body #{comment.body} 460 + 461 + if ownDid 462 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 463 + -- 464 + 2.47.0 465 +