Thread viewer for Bluesky

split models.js into 3 modules

+343 -340
+1 -1
src/api/api.js
··· 1 1 import { AuthError, Minisky } from './minisky.js'; 2 2 import { atURI, feedPostTime } from '../utils.js'; 3 - import { Post } from '../models.js'; 3 + import { Post } from '../models/posts.js'; 4 4 5 5 /** 6 6 * 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
··· 2 2 import { $tag } from './utils_ts.js'; 3 3 import { PostComponent } from './post_component.js'; 4 4 5 + import { FeedGeneratorRecord, StarterPackRecord, UserListRecord } from './models/records.js'; 6 + import { Post, BlockedPost, MissingPost, DetachedQuotePost } from './models/posts.js'; 5 7 import { 6 - Post, BlockedPost, MissingPost, DetachedQuotePost, Embed, 7 - RawRecordEmbed, RawRecordWithMediaEmbed, RawImageEmbed, RawLinkEmbed, RawVideoEmbed, 8 - InlineRecordEmbed, InlineRecordWithMediaEmbed, InlineImageEmbed, InlineLinkEmbed, InlineVideoEmbed, 9 - FeedGeneratorRecord, StarterPackRecord, UserListRecord 10 - } from './models.js'; 8 + Embed, RawRecordEmbed, RawRecordWithMediaEmbed, RawImageEmbed, RawLinkEmbed, RawVideoEmbed, 9 + InlineRecordEmbed, InlineRecordWithMediaEmbed, InlineImageEmbed, InlineLinkEmbed, InlineVideoEmbed 10 + } from './models/embeds.js'; 11 11 12 12 /** 13 13 * Renders an embed (e.g. image or quoted post) inside the post view.
+4 -328
src/models.js src/models/posts.js
··· 1 - import { atURI, castToInt } from './utils.js'; 1 + import { castToInt } from '../utils.js'; 2 + import { ATProtoRecord, FeedGeneratorRecord, StarterPackRecord, UserListRecord } from './records.js'; 3 + import { Embed } from './embeds.js'; 2 4 3 5 /** 4 6 * Thrown when parsing post JSON fails. 5 7 */ 6 8 7 - class PostDataError extends Error { 9 + export class PostDataError extends Error { 8 10 9 11 /** @param {string} message */ 10 12 constructor(message) { 11 13 super(message); 12 14 } 13 15 } 14 - 15 - 16 - /** 17 - * Generic record type, base class for all records or record view objects. 18 - */ 19 - 20 - export class ATProtoRecord { 21 - 22 - /** @param {json} data, @param {json} [extra] */ 23 - constructor(data, extra) { 24 - this.data = data; 25 - Object.assign(this, extra ?? {}); 26 - } 27 - 28 - /** @returns {string} */ 29 - get uri() { 30 - return this.data.uri; 31 - } 32 - 33 - /** @returns {string} */ 34 - get cid() { 35 - return this.data.cid; 36 - } 37 - 38 - /** @returns {string} */ 39 - get rkey() { 40 - return atURI(this.uri).rkey; 41 - } 42 - 43 - /** @returns {string} */ 44 - get type() { 45 - return this.data.$type; 46 - } 47 - } 48 - 49 16 50 17 /** 51 18 * Standard Bluesky post record. ··· 479 446 */ 480 447 481 448 export class DetachedQuotePost extends ATProtoRecord {} 482 - 483 - 484 - /** 485 - * Record representing a feed generator. 486 - */ 487 - 488 - export class FeedGeneratorRecord extends ATProtoRecord { 489 - 490 - /** @param {json} data */ 491 - constructor(data) { 492 - super(data); 493 - this.author = data.creator; 494 - } 495 - 496 - /** @returns {string | undefined} */ 497 - get title() { 498 - return this.data.displayName; 499 - } 500 - 501 - /** @returns {string | undefined} */ 502 - get description() { 503 - return this.data.description; 504 - } 505 - 506 - /** @returns {number} */ 507 - get likeCount() { 508 - return castToInt(this.data.likeCount); 509 - } 510 - 511 - /** @returns {string | undefined} */ 512 - get avatar() { 513 - return this.data.avatar; 514 - } 515 - } 516 - 517 - 518 - /** 519 - * Record representing a user list or moderation list. 520 - */ 521 - 522 - export class UserListRecord extends ATProtoRecord { 523 - 524 - /** @param {json} data */ 525 - constructor(data) { 526 - super(data); 527 - this.author = data.creator; 528 - } 529 - 530 - /** @returns {string | undefined} */ 531 - get title() { 532 - return this.data.name; 533 - } 534 - 535 - /** @returns {string | undefined} */ 536 - get purpose() { 537 - return this.data.purpose; 538 - } 539 - 540 - /** @returns {string | undefined} */ 541 - get description() { 542 - return this.data.description; 543 - } 544 - 545 - /** @returns {string | undefined} */ 546 - get avatar() { 547 - return this.data.avatar; 548 - } 549 - } 550 - 551 - 552 - /** 553 - * Record representing a starter pack. 554 - */ 555 - 556 - export class StarterPackRecord extends ATProtoRecord { 557 - 558 - /** @param {json} data */ 559 - constructor(data) { 560 - super(data); 561 - this.author = data.creator; 562 - } 563 - 564 - /** @returns {string | undefined} */ 565 - get title() { 566 - return this.data.record.name; 567 - } 568 - 569 - /** @returns {string | undefined} */ 570 - get description() { 571 - return this.data.record.description; 572 - } 573 - } 574 - 575 - 576 - /** 577 - * Base class for embed objects. 578 - */ 579 - 580 - export class Embed { 581 - 582 - /** 583 - * More hydrated view of an embed, taken from a full post view (#postView). 584 - * 585 - * @param {json} json, @returns {Embed} 586 - */ 587 - 588 - static parseInlineEmbed(json) { 589 - switch (json.$type) { 590 - case 'app.bsky.embed.record#view': 591 - return new InlineRecordEmbed(json); 592 - 593 - case 'app.bsky.embed.recordWithMedia#view': 594 - return new InlineRecordWithMediaEmbed(json); 595 - 596 - case 'app.bsky.embed.images#view': 597 - return new InlineImageEmbed(json); 598 - 599 - case 'app.bsky.embed.external#view': 600 - return new InlineLinkEmbed(json); 601 - 602 - case 'app.bsky.embed.video#view': 603 - return new InlineVideoEmbed(json); 604 - 605 - default: 606 - if (location.protocol == 'file:') { 607 - throw new PostDataError(`Unexpected embed type: ${json.$type}`); 608 - } else { 609 - console.warn('Unexpected embed type:', json.$type); 610 - return new Embed(json); 611 - } 612 - } 613 - } 614 - 615 - /** 616 - * Raw embed extracted from raw record data of a post. Does not include quoted post contents. 617 - * 618 - * @param {json} json, @returns {Embed} 619 - */ 620 - 621 - static parseRawEmbed(json) { 622 - switch (json.$type) { 623 - case 'app.bsky.embed.record': 624 - return new RawRecordEmbed(json); 625 - 626 - case 'app.bsky.embed.recordWithMedia': 627 - return new RawRecordWithMediaEmbed(json); 628 - 629 - case 'app.bsky.embed.images': 630 - return new RawImageEmbed(json); 631 - 632 - case 'app.bsky.embed.external': 633 - return new RawLinkEmbed(json); 634 - 635 - case 'app.bsky.embed.video': 636 - return new RawVideoEmbed(json); 637 - 638 - default: 639 - if (location.protocol == 'file:') { 640 - throw new PostDataError(`Unexpected embed type: ${json.$type}`); 641 - } else { 642 - console.warn('Unexpected embed type:', json.$type); 643 - return new Embed(json); 644 - } 645 - } 646 - } 647 - 648 - /** @param {json} json */ 649 - constructor(json) { 650 - this.json = json; 651 - } 652 - 653 - /** @returns {string} */ 654 - get type() { 655 - return this.json.$type; 656 - } 657 - } 658 - 659 - export class RawImageEmbed extends Embed { 660 - 661 - /** @param {json} json */ 662 - constructor(json) { 663 - super(json); 664 - this.images = json.images; 665 - } 666 - } 667 - 668 - export class RawLinkEmbed extends Embed { 669 - 670 - /** @param {json} json */ 671 - constructor(json) { 672 - super(json); 673 - 674 - this.url = json.external.uri; 675 - this.title = json.external.title; 676 - this.thumb = json.external.thumb; 677 - } 678 - } 679 - 680 - export class RawVideoEmbed extends Embed { 681 - 682 - /** @param {json} json */ 683 - constructor(json) { 684 - super(json); 685 - this.video = json.video; 686 - } 687 - } 688 - 689 - export class RawRecordEmbed extends Embed { 690 - 691 - /** @param {json} json */ 692 - constructor(json) { 693 - super(json); 694 - this.record = new ATProtoRecord(json.record); 695 - } 696 - } 697 - 698 - export class RawRecordWithMediaEmbed extends Embed { 699 - 700 - /** @param {json} json */ 701 - constructor(json) { 702 - super(json); 703 - this.record = new ATProtoRecord(json.record.record); 704 - this.media = Embed.parseRawEmbed(json.media); 705 - } 706 - } 707 - 708 - export class InlineRecordEmbed extends Embed { 709 - 710 - /** 711 - * app.bsky.embed.record#view 712 - * @param {json} json 713 - */ 714 - constructor(json) { 715 - super(json); 716 - this.post = Post.parseViewRecord(json.record); 717 - } 718 - } 719 - 720 - export class InlineRecordWithMediaEmbed extends Embed { 721 - 722 - /** 723 - * app.bsky.embed.recordWithMedia#view 724 - * @param {json} json 725 - */ 726 - constructor(json) { 727 - super(json); 728 - this.post = Post.parseViewRecord(json.record.record); 729 - this.media = Embed.parseInlineEmbed(json.media); 730 - } 731 - } 732 - 733 - export class InlineLinkEmbed extends Embed { 734 - 735 - /** 736 - * app.bsky.embed.external#view 737 - * @param {json} json 738 - */ 739 - constructor(json) { 740 - super(json); 741 - 742 - this.url = json.external.uri; 743 - this.title = json.external.title; 744 - this.description = json.external.description; 745 - this.thumb = json.external.thumb; 746 - } 747 - } 748 - 749 - export class InlineImageEmbed extends Embed { 750 - 751 - /** 752 - * app.bsky.embed.images#view 753 - * @param {json} json 754 - */ 755 - constructor(json) { 756 - super(json); 757 - this.images = json.images; 758 - } 759 - } 760 - 761 - export class InlineVideoEmbed extends Embed { 762 - 763 - /** 764 - * app.bsky.embed.video#view 765 - * @param {json} json 766 - */ 767 - constructor(json) { 768 - super(json); 769 - this.playlistURL = json.playlist; 770 - this.alt = json.alt; 771 - } 772 - }
+200
src/models/embeds.js
··· 1 + import { ATProtoRecord } from './records.js'; 2 + import { Post, PostDataError } from './posts.js'; 3 + 4 + /** 5 + * Base class for embed objects. 6 + */ 7 + 8 + export class Embed { 9 + 10 + /** 11 + * More hydrated view of an embed, taken from a full post view (#postView). 12 + * 13 + * @param {json} json, @returns {Embed} 14 + */ 15 + 16 + static parseInlineEmbed(json) { 17 + switch (json.$type) { 18 + case 'app.bsky.embed.record#view': 19 + return new InlineRecordEmbed(json); 20 + 21 + case 'app.bsky.embed.recordWithMedia#view': 22 + return new InlineRecordWithMediaEmbed(json); 23 + 24 + case 'app.bsky.embed.images#view': 25 + return new InlineImageEmbed(json); 26 + 27 + case 'app.bsky.embed.external#view': 28 + return new InlineLinkEmbed(json); 29 + 30 + case 'app.bsky.embed.video#view': 31 + return new InlineVideoEmbed(json); 32 + 33 + default: 34 + if (location.protocol == 'file:') { 35 + throw new PostDataError(`Unexpected embed type: ${json.$type}`); 36 + } else { 37 + console.warn('Unexpected embed type:', json.$type); 38 + return new Embed(json); 39 + } 40 + } 41 + } 42 + 43 + /** 44 + * Raw embed extracted from raw record data of a post. Does not include quoted post contents. 45 + * 46 + * @param {json} json, @returns {Embed} 47 + */ 48 + 49 + static parseRawEmbed(json) { 50 + switch (json.$type) { 51 + case 'app.bsky.embed.record': 52 + return new RawRecordEmbed(json); 53 + 54 + case 'app.bsky.embed.recordWithMedia': 55 + return new RawRecordWithMediaEmbed(json); 56 + 57 + case 'app.bsky.embed.images': 58 + return new RawImageEmbed(json); 59 + 60 + case 'app.bsky.embed.external': 61 + return new RawLinkEmbed(json); 62 + 63 + case 'app.bsky.embed.video': 64 + return new RawVideoEmbed(json); 65 + 66 + default: 67 + if (location.protocol == 'file:') { 68 + throw new PostDataError(`Unexpected embed type: ${json.$type}`); 69 + } else { 70 + console.warn('Unexpected embed type:', json.$type); 71 + return new Embed(json); 72 + } 73 + } 74 + } 75 + 76 + /** @param {json} json */ 77 + constructor(json) { 78 + this.json = json; 79 + } 80 + 81 + /** @returns {string} */ 82 + get type() { 83 + return this.json.$type; 84 + } 85 + } 86 + 87 + export class RawImageEmbed extends Embed { 88 + 89 + /** @param {json} json */ 90 + constructor(json) { 91 + super(json); 92 + this.images = json.images; 93 + } 94 + } 95 + 96 + export class RawLinkEmbed extends Embed { 97 + 98 + /** @param {json} json */ 99 + constructor(json) { 100 + super(json); 101 + 102 + this.url = json.external.uri; 103 + this.title = json.external.title; 104 + this.thumb = json.external.thumb; 105 + } 106 + } 107 + 108 + export class RawVideoEmbed extends Embed { 109 + 110 + /** @param {json} json */ 111 + constructor(json) { 112 + super(json); 113 + this.video = json.video; 114 + } 115 + } 116 + 117 + export class RawRecordEmbed extends Embed { 118 + 119 + /** @param {json} json */ 120 + constructor(json) { 121 + super(json); 122 + this.record = new ATProtoRecord(json.record); 123 + } 124 + } 125 + 126 + export class RawRecordWithMediaEmbed extends Embed { 127 + 128 + /** @param {json} json */ 129 + constructor(json) { 130 + super(json); 131 + this.record = new ATProtoRecord(json.record.record); 132 + this.media = Embed.parseRawEmbed(json.media); 133 + } 134 + } 135 + 136 + export class InlineRecordEmbed extends Embed { 137 + 138 + /** 139 + * app.bsky.embed.record#view 140 + * @param {json} json 141 + */ 142 + constructor(json) { 143 + super(json); 144 + this.post = Post.parseViewRecord(json.record); 145 + } 146 + } 147 + 148 + export class InlineRecordWithMediaEmbed extends Embed { 149 + 150 + /** 151 + * app.bsky.embed.recordWithMedia#view 152 + * @param {json} json 153 + */ 154 + constructor(json) { 155 + super(json); 156 + this.post = Post.parseViewRecord(json.record.record); 157 + this.media = Embed.parseInlineEmbed(json.media); 158 + } 159 + } 160 + 161 + export class InlineLinkEmbed extends Embed { 162 + 163 + /** 164 + * app.bsky.embed.external#view 165 + * @param {json} json 166 + */ 167 + constructor(json) { 168 + super(json); 169 + 170 + this.url = json.external.uri; 171 + this.title = json.external.title; 172 + this.description = json.external.description; 173 + this.thumb = json.external.thumb; 174 + } 175 + } 176 + 177 + export class InlineImageEmbed extends Embed { 178 + 179 + /** 180 + * app.bsky.embed.images#view 181 + * @param {json} json 182 + */ 183 + constructor(json) { 184 + super(json); 185 + this.images = json.images; 186 + } 187 + } 188 + 189 + export class InlineVideoEmbed extends Embed { 190 + 191 + /** 192 + * app.bsky.embed.video#view 193 + * @param {json} json 194 + */ 195 + constructor(json) { 196 + super(json); 197 + this.playlistURL = json.playlist; 198 + this.alt = json.alt; 199 + } 200 + }
+126
src/models/records.js
··· 1 + import { atURI, castToInt } from '../utils.js'; 2 + 3 + /** 4 + * Generic record type, base class for all records or record view objects. 5 + */ 6 + 7 + export class ATProtoRecord { 8 + 9 + /** @param {json} data, @param {json} [extra] */ 10 + constructor(data, extra) { 11 + this.data = data; 12 + Object.assign(this, extra ?? {}); 13 + } 14 + 15 + /** @returns {string} */ 16 + get uri() { 17 + return this.data.uri; 18 + } 19 + 20 + /** @returns {string} */ 21 + get cid() { 22 + return this.data.cid; 23 + } 24 + 25 + /** @returns {string} */ 26 + get rkey() { 27 + return atURI(this.uri).rkey; 28 + } 29 + 30 + /** @returns {string} */ 31 + get type() { 32 + return this.data.$type; 33 + } 34 + } 35 + 36 + 37 + /** 38 + * Record representing a feed generator. 39 + */ 40 + 41 + export class FeedGeneratorRecord extends ATProtoRecord { 42 + 43 + /** @param {json} data */ 44 + constructor(data) { 45 + super(data); 46 + this.author = data.creator; 47 + } 48 + 49 + /** @returns {string | undefined} */ 50 + get title() { 51 + return this.data.displayName; 52 + } 53 + 54 + /** @returns {string | undefined} */ 55 + get description() { 56 + return this.data.description; 57 + } 58 + 59 + /** @returns {number} */ 60 + get likeCount() { 61 + return castToInt(this.data.likeCount); 62 + } 63 + 64 + /** @returns {string | undefined} */ 65 + get avatar() { 66 + return this.data.avatar; 67 + } 68 + } 69 + 70 + 71 + /** 72 + * Record representing a user list or moderation list. 73 + */ 74 + 75 + export class UserListRecord extends ATProtoRecord { 76 + 77 + /** @param {json} data */ 78 + constructor(data) { 79 + super(data); 80 + this.author = data.creator; 81 + } 82 + 83 + /** @returns {string | undefined} */ 84 + get title() { 85 + return this.data.name; 86 + } 87 + 88 + /** @returns {string | undefined} */ 89 + get purpose() { 90 + return this.data.purpose; 91 + } 92 + 93 + /** @returns {string | undefined} */ 94 + get description() { 95 + return this.data.description; 96 + } 97 + 98 + /** @returns {string | undefined} */ 99 + get avatar() { 100 + return this.data.avatar; 101 + } 102 + } 103 + 104 + 105 + /** 106 + * Record representing a starter pack. 107 + */ 108 + 109 + export class StarterPackRecord extends ATProtoRecord { 110 + 111 + /** @param {json} data */ 112 + constructor(data) { 113 + super(data); 114 + this.author = data.creator; 115 + } 116 + 117 + /** @returns {string | undefined} */ 118 + get title() { 119 + return this.data.record.name; 120 + } 121 + 122 + /** @returns {string | undefined} */ 123 + get description() { 124 + return this.data.record.description; 125 + } 126 + }
+1 -1
src/notifications_page.js
··· 2 2 import { $id, atURI, linkToPostById, Paginator } from './utils.js'; 3 3 import { $tag } from './utils_ts.js'; 4 4 import { PostComponent } from './post_component.js'; 5 - import { Post } from './models.js'; 5 + import { Post } from './models/posts.js'; 6 6 7 7 export class NotificationsPage { 8 8
+2 -1
src/post_component.js
··· 11 11 } from './utils.js'; 12 12 13 13 import { $tag } from './utils_ts.js'; 14 - import { Post, BlockedPost, MissingPost, DetachedQuotePost, InlineLinkEmbed } from './models.js'; 14 + import { Post, BlockedPost, MissingPost, DetachedQuotePost } from './models/posts.js'; 15 + import { InlineLinkEmbed } from './models/embeds.js'; 15 16 import { APIError } from './api/minisky.js'; 16 17 import { EmbedComponent } from './embed_component.js'; 17 18 import { RichText } from '../lib/rich_text_lite.js';
+1 -1
src/private_search_page.js
··· 1 1 import { $, $id, feedPostTime, Paginator } from './utils.js'; 2 2 import { $tag } from './utils_ts.js'; 3 3 import { PostComponent } from './post_component.js'; 4 - import { Post } from './models.js'; 4 + import { Post } from './models/posts.js'; 5 5 import { BlueskyAPI } from './api/api.js'; 6 6 7 7 export class PrivateSearchPage {
+1 -1
src/skythread.js
··· 6 6 import { getLocation, linkToPostById } from './utils.js'; 7 7 import { BlueskyAPI } from './api/api.js'; 8 8 import { Minisky } from './api/minisky.js'; 9 - import { Post } from './models.js'; 9 + import { Post } from './models/posts.js'; 10 10 import { PostComponent } from './post_component.js'; 11 11 import { Menu } from './menu.js'; 12 12 import { ThreadPage } from './thread_page.js';
+1 -1
src/thread_page.js
··· 1 1 import { $, $id, atURI, linkToPostById, linkToPostThread, showError } from './utils.js'; 2 2 import { $tag } from './utils_ts.js'; 3 - import { Post, BlockedPost, MissingPost } from './models.js'; 3 + import { Post, BlockedPost, MissingPost } from './models/posts.js'; 4 4 import { PostComponent } from './post_component.js'; 5 5 import { setPageTitle, hideLoader } from './skythread.js'; 6 6
+1 -1
src/utils.js
··· 1 1 import DOMPurify from 'dompurify'; 2 2 import { URLError } from './api/api.js'; 3 - import { Post } from './models.js'; 3 + import { Post } from './models/posts.js'; 4 4 5 5 export class AtURI { 6 6 /** @param {string} uri */