Thread viewer for Bluesky
1class AtURI {
2 /** @param {string} uri */
3 constructor(uri) {
4 if (!uri.startsWith('at://')) {
5 throw new URLError(`Not an at:// URI: ${uri}`);
6 }
7
8 let parts = uri.split('/');
9
10 if (parts.length != 5) {
11 throw new URLError(`Invalid at:// URI: ${uri}`);
12 }
13
14 this.repo = parts[2];
15 this.collection = parts[3];
16 this.rkey = parts[4];
17 }
18}
19
20/**
21 * @template T
22 * @param {string} tag
23 * @param {string | object} params
24 * @param {new (...args: any[]) => T} type
25 * @returns {T}
26 */
27
28function $tag(tag, params, type) {
29 let element;
30 let parts = tag.split('.');
31
32 if (parts.length > 1) {
33 let tagName = parts[0];
34 element = document.createElement(tagName);
35 element.className = parts.slice(1).join(' ');
36 } else {
37 element = document.createElement(tag);
38 }
39
40 if (typeof params === 'string') {
41 element.className = element.className + ' ' + params;
42 } else if (params) {
43 for (let key in params) {
44 if (key == 'text') {
45 element.innerText = params[key];
46 } else if (key == 'html') {
47 element.innerHTML = params[key];
48 } else {
49 element[key] = params[key];
50 }
51 }
52 }
53
54 return /** @type {T} */ (element);
55}
56
57/**
58 * @template {HTMLElement} T
59 * @param {string} name
60 * @param {new (...args: any[]) => T} [type]
61 * @returns {T}
62 */
63
64function $id(name, type) {
65 return /** @type {T} */ (document.getElementById(name));
66}
67
68/**
69 * @template {HTMLElement} T
70 * @param {Node | EventTarget | null} element
71 * @param {new (...args: any[]) => T} [type]
72 * @returns {T}
73 */
74
75function $(element, type) {
76 return /** @type {T} */ (element);
77}
78
79/** @param {string} uri, @returns {AtURI} */
80
81function atURI(uri) {
82 return new AtURI(uri);
83}
84
85function castToInt(value) {
86 if (value === undefined || value === null || typeof value == "number") {
87 return value;
88 } else {
89 return parseInt(value, 10);
90 }
91}
92
93/** @param {string} html, @returns {string} */
94
95function escapeHTML(html) {
96 return html.replace(/&/g, '&')
97 .replace(/</g, '<')
98 .replace(/>/g,'>');
99}
100
101/** @param {string} html, @returns {string} */
102
103function sanitizeHTML(html) {
104 return DOMPurify.sanitize(html, {
105 ALLOWED_TAGS: [
106 'a', 'b', 'blockquote', 'br', 'code', 'dd', 'del', 'div', 'dl', 'dt', 'em', 'font',
107 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'li', 'ol', 'p', 'q', 'pre', 's', 'span', 'strong',
108 'sub', 'sup', 'u', 'wbr', '#text'
109 ],
110 ALLOWED_ATTR: [
111 'align', 'alt', 'class', 'clear', 'color', 'dir', 'href', 'lang', 'rel', 'title', 'translate'
112 ]
113 });
114}
115
116/** @returns {string} */
117
118function getLocation() {
119 return location.origin + location.pathname;
120}
121
122/** @param {object} error */
123
124function showError(error) {
125 console.log(error);
126 alert(error);
127}
128
129/** @param {Date} date1, @param {Date} date2, @returns {boolean} */
130
131function sameDay(date1, date2) {
132 return (
133 date1.getDate() == date2.getDate() &&
134 date1.getMonth() == date2.getMonth() &&
135 date1.getFullYear() == date2.getFullYear()
136 );
137}
138
139/** @param {Post} post, @returns {string} */
140
141function linkToPostThread(post) {
142 return linkToPostById(post.author.handle, post.rkey);
143}
144
145/** @param {string} handle, @param {string} postId, @returns {string} */
146
147function linkToPostById(handle, postId) {
148 let url = new URL(getLocation());
149 url.searchParams.set('author', handle);
150 url.searchParams.set('post', postId);
151 return url.toString();
152}