···11import { AuthError, Minisky } from './minisky.js';
22import { atURI, feedPostTime } from '../utils.js';
33-import { Post } from '../models.js';
33+import { Post } from '../models/posts.js';
4455/**
66 * Thrown when the response is technically a "success" one, but the returned data is not what it should be.
+5-5
src/embed_component.js
···22import { $tag } from './utils_ts.js';
33import { PostComponent } from './post_component.js';
4455+import { FeedGeneratorRecord, StarterPackRecord, UserListRecord } from './models/records.js';
66+import { Post, BlockedPost, MissingPost, DetachedQuotePost } from './models/posts.js';
57import {
66- Post, BlockedPost, MissingPost, DetachedQuotePost, Embed,
77- RawRecordEmbed, RawRecordWithMediaEmbed, RawImageEmbed, RawLinkEmbed, RawVideoEmbed,
88- InlineRecordEmbed, InlineRecordWithMediaEmbed, InlineImageEmbed, InlineLinkEmbed, InlineVideoEmbed,
99- FeedGeneratorRecord, StarterPackRecord, UserListRecord
1010-} from './models.js';
88+ Embed, RawRecordEmbed, RawRecordWithMediaEmbed, RawImageEmbed, RawLinkEmbed, RawVideoEmbed,
99+ InlineRecordEmbed, InlineRecordWithMediaEmbed, InlineImageEmbed, InlineLinkEmbed, InlineVideoEmbed
1010+} from './models/embeds.js';
11111212/**
1313 * Renders an embed (e.g. image or quoted post) inside the post view.
+4-328
src/models.js
src/models/posts.js
···11-import { atURI, castToInt } from './utils.js';
11+import { castToInt } from '../utils.js';
22+import { ATProtoRecord, FeedGeneratorRecord, StarterPackRecord, UserListRecord } from './records.js';
33+import { Embed } from './embeds.js';
2435/**
46 * Thrown when parsing post JSON fails.
57 */
6877-class PostDataError extends Error {
99+export class PostDataError extends Error {
810911 /** @param {string} message */
1012 constructor(message) {
1113 super(message);
1214 }
1315}
1414-1515-1616-/**
1717- * Generic record type, base class for all records or record view objects.
1818- */
1919-2020-export class ATProtoRecord {
2121-2222- /** @param {json} data, @param {json} [extra] */
2323- constructor(data, extra) {
2424- this.data = data;
2525- Object.assign(this, extra ?? {});
2626- }
2727-2828- /** @returns {string} */
2929- get uri() {
3030- return this.data.uri;
3131- }
3232-3333- /** @returns {string} */
3434- get cid() {
3535- return this.data.cid;
3636- }
3737-3838- /** @returns {string} */
3939- get rkey() {
4040- return atURI(this.uri).rkey;
4141- }
4242-4343- /** @returns {string} */
4444- get type() {
4545- return this.data.$type;
4646- }
4747-}
4848-49165017/**
5118 * Standard Bluesky post record.
···479446 */
480447481448export class DetachedQuotePost extends ATProtoRecord {}
482482-483483-484484-/**
485485- * Record representing a feed generator.
486486- */
487487-488488-export class FeedGeneratorRecord extends ATProtoRecord {
489489-490490- /** @param {json} data */
491491- constructor(data) {
492492- super(data);
493493- this.author = data.creator;
494494- }
495495-496496- /** @returns {string | undefined} */
497497- get title() {
498498- return this.data.displayName;
499499- }
500500-501501- /** @returns {string | undefined} */
502502- get description() {
503503- return this.data.description;
504504- }
505505-506506- /** @returns {number} */
507507- get likeCount() {
508508- return castToInt(this.data.likeCount);
509509- }
510510-511511- /** @returns {string | undefined} */
512512- get avatar() {
513513- return this.data.avatar;
514514- }
515515-}
516516-517517-518518-/**
519519- * Record representing a user list or moderation list.
520520- */
521521-522522-export class UserListRecord extends ATProtoRecord {
523523-524524- /** @param {json} data */
525525- constructor(data) {
526526- super(data);
527527- this.author = data.creator;
528528- }
529529-530530- /** @returns {string | undefined} */
531531- get title() {
532532- return this.data.name;
533533- }
534534-535535- /** @returns {string | undefined} */
536536- get purpose() {
537537- return this.data.purpose;
538538- }
539539-540540- /** @returns {string | undefined} */
541541- get description() {
542542- return this.data.description;
543543- }
544544-545545- /** @returns {string | undefined} */
546546- get avatar() {
547547- return this.data.avatar;
548548- }
549549-}
550550-551551-552552-/**
553553- * Record representing a starter pack.
554554- */
555555-556556-export class StarterPackRecord extends ATProtoRecord {
557557-558558- /** @param {json} data */
559559- constructor(data) {
560560- super(data);
561561- this.author = data.creator;
562562- }
563563-564564- /** @returns {string | undefined} */
565565- get title() {
566566- return this.data.record.name;
567567- }
568568-569569- /** @returns {string | undefined} */
570570- get description() {
571571- return this.data.record.description;
572572- }
573573-}
574574-575575-576576-/**
577577- * Base class for embed objects.
578578- */
579579-580580-export class Embed {
581581-582582- /**
583583- * More hydrated view of an embed, taken from a full post view (#postView).
584584- *
585585- * @param {json} json, @returns {Embed}
586586- */
587587-588588- static parseInlineEmbed(json) {
589589- switch (json.$type) {
590590- case 'app.bsky.embed.record#view':
591591- return new InlineRecordEmbed(json);
592592-593593- case 'app.bsky.embed.recordWithMedia#view':
594594- return new InlineRecordWithMediaEmbed(json);
595595-596596- case 'app.bsky.embed.images#view':
597597- return new InlineImageEmbed(json);
598598-599599- case 'app.bsky.embed.external#view':
600600- return new InlineLinkEmbed(json);
601601-602602- case 'app.bsky.embed.video#view':
603603- return new InlineVideoEmbed(json);
604604-605605- default:
606606- if (location.protocol == 'file:') {
607607- throw new PostDataError(`Unexpected embed type: ${json.$type}`);
608608- } else {
609609- console.warn('Unexpected embed type:', json.$type);
610610- return new Embed(json);
611611- }
612612- }
613613- }
614614-615615- /**
616616- * Raw embed extracted from raw record data of a post. Does not include quoted post contents.
617617- *
618618- * @param {json} json, @returns {Embed}
619619- */
620620-621621- static parseRawEmbed(json) {
622622- switch (json.$type) {
623623- case 'app.bsky.embed.record':
624624- return new RawRecordEmbed(json);
625625-626626- case 'app.bsky.embed.recordWithMedia':
627627- return new RawRecordWithMediaEmbed(json);
628628-629629- case 'app.bsky.embed.images':
630630- return new RawImageEmbed(json);
631631-632632- case 'app.bsky.embed.external':
633633- return new RawLinkEmbed(json);
634634-635635- case 'app.bsky.embed.video':
636636- return new RawVideoEmbed(json);
637637-638638- default:
639639- if (location.protocol == 'file:') {
640640- throw new PostDataError(`Unexpected embed type: ${json.$type}`);
641641- } else {
642642- console.warn('Unexpected embed type:', json.$type);
643643- return new Embed(json);
644644- }
645645- }
646646- }
647647-648648- /** @param {json} json */
649649- constructor(json) {
650650- this.json = json;
651651- }
652652-653653- /** @returns {string} */
654654- get type() {
655655- return this.json.$type;
656656- }
657657-}
658658-659659-export class RawImageEmbed extends Embed {
660660-661661- /** @param {json} json */
662662- constructor(json) {
663663- super(json);
664664- this.images = json.images;
665665- }
666666-}
667667-668668-export class RawLinkEmbed extends Embed {
669669-670670- /** @param {json} json */
671671- constructor(json) {
672672- super(json);
673673-674674- this.url = json.external.uri;
675675- this.title = json.external.title;
676676- this.thumb = json.external.thumb;
677677- }
678678-}
679679-680680-export class RawVideoEmbed extends Embed {
681681-682682- /** @param {json} json */
683683- constructor(json) {
684684- super(json);
685685- this.video = json.video;
686686- }
687687-}
688688-689689-export class RawRecordEmbed extends Embed {
690690-691691- /** @param {json} json */
692692- constructor(json) {
693693- super(json);
694694- this.record = new ATProtoRecord(json.record);
695695- }
696696-}
697697-698698-export class RawRecordWithMediaEmbed extends Embed {
699699-700700- /** @param {json} json */
701701- constructor(json) {
702702- super(json);
703703- this.record = new ATProtoRecord(json.record.record);
704704- this.media = Embed.parseRawEmbed(json.media);
705705- }
706706-}
707707-708708-export class InlineRecordEmbed extends Embed {
709709-710710- /**
711711- * app.bsky.embed.record#view
712712- * @param {json} json
713713- */
714714- constructor(json) {
715715- super(json);
716716- this.post = Post.parseViewRecord(json.record);
717717- }
718718-}
719719-720720-export class InlineRecordWithMediaEmbed extends Embed {
721721-722722- /**
723723- * app.bsky.embed.recordWithMedia#view
724724- * @param {json} json
725725- */
726726- constructor(json) {
727727- super(json);
728728- this.post = Post.parseViewRecord(json.record.record);
729729- this.media = Embed.parseInlineEmbed(json.media);
730730- }
731731-}
732732-733733-export class InlineLinkEmbed extends Embed {
734734-735735- /**
736736- * app.bsky.embed.external#view
737737- * @param {json} json
738738- */
739739- constructor(json) {
740740- super(json);
741741-742742- this.url = json.external.uri;
743743- this.title = json.external.title;
744744- this.description = json.external.description;
745745- this.thumb = json.external.thumb;
746746- }
747747-}
748748-749749-export class InlineImageEmbed extends Embed {
750750-751751- /**
752752- * app.bsky.embed.images#view
753753- * @param {json} json
754754- */
755755- constructor(json) {
756756- super(json);
757757- this.images = json.images;
758758- }
759759-}
760760-761761-export class InlineVideoEmbed extends Embed {
762762-763763- /**
764764- * app.bsky.embed.video#view
765765- * @param {json} json
766766- */
767767- constructor(json) {
768768- super(json);
769769- this.playlistURL = json.playlist;
770770- this.alt = json.alt;
771771- }
772772-}
+200
src/models/embeds.js
···11+import { ATProtoRecord } from './records.js';
22+import { Post, PostDataError } from './posts.js';
33+44+/**
55+ * Base class for embed objects.
66+ */
77+88+export class Embed {
99+1010+ /**
1111+ * More hydrated view of an embed, taken from a full post view (#postView).
1212+ *
1313+ * @param {json} json, @returns {Embed}
1414+ */
1515+1616+ static parseInlineEmbed(json) {
1717+ switch (json.$type) {
1818+ case 'app.bsky.embed.record#view':
1919+ return new InlineRecordEmbed(json);
2020+2121+ case 'app.bsky.embed.recordWithMedia#view':
2222+ return new InlineRecordWithMediaEmbed(json);
2323+2424+ case 'app.bsky.embed.images#view':
2525+ return new InlineImageEmbed(json);
2626+2727+ case 'app.bsky.embed.external#view':
2828+ return new InlineLinkEmbed(json);
2929+3030+ case 'app.bsky.embed.video#view':
3131+ return new InlineVideoEmbed(json);
3232+3333+ default:
3434+ if (location.protocol == 'file:') {
3535+ throw new PostDataError(`Unexpected embed type: ${json.$type}`);
3636+ } else {
3737+ console.warn('Unexpected embed type:', json.$type);
3838+ return new Embed(json);
3939+ }
4040+ }
4141+ }
4242+4343+ /**
4444+ * Raw embed extracted from raw record data of a post. Does not include quoted post contents.
4545+ *
4646+ * @param {json} json, @returns {Embed}
4747+ */
4848+4949+ static parseRawEmbed(json) {
5050+ switch (json.$type) {
5151+ case 'app.bsky.embed.record':
5252+ return new RawRecordEmbed(json);
5353+5454+ case 'app.bsky.embed.recordWithMedia':
5555+ return new RawRecordWithMediaEmbed(json);
5656+5757+ case 'app.bsky.embed.images':
5858+ return new RawImageEmbed(json);
5959+6060+ case 'app.bsky.embed.external':
6161+ return new RawLinkEmbed(json);
6262+6363+ case 'app.bsky.embed.video':
6464+ return new RawVideoEmbed(json);
6565+6666+ default:
6767+ if (location.protocol == 'file:') {
6868+ throw new PostDataError(`Unexpected embed type: ${json.$type}`);
6969+ } else {
7070+ console.warn('Unexpected embed type:', json.$type);
7171+ return new Embed(json);
7272+ }
7373+ }
7474+ }
7575+7676+ /** @param {json} json */
7777+ constructor(json) {
7878+ this.json = json;
7979+ }
8080+8181+ /** @returns {string} */
8282+ get type() {
8383+ return this.json.$type;
8484+ }
8585+}
8686+8787+export class RawImageEmbed extends Embed {
8888+8989+ /** @param {json} json */
9090+ constructor(json) {
9191+ super(json);
9292+ this.images = json.images;
9393+ }
9494+}
9595+9696+export class RawLinkEmbed extends Embed {
9797+9898+ /** @param {json} json */
9999+ constructor(json) {
100100+ super(json);
101101+102102+ this.url = json.external.uri;
103103+ this.title = json.external.title;
104104+ this.thumb = json.external.thumb;
105105+ }
106106+}
107107+108108+export class RawVideoEmbed extends Embed {
109109+110110+ /** @param {json} json */
111111+ constructor(json) {
112112+ super(json);
113113+ this.video = json.video;
114114+ }
115115+}
116116+117117+export class RawRecordEmbed extends Embed {
118118+119119+ /** @param {json} json */
120120+ constructor(json) {
121121+ super(json);
122122+ this.record = new ATProtoRecord(json.record);
123123+ }
124124+}
125125+126126+export class RawRecordWithMediaEmbed extends Embed {
127127+128128+ /** @param {json} json */
129129+ constructor(json) {
130130+ super(json);
131131+ this.record = new ATProtoRecord(json.record.record);
132132+ this.media = Embed.parseRawEmbed(json.media);
133133+ }
134134+}
135135+136136+export class InlineRecordEmbed extends Embed {
137137+138138+ /**
139139+ * app.bsky.embed.record#view
140140+ * @param {json} json
141141+ */
142142+ constructor(json) {
143143+ super(json);
144144+ this.post = Post.parseViewRecord(json.record);
145145+ }
146146+}
147147+148148+export class InlineRecordWithMediaEmbed extends Embed {
149149+150150+ /**
151151+ * app.bsky.embed.recordWithMedia#view
152152+ * @param {json} json
153153+ */
154154+ constructor(json) {
155155+ super(json);
156156+ this.post = Post.parseViewRecord(json.record.record);
157157+ this.media = Embed.parseInlineEmbed(json.media);
158158+ }
159159+}
160160+161161+export class InlineLinkEmbed extends Embed {
162162+163163+ /**
164164+ * app.bsky.embed.external#view
165165+ * @param {json} json
166166+ */
167167+ constructor(json) {
168168+ super(json);
169169+170170+ this.url = json.external.uri;
171171+ this.title = json.external.title;
172172+ this.description = json.external.description;
173173+ this.thumb = json.external.thumb;
174174+ }
175175+}
176176+177177+export class InlineImageEmbed extends Embed {
178178+179179+ /**
180180+ * app.bsky.embed.images#view
181181+ * @param {json} json
182182+ */
183183+ constructor(json) {
184184+ super(json);
185185+ this.images = json.images;
186186+ }
187187+}
188188+189189+export class InlineVideoEmbed extends Embed {
190190+191191+ /**
192192+ * app.bsky.embed.video#view
193193+ * @param {json} json
194194+ */
195195+ constructor(json) {
196196+ super(json);
197197+ this.playlistURL = json.playlist;
198198+ this.alt = json.alt;
199199+ }
200200+}
+126
src/models/records.js
···11+import { atURI, castToInt } from '../utils.js';
22+33+/**
44+ * Generic record type, base class for all records or record view objects.
55+ */
66+77+export class ATProtoRecord {
88+99+ /** @param {json} data, @param {json} [extra] */
1010+ constructor(data, extra) {
1111+ this.data = data;
1212+ Object.assign(this, extra ?? {});
1313+ }
1414+1515+ /** @returns {string} */
1616+ get uri() {
1717+ return this.data.uri;
1818+ }
1919+2020+ /** @returns {string} */
2121+ get cid() {
2222+ return this.data.cid;
2323+ }
2424+2525+ /** @returns {string} */
2626+ get rkey() {
2727+ return atURI(this.uri).rkey;
2828+ }
2929+3030+ /** @returns {string} */
3131+ get type() {
3232+ return this.data.$type;
3333+ }
3434+}
3535+3636+3737+/**
3838+ * Record representing a feed generator.
3939+ */
4040+4141+export class FeedGeneratorRecord extends ATProtoRecord {
4242+4343+ /** @param {json} data */
4444+ constructor(data) {
4545+ super(data);
4646+ this.author = data.creator;
4747+ }
4848+4949+ /** @returns {string | undefined} */
5050+ get title() {
5151+ return this.data.displayName;
5252+ }
5353+5454+ /** @returns {string | undefined} */
5555+ get description() {
5656+ return this.data.description;
5757+ }
5858+5959+ /** @returns {number} */
6060+ get likeCount() {
6161+ return castToInt(this.data.likeCount);
6262+ }
6363+6464+ /** @returns {string | undefined} */
6565+ get avatar() {
6666+ return this.data.avatar;
6767+ }
6868+}
6969+7070+7171+/**
7272+ * Record representing a user list or moderation list.
7373+ */
7474+7575+export class UserListRecord extends ATProtoRecord {
7676+7777+ /** @param {json} data */
7878+ constructor(data) {
7979+ super(data);
8080+ this.author = data.creator;
8181+ }
8282+8383+ /** @returns {string | undefined} */
8484+ get title() {
8585+ return this.data.name;
8686+ }
8787+8888+ /** @returns {string | undefined} */
8989+ get purpose() {
9090+ return this.data.purpose;
9191+ }
9292+9393+ /** @returns {string | undefined} */
9494+ get description() {
9595+ return this.data.description;
9696+ }
9797+9898+ /** @returns {string | undefined} */
9999+ get avatar() {
100100+ return this.data.avatar;
101101+ }
102102+}
103103+104104+105105+/**
106106+ * Record representing a starter pack.
107107+ */
108108+109109+export class StarterPackRecord extends ATProtoRecord {
110110+111111+ /** @param {json} data */
112112+ constructor(data) {
113113+ super(data);
114114+ this.author = data.creator;
115115+ }
116116+117117+ /** @returns {string | undefined} */
118118+ get title() {
119119+ return this.data.record.name;
120120+ }
121121+122122+ /** @returns {string | undefined} */
123123+ get description() {
124124+ return this.data.record.description;
125125+ }
126126+}
+1-1
src/notifications_page.js
···22import { $id, atURI, linkToPostById, Paginator } from './utils.js';
33import { $tag } from './utils_ts.js';
44import { PostComponent } from './post_component.js';
55-import { Post } from './models.js';
55+import { Post } from './models/posts.js';
6677export class NotificationsPage {
88
+2-1
src/post_component.js
···1111} from './utils.js';
12121313import { $tag } from './utils_ts.js';
1414-import { Post, BlockedPost, MissingPost, DetachedQuotePost, InlineLinkEmbed } from './models.js';
1414+import { Post, BlockedPost, MissingPost, DetachedQuotePost } from './models/posts.js';
1515+import { InlineLinkEmbed } from './models/embeds.js';
1516import { APIError } from './api/minisky.js';
1617import { EmbedComponent } from './embed_component.js';
1718import { RichText } from '../lib/rich_text_lite.js';
+1-1
src/private_search_page.js
···11import { $, $id, feedPostTime, Paginator } from './utils.js';
22import { $tag } from './utils_ts.js';
33import { PostComponent } from './post_component.js';
44-import { Post } from './models.js';
44+import { Post } from './models/posts.js';
55import { BlueskyAPI } from './api/api.js';
6677export class PrivateSearchPage {
+1-1
src/skythread.js
···66import { getLocation, linkToPostById } from './utils.js';
77import { BlueskyAPI } from './api/api.js';
88import { Minisky } from './api/minisky.js';
99-import { Post } from './models.js';
99+import { Post } from './models/posts.js';
1010import { PostComponent } from './post_component.js';
1111import { Menu } from './menu.js';
1212import { ThreadPage } from './thread_page.js';
+1-1
src/thread_page.js
···11import { $, $id, atURI, linkToPostById, linkToPostThread, showError } from './utils.js';
22import { $tag } from './utils_ts.js';
33-import { Post, BlockedPost, MissingPost } from './models.js';
33+import { Post, BlockedPost, MissingPost } from './models/posts.js';
44import { PostComponent } from './post_component.js';
55import { setPageTitle, hideLoader } from './skythread.js';
66
+1-1
src/utils.js
···11import DOMPurify from 'dompurify';
22import { URLError } from './api/api.js';
33-import { Post } from './models.js';
33+import { Post } from './models/posts.js';
4455export class AtURI {
66 /** @param {string} uri */