diff --git a/static/js/discourse-api.js b/static/js/discourse-api.js new file mode 100644 index 00000000..f7f10db4 --- /dev/null +++ b/static/js/discourse-api.js @@ -0,0 +1,196 @@ +/** + * Discourse API Integration for enviPath Community + * Handles fetching topics from the Discourse forum API + */ + +class DiscourseAPI { + constructor() { + this.baseUrl = 'https://community.envipath.org'; + this.categoryId = 10; // Announcements category + this.limit = 3; // Number of topics to fetch + } + + /** + * Fetch topics from Discourse API + * @param {number} limit - Number of topics to fetch + * @returns {Promise} Array of topic objects + */ + async fetchTopics(limit = this.limit) { + try { + const url = `${this.baseUrl}/c/announcements/${this.categoryId}.json`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return this.processTopics(data.topic_list.topics, limit); + } catch (error) { + console.error('Error fetching Discourse topics:', error); + return this.getFallbackTopics(); + } + } + + /** + * Process raw Discourse topics into standardized format + * @param {Array} topics - Raw topics from Discourse API + * @param {number} limit - Number of topics to return + * @returns {Array} Processed topics + */ + processTopics(topics, limit) { + return topics + .slice(0, limit) + .map(topic => ({ + id: topic.id, + title: topic.title, + excerpt: this.extractExcerpt(topic.excerpt), + url: `${this.baseUrl}/t/${topic.slug}/${topic.id}`, + replies: topic.reply_count, + views: topic.views, + created_at: topic.created_at, + category: 'Announcements', + category_id: this.categoryId, + author: topic.last_poster_username, + author_avatar: this.getAvatarUrl(topic.last_poster_username) + })) + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // Latest first + } + + /** + * Extract excerpt from topic content + * @param {string} excerpt - Raw excerpt from Discourse + * @returns {string} Cleaned excerpt + */ + extractExcerpt(excerpt) { + if (!excerpt) return 'Click to read more...'; + + // Remove HTML tags and clean up + return excerpt + .replace(/<[^>]*>/g, '') // Remove HTML tags + .replace(/ /g, ' ') // Replace   with spaces + .replace(/&/g, '&') // Replace & with & + .replace(/</g, '<') // Replace < with < + .replace(/>/g, '>') // Replace > with > + .trim() + .substring(0, 200) + '...'; // Limit length + } + + /** + * Get avatar URL for user + * @param {string} username - Username + * @returns {string} Avatar URL + */ + getAvatarUrl(username) { + if (!username) return `${this.baseUrl}/letter_avatar_proxy/v4/letter/u/1.png`; + return `${this.baseUrl}/user_avatar/${this.baseUrl.replace('https://', '')}/${username}/40/1_1.png`; + } + + /** + * Get fallback topics when API fails + * @returns {Array} Fallback topics + */ + getFallbackTopics() { + return [ + { + id: 110, + title: "enviPath Beta Update: Major Improvements to Prediction, Analysis & Collaboration!", + excerpt: "We're excited to announce major updates to the enviPath beta platform! This release includes significant improvements to our prediction algorithms, enhanced analysis tools, and new collaboration features that will make environmental biotransformation research more accessible and efficient.", + url: "https://community.envipath.org/t/envipath-beta-update-major-improvements-to-prediction-analysis-collaboration/110", + replies: 0, + views: 16, + created_at: "2025-09-23T00:00:00Z", + category: "Announcements", + category_id: 10, + author: "wicker", + author_avatar: "https://community.envipath.org/user_avatar/community.envipath.org/wicker/40/1_1.png" + }, + { + id: 109, + title: "enviPath License", + excerpt: "Information about the enviPath license and terms of use for the platform. Learn about our licensing model and how it affects your research and commercial use of enviPath data and predictions.", + url: "https://community.envipath.org/t/envipath-license/109", + replies: 0, + views: 13, + created_at: "2025-09-22T00:00:00Z", + category: "Announcements", + category_id: 10, + author: "wicker", + author_avatar: "https://community.envipath.org/user_avatar/community.envipath.org/wicker/40/1_1.png" + }, + { + id: 95, + title: "Apply to join our Closed Beta", + excerpt: "We're opening applications for our closed beta program! Join early adopters in testing new features and helping shape the future of environmental biotransformation prediction. Apply now to get early access to cutting-edge tools and provide valuable feedback.", + url: "https://community.envipath.org/t/apply-to-join-our-closed-beta/95", + replies: 0, + views: 69, + created_at: "2025-07-23T00:00:00Z", + category: "Announcements", + category_id: 10, + author: "wicker", + author_avatar: "https://community.envipath.org/user_avatar/community.envipath.org/wicker/40/1_1.png" + } + ]; + } + + /** + * Format date for display + * @param {string} dateString - ISO date string + * @returns {string} Formatted date + */ + formatDate(dateString) { + const date = new Date(dateString); + return date.toLocaleDateString(); + } + + + /** + * Load topics and call render function + * @param {string} containerId - ID of container element + * @param {Function} renderCallback - Function to render topics + */ + async loadTopics(containerId = 'community-news-container', renderCallback = null) { + const container = document.getElementById(containerId); + if (!container) { + console.error(`Container with ID '${containerId}' not found`); + return; + } + + // Hide loading spinner + const loading = document.getElementById('loading'); + if (loading) { + loading.style.display = 'none'; + } + + try { + const topics = await this.fetchTopics(); + + if (renderCallback && typeof renderCallback === 'function') { + renderCallback(topics); + } else { + // Default rendering - just log topics + console.log('Topics loaded:', topics); + } + } catch (error) { + console.error('Error loading topics:', error); + container.innerHTML = '

No updates found. Head over to the community to see the latest discussions.

'; + } + } +} + +// Export for use in other scripts +window.DiscourseAPI = DiscourseAPI; + +// Auto-initialize if container exists +document.addEventListener('DOMContentLoaded', function() { + if (document.getElementById('community-news-container')) { + const discourseAPI = new DiscourseAPI(); + discourseAPI.loadTopics('community-news-container', function(topics) { + // This will be handled by the template's render function + if (window.renderDiscourseTopics) { + window.renderDiscourseTopics(topics); + } + }); + } +});