// The existingPosts array is used to track already displayed posts let existingPosts = []; // getUrlParameter helps to fetch URL parameters function getUrlParameter(name) { name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); var results = regex.exec(location.search); console.log(results) return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); } // secondsAgo calculates how many seconds have passed since the provided date const secondsAgo = date => Math.floor((new Date() - date) / 1000); // timeAgo formats the time elapsed in a human readable format const timeAgo = function(seconds) { // An array of intervals for years, months, days, hours, and minutes. const intervals = [ { limit: 31536000, singular: 'Jahre', plural: 'Jahren' }, { limit: 2592000, singular: 'Monat', plural: 'Monaten' }, { limit: 86400, singular: 'Tag', plural: 'Tagen' }, { limit: 3600, singular: 'Stunde', plural: 'Stunden' }, { limit: 60, singular: 'Minute', plural: 'Minuten' } ]; // Loop through the intervals to find which one is the best fit. for (let interval of intervals) { if (seconds >= interval.limit) { let amount = Math.floor(seconds / interval.limit); let text; if (amount !== 1) { text = interval.plural; } else { text = interval.singular; } return `Vor ${amount} ${text}`; } } let text = "Sekunde"; let amount = Math.floor(seconds); if (amount !== 1) { text += "n"; } return `Vor ${amount} ${text}`; }; let includeReplies; // max post age in seconds let maxAge; // below times are in milliseconds // duration for slide animations let duration; // refresh rate let refresh; // extra cards text let extraCards; // toggle Carousel let withCarousel=false; // fetchConfig fetches the configuration from the config.json file const fetchConfig = async function() { try { const config = await $.getJSON('config.json'); $('#navbar-brand').text(config.navbarBrandText); $('.navbar').css('background-color', config.navbarColor); includeReplies = config.includeReplies; maxAge = config.maxAge; duration = config.duration * 1000; refresh = config.refreshDuration * 1000; extraCards = config.extraCards; return config.defaultServerUrl; } catch (error) { console.log("Error loading config.json:", error); $('#navbar-brand').text("Netzbegrünung Mastowall - gruene.social"); $('.navbar').css('background-color', "#008939"); includeReplies = true; maxAge = 60 * 60 * 24 * 7; duration = 10000; refresh = 30000; extraCards = [ "", "", "", "" ]; return "https://gruene.social"; } } // fetchPosts fetches posts from the server using the given hashtag const fetchPosts = async function(serverUrl, hashtag) { try { const posts = await $.get(`${serverUrl}/api/v1/timelines/tag/${hashtag}?limit=20`); return posts; } catch (error) { console.error(`Error loading posts for hashtag #${hashtag}:`, error); } }; // updateTimesOnPage updates the time information displayed for each post const updateTimesOnPage = function() { $('.card-text a.time').each(function() { const timeValue = $(this).attr('data-time'); if (timeValue === '') return; const date = new Date(timeValue); const newTimeAgo = timeAgo(secondsAgo(date)); $(this).text(newTimeAgo); }); }; // replace certain emojies in some text with images const replaceEmojies = (text, emojis) => { emojis.forEach(emoji => { text = text.replaceAll(`:${emoji.shortcode}:`, ``); }); return text; }; // displayPost creates and displays a post const displayPost = function(post) { if (existingPosts.includes(post.id) || (!includeReplies && post.in_reply_to_id !== null)) return 0; existingPosts.push(post.id); let cardHTML = `

${replaceEmojies(DOMPurify.sanitize(post.account.display_name), post.account.emojis)} @${DOMPurify.sanitize(post.account.acct)}

${post.media_attachments[0] ? (post.media_attachments[0].url.endsWith('.mp4') ? `` : ``) : ''}

${replaceEmojies(DOMPurify.sanitize(post.content), post.emojis)}

${post.spoiler_text ? `

${DOMPurify.sanitize(post.spoiler_text)}

` : ''}

${timeAgo(secondsAgo(new Date(post.created_at)))}

`; let $card = $(cardHTML); $('#wall').prepend($card); $('.masonry-grid').masonry('prepended', $card); return 1; }; const processPosts = function(posts) { posts = posts.filter((post) => { return secondsAgo(new Date(post.created_at)) < maxAge && post.content.indexOf("nitter.") === -1 && post.spoiler_text === "" }); return posts; }; // updateWall displays all posts const updateWall = function(posts) { if (!posts || posts.length === 0) return; posts = processPosts(posts); posts.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); let ret = 0 posts.forEach(post => ret += displayPost(post)); $('.masonry-grid').masonry('layout'); return ret; }; // updateCarousel const updateCarousel = function(slides, posts) { if (!posts || posts.length === 0) return; posts = processPosts(posts); posts.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // remove slides in carousel slides.innerHTML = ""; var newHTML = ` ` newHTML += `