/* Beep — Twitter-style microblog frontend */ (function () { 'use strict'; var config = window.BeepConfig || {}; var limit = (config.charLimit || 280) | 0; document.addEventListener('DOMContentLoaded', init); function init() { var feeds = document.querySelectorAll('[data-beep-feed]'); Array.prototype.forEach.call(feeds, initFeed); // Delegate on document so dynamically injected content (thread modal, // GIF picker, toasts) is handled too. document.addEventListener('click', onClick); document.addEventListener('input', onInput); document.addEventListener('keydown', onKeydown); document.addEventListener('keydown', onEscape); } var feedState = { offset: 0, loading: false, hasMore: true, currentSort: 'latest' }; function initFeed(feed) { var compose = feed.querySelector('[data-beep-compose]'); if (compose) initCompose(compose, feed); initSortTabs(feed); initMedia(compose, feed); initInfiniteScroll(feed); } /* ---------- Infinite scroll ---------- */ function initInfiniteScroll(feed) { var sentinel = document.createElement('div'); sentinel.className = 'beep-scroll-sentinel'; sentinel.setAttribute('data-beep-scroll-sentinel', ''); var list = feed.querySelector('[data-beep-list]'); if (list) list.appendChild(sentinel); if (!('IntersectionObserver' in window)) return; var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting && !feedState.loading && feedState.hasMore) { loadMore(feed); } }, { rootMargin: '400px' }); observer.observe(sentinel); feed._beepScrollObserver = observer; } function loadMore(feed) { feedState.loading = true; var sentinel = feed.querySelector('[data-beep-scroll-sentinel]'); if (sentinel) sentinel.innerHTML = '
No beeps yet.
Be the first to share something!Type to search GIFs...
'; if (searchInput) searchInput.focus(); } function closeGifPicker() { var modal = document.querySelector('[data-beep-gif-modal]'); if (modal) modal.hidden = true; } function searchGifs(query, compose) { if (!query || !query.trim()) return; var apiKey = config.giphyKey || ''; if (!apiKey) { console.warn('Beep: No Giphy API key configured.'); return; } var modal = document.querySelector('[data-beep-gif-modal]'); var grid = modal && modal.querySelector('[data-beep-gif-grid]'); if (!grid) return; grid.innerHTML = 'Searching...
'; var url = 'https://api.giphy.com/v1/gifs/search?api_key=' + encodeURIComponent(apiKey) + '&q=' + encodeURIComponent(query) + '&limit=30&rating=g'; fetch(url) .then(function(res) { return res.json(); }) .then(function(data) { if (!data.data || !data.data.length) { grid.innerHTML = 'No GIFs found.
'; return; } grid.innerHTML = ''; data.data.forEach(function(gif) { var imgUrl = gif.images && gif.images.fixed_width_small && gif.images.fixed_width_small.url; if (!imgUrl) return; var item = document.createElement('div'); item.className = 'beep-gif-item'; var altText = escapeHtml(gif.title || ''); item.innerHTML = 'Search failed.
'; }); } function selectGif(gif, compose) { if (!compose) return; var url = gif.images && gif.images.fixed_width && gif.images.fixed_width.url; if (!url) return; compose._beepGifUrl = url; var preview = compose.querySelector('[data-beep-gif-preview]'); if (preview) { preview.innerHTML = '