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, '&amp;') 97 .replace(/</g, '&lt;') 98 .replace(/>/g,'&gt;'); 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}