diff --git a/actor-mini-profile.css b/actor-mini-profile.css index 5579e0c..dec788c 100644 --- a/actor-mini-profile.css +++ b/actor-mini-profile.css @@ -11,14 +11,6 @@ font: inherit; } -.profile-mini-icon { - width: 28px; - height: 28px; - border-radius: 50%; - background-color: #000000; - margin-right: 6px; -} - .profile-mini-name { color: var(--rdp-text-color); } diff --git a/actor-mini-profile.js b/actor-mini-profile.js index 319319a..e781a9f 100644 --- a/actor-mini-profile.js +++ b/actor-mini-profile.js @@ -50,6 +50,7 @@ class ActorMiniProfile extends HTMLElement { // Actor icon const p2pImage = document.createElement('p2p-image') p2pImage.className = 'profile-mini-icon' + p2pImage.setAttribute('src', iconUrl) p2pImage.alt = actorInfo.name ? actorInfo.name : 'Actor icon' clickableContainer.appendChild(p2pImage) diff --git a/actor-profile.css b/actor-profile.css index 5d9e668..d9f1bb3 100644 --- a/actor-profile.css +++ b/actor-profile.css @@ -13,15 +13,6 @@ margin-bottom: 16px; } -.profile-icon { - width: 50px; - height: 50px; - border-radius: 50%; - background-color: #000000; - margin-right: 8px; - margin-bottom: 8px; -} - .profile-details { display: flex; flex-direction: column; diff --git a/actor-profile.js b/actor-profile.js index 066c31e..c204c76 100644 --- a/actor-profile.js +++ b/actor-profile.js @@ -70,8 +70,8 @@ class ActorProfile extends HTMLElement { } const p2pImage = document.createElement('p2p-image') - p2pImage.setAttribute('src', iconUrl) p2pImage.classList.add('profile-icon') + p2pImage.setAttribute('src', iconUrl) p2pImage.alt = actorInfo.name ? actorInfo.name : 'Actor icon' actorContainer.appendChild(p2pImage) // Append to the actor container diff --git a/db.js b/db.js index daa267b..0530b0e 100644 --- a/db.js +++ b/db.js @@ -42,14 +42,32 @@ export function isP2P (url) { return url.startsWith(HYPER_PREFIX) || url.startsWith(IPNS_PREFIX) } +// Global cache to store protocol reachability +const protocolSupportMap = new Map() + export async function supportsP2P (url) { - try { - const response = await fetch(url) - return response.ok - } catch (error) { - console.log('P2P URL loading failed:', error) - return false + const urlObject = new URL(url) + const protocol = urlObject.protocol + + if (protocolSupportMap.has(protocol)) { + return protocolSupportMap.get(protocol) } + + // Set a promise to avoid multiple simultaneous checks + const supportCheckPromise = fetch(url) + .then(response => { + const supported = response.ok + protocolSupportMap.set(protocol, supported) + return supported + }) + .catch(error => { + console.error(`Error checking protocol support for ${protocol}:`, error) + protocolSupportMap.set(protocol, false) + return false + }) + + protocolSupportMap.set(protocol, supportCheckPromise) + return supportCheckPromise } export function resolveP2PUrl (url) { diff --git a/example/post.html b/example/post.html index 5b99b51..4b3448c 100644 --- a/example/post.html +++ b/example/post.html @@ -10,7 +10,8 @@ - + + - diff --git a/outbox.js b/outbox.js index 0989b90..b47f13f 100644 --- a/outbox.js +++ b/outbox.js @@ -1,4 +1,5 @@ import { db } from './dbInstance.js' +import './post.js' class DistributedOutbox extends HTMLElement { skip = 0 diff --git a/p2p-media.css b/p2p-media.css new file mode 100644 index 0000000..31b506b --- /dev/null +++ b/p2p-media.css @@ -0,0 +1,31 @@ +p2p-image, +p2p-video { + max-width: 100%; +} + +.profile-icon { + width: 50px; + height: 50px; + border-radius: 50%; + background-color: #000000; + margin-right: 8px; + margin-bottom: 8px; + } + + +.profile-mini-icon { + width: 28px; + height: 28px; + border-radius: 50%; + background-color: #000000; + margin-right: 6px; + } + + .actor-icon { + width: 50px; + height: 50px; + border-radius: 50%; + background-color: #000000; + margin-right: 8px; + cursor: pointer; + } \ No newline at end of file diff --git a/p2p-media.js b/p2p-media.js index d6469f8..7933c3d 100644 --- a/p2p-media.js +++ b/p2p-media.js @@ -1,28 +1,43 @@ -import { supportsP2P, resolveP2PUrl } from './db.js' +import { supportsP2P, resolveP2PUrl, isP2P } from './db.js' class P2PImage extends HTMLElement { constructor () { super() + this.attachShadow({ mode: 'open' }) + // Create an img element this.img = document.createElement('img') - this.attachShadow({ mode: 'open' }) - this.shadowRoot.appendChild(this.img) + + // Load the CSS file + const style = document.createElement('style') + fetch('./p2p-media.css').then(response => response.text()).then(css => { + style.textContent = css + this.shadowRoot.append(style, this.img) + }) + this.img.addEventListener('error', () => this.handleError()) } static get observedAttributes () { - return ['src'] + return ['src', 'class'] } connectedCallback () { if (this.hasAttribute('src')) { this.loadImage(this.getAttribute('src')) } + this.syncClass() + this.addEventListener('load-error', e => { + console.log(`Handled fallback for src: ${e.detail.fallbackSrc}`) + }) } attributeChangedCallback (name, oldValue, newValue) { if (name === 'src' && newValue !== oldValue) { this.loadImage(newValue) + this.img.src = newValue // Ensure the inner image src is updated immediately + } else if (name === 'class' && newValue !== oldValue) { + this.syncClass() } } @@ -39,10 +54,20 @@ class P2PImage extends HTMLElement { } } - async handleError () { - const fallbackSrc = resolveP2PUrl(this.getAttribute('src')) - console.log(`Failed to load image. Resolving to gateway URL: ${fallbackSrc}`) - this.img.src = fallbackSrc + syncClass () { + const classes = this.className.split(' ') + this.img.className = classes.join(' ') + } + + handleError () { + const src = this.getAttribute('src') + if (isP2P(src)) { + const fallbackSrc = resolveP2PUrl(src) + console.log(`Failed to load, resolving to gateway URL: ${fallbackSrc}`) + this.img.src = fallbackSrc + this.setAttribute('src', fallbackSrc) // Update the attribute to the fallback source + this.dispatchEvent(new CustomEvent('load-error', { detail: { fallbackSrc } })) + } } } @@ -51,25 +76,40 @@ customElements.define('p2p-image', P2PImage) class P2PVideo extends HTMLElement { constructor () { super() + this.attachShadow({ mode: 'open' }) + + // Create a video element this.video = document.createElement('video') this.video.controls = true - this.attachShadow({ mode: 'open' }) - this.shadowRoot.appendChild(this.video) + + // Load the CSS file + const style = document.createElement('style') + fetch('./p2p-media.css').then(response => response.text()).then(css => { + style.textContent = css + this.shadowRoot.append(style, this.video) + }) } static get observedAttributes () { - return ['src'] + return ['src', 'class'] } connectedCallback () { if (this.hasAttribute('src')) { this.loadVideo(this.getAttribute('src')) } + this.syncClass() + this.addEventListener('load-error', e => { + console.log(`Handled fallback for src: ${e.detail.fallbackSrc}`) + }) } attributeChangedCallback (name, oldValue, newValue) { if (name === 'src' && newValue !== oldValue) { this.loadVideo(newValue) + this.video.src = newValue // Ensure the inner video src is updated immediately + } else if (name === 'class' && newValue !== oldValue) { + this.syncClass() } } @@ -92,10 +132,20 @@ class P2PVideo extends HTMLElement { this.video.appendChild(source) } + syncClass () { + const classes = this.className.split(' ') + this.video.className = classes.join(' ') + } + handleError (source) { - const fallbackSrc = resolveP2PUrl(source.src) - console.log(`Failed to load video source. Resolving to gateway URL: ${fallbackSrc}`) - source.src = fallbackSrc + const src = source.src + if (isP2P(src)) { + const fallbackSrc = resolveP2PUrl(src) + console.log(`Failed to load video source. Resolving to gateway URL: ${fallbackSrc}`) + source.src = fallbackSrc + this.setAttribute('src', fallbackSrc) // Update the attribute to the fallback source + this.dispatchEvent(new CustomEvent('load-error', { detail: { fallbackSrc } })) + } } } diff --git a/post.css b/post.css index f788527..339098a 100644 --- a/post.css +++ b/post.css @@ -32,15 +32,6 @@ align-items: center; } -.actor-icon { - width: 50px; - height: 50px; - border-radius: 50%; - background-color: #000000; - margin-right: 8px; - cursor: pointer; -} - .actor-details { display: flex; flex-direction: column; diff --git a/post.html b/post.html index d3f2451..fe4fa5d 100644 --- a/post.html +++ b/post.html @@ -33,7 +33,6 @@ - diff --git a/post.js b/post.js index 60aaf9f..3118e5d 100644 --- a/post.js +++ b/post.js @@ -1,7 +1,7 @@ /* global customElements, HTMLElement */ import DOMPurify from './dependencies/dompurify/purify.js' import { db } from './dbInstance.js' -import { resolveP2PUrl, isP2P } from './db.js' +import './p2p-media.js' function formatDate (dateString) { const options = { year: 'numeric', month: 'short', day: 'numeric' } @@ -40,42 +40,41 @@ function timeSince (dateString) { return Math.floor(seconds) + 's' } -function insertImagesAndVideos (content) { +async function insertImagesAndVideos (content) { const parser = new DOMParser() const contentDOM = parser.parseFromString(content, 'text/html') - // Replace all tags with tags - contentDOM.querySelectorAll('img').forEach(img => { + const imgPromises = Array.from(contentDOM.querySelectorAll('img')).map(async (img) => { const originalSrc = img.getAttribute('src') - console.log(`Original img src: ${originalSrc}`) const p2pImg = document.createElement('p2p-image') p2pImg.setAttribute('src', originalSrc) + + p2pImg.addEventListener('load-error', (event) => { + console.log(`Error loading image at ${originalSrc}, fallback to ${event.detail.fallbackSrc}`) + p2pImg.setAttribute('src', event.detail.fallbackSrc) + console.log('Updated p2p-image src after fallback:', p2pImg.getAttribute('src')) + }) + img.parentNode.replaceChild(p2pImg, img) - console.log(`Replaced img with p2p-image having src: ${p2pImg.getAttribute('src')}`) + console.log('Replaced img with p2p-image having initial src:', p2pImg.getAttribute('src')) }) - // Replace all