class GuestbookForm extends HTMLElement { static get observedAttributes() { return ['guestbook']; } constructor() { super(); this.attachShadow({ mode: 'open' }); this._guestbook = this.getAttribute('guestbook') || ''; this._postUrl = `${this._guestbook}/comments/create/remote` this.render(); } attributeChangedCallback(name, oldValue, newValue) { if (name === 'guestbook') { this._guestbook = newValue; this.render(); } } render() { this.shadowRoot.innerHTML = `
`; } } class CommentList extends HTMLElement { static get observedAttributes() { return ['guestbook']; } constructor() { super(); this.attachShadow({ mode: 'open' }); this.comments = []; this.loading = false; this.error = null; } connectedCallback() { if (this.hasAttribute('guestbook')) { this.fetchComments(); } } attributeChangedCallback(name, oldValue, newValue) { if (name === 'guestbook' && oldValue !== newValue) { this.fetchComments(); } } async fetchComments() { const guestbook = this.getAttribute('guestbook'); if (!guestbook) return; this.loading = true; this.error = null; this.render(); const commentsUrl = `${guestbook}/comments` try { const response = await fetch(commentsUrl); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); const data = await response.json(); this.comments = Array.isArray(data) ? data : []; this.loading = false; this.render(); } catch (err) { this.error = err.message; this.loading = false; this.render(); } } formatDate(isoString) { if (!isoString) return ''; const date = new Date(isoString); return date.toLocaleString(); } render() { this.shadowRoot.innerHTML = `