Skip to content

Commit 3acf234

Browse files
Merge pull request #699: Add Community Stories feature
2 parents 0601012 + e48e4b7 commit 3acf234

4 files changed

Lines changed: 813 additions & 36 deletions

File tree

‎frontend/js/community-stories.js‎

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Community Stories Logic
3+
* Added by devanshi14malhotra
4+
*/
5+
6+
document.addEventListener('DOMContentLoaded', () => {
7+
// DOM Elements
8+
const openFormBtn = document.getElementById('openFormBtn');
9+
const closeFormBtn = document.getElementById('closeFormBtn');
10+
const cancelBtn = document.getElementById('cancelBtn');
11+
const publishBtn = document.getElementById('publishBtn');
12+
const storyFormBackdrop = document.getElementById('storyFormBackdrop');
13+
14+
const storyTitle = document.getElementById('storyTitle');
15+
const storyAuthor = document.getElementById('storyAuthor');
16+
const storyGenre = document.getElementById('storyGenre');
17+
const storyContent = document.getElementById('storyContent');
18+
const charCount = document.getElementById('charCount');
19+
20+
const storiesGrid = document.getElementById('storiesGrid');
21+
const filterBtns = document.querySelectorAll('.filter-btn');
22+
23+
const storyReadBackdrop = document.getElementById('storyReadBackdrop');
24+
const closeReadBtn = document.getElementById('closeReadBtn');
25+
const readGenre = document.getElementById('readGenre');
26+
const readTitle = document.getElementById('readTitle');
27+
const readAuthor = document.getElementById('readAuthor');
28+
const readContent = document.getElementById('readContent');
29+
30+
const toast = document.getElementById('toast');
31+
32+
// Seed default stories if none exist
33+
const defaultStories = [
34+
{
35+
id: 1,
36+
title: "The Whispers of the Old Library",
37+
author: "Elara Vance",
38+
genre: "mystery",
39+
content: "The dust motes danced in the pale moonlight that filtered through the stained glass. I had been told the restricted section was locked, but the heavy oak door yielded with a soft creak. Inside, the books weren't just old; they felt alive. As I reached for a leather-bound tome on the top shelf, a whisper echoed through the silent aisles. 'You're not supposed to be here.' I turned around, but the corridor was completely empty. Only the faint smell of ozone and old parchment remained.",
40+
date: new Date().toLocaleDateString()
41+
},
42+
{
43+
id: 2,
44+
title: "Stardust and Tea Leaves",
45+
author: "Julian Moon",
46+
genre: "fantasy",
47+
content: "In the cafe at the edge of the universe, they serve tea brewed with actual stardust. It tastes like nostalgia and peppermint. I sat at my usual table by the viewing port, watching a nebula bloom in vibrant hues of purple and gold. The barista, an entity composed entirely of refracted light, slid my cup across the counter. 'Rough cycle?' it asked. I nodded, wrapping my hands around the warm ceramic. Here, at the end of everything, it was nice to just sit and drink tea.",
48+
date: new Date().toLocaleDateString()
49+
}
50+
];
51+
52+
let stories = JSON.parse(localStorage.getItem('bibliodrift_community_stories')) || defaultStories;
53+
54+
// Save to local storage
55+
const saveStories = () => {
56+
localStorage.setItem('bibliodrift_community_stories', JSON.stringify(stories));
57+
};
58+
59+
// Render Stories
60+
const renderStories = (filter = 'all') => {
61+
storiesGrid.innerHTML = '';
62+
63+
const filteredStories = filter === 'all'
64+
? stories
65+
: stories.filter(s => s.genre === filter);
66+
67+
if (filteredStories.length === 0) {
68+
storiesGrid.innerHTML = `
69+
<div class="stories-empty">
70+
<i class="fa-solid fa-book-open-reader"></i>
71+
<p>No stories found in this genre yet. Be the first to write one!</p>
72+
</div>
73+
`;
74+
return;
75+
}
76+
77+
// Sort by newest first (reverse order)
78+
[...filteredStories].reverse().forEach(story => {
79+
const card = document.createElement('div');
80+
card.className = 'story-card';
81+
card.innerHTML = `
82+
<div class="story-genre-badge">${story.genre}</div>
83+
<h3>${story.title}</h3>
84+
<div class="story-author-line">
85+
<i class="fa-solid fa-pen-nib" style="font-size: 0.8rem;"></i>
86+
${story.author}
87+
</div>
88+
<div class="story-preview">${story.content}</div>
89+
<div class="story-meta">
90+
<div class="story-date"><i class="fa-regular fa-clock"></i> ${story.date}</div>
91+
<button class="story-read-btn">Read <i class="fa-solid fa-arrow-right-long"></i></button>
92+
</div>
93+
`;
94+
95+
card.addEventListener('click', () => openReadModal(story));
96+
storiesGrid.appendChild(card);
97+
});
98+
};
99+
100+
// Modal Logic
101+
const openFormModal = () => {
102+
storyFormBackdrop.classList.add('active');
103+
document.body.style.overflow = 'hidden';
104+
};
105+
106+
const closeFormModal = () => {
107+
storyFormBackdrop.classList.remove('active');
108+
document.body.style.overflow = '';
109+
// Reset form
110+
storyTitle.value = '';
111+
storyAuthor.value = '';
112+
storyGenre.value = '';
113+
storyContent.value = '';
114+
charCount.textContent = '0';
115+
};
116+
117+
const openReadModal = (story) => {
118+
readGenre.textContent = story.genre;
119+
readTitle.textContent = story.title;
120+
readAuthor.textContent = `by ${story.author}`;
121+
readContent.textContent = story.content;
122+
123+
storyReadBackdrop.classList.add('active');
124+
document.body.style.overflow = 'hidden';
125+
};
126+
127+
const closeReadModal = () => {
128+
storyReadBackdrop.classList.remove('active');
129+
document.body.style.overflow = '';
130+
};
131+
132+
// Event Listeners
133+
openFormBtn?.addEventListener('click', openFormModal);
134+
closeFormBtn?.addEventListener('click', closeFormModal);
135+
cancelBtn?.addEventListener('click', closeFormModal);
136+
closeReadBtn?.addEventListener('click', closeReadModal);
137+
138+
// Close on backdrop click
139+
storyFormBackdrop?.addEventListener('click', (e) => {
140+
if (e.target === storyFormBackdrop) closeFormModal();
141+
});
142+
storyReadBackdrop?.addEventListener('click', (e) => {
143+
if (e.target === storyReadBackdrop) closeReadModal();
144+
});
145+
146+
// Character counter
147+
storyContent?.addEventListener('input', () => {
148+
charCount.textContent = storyContent.value.length;
149+
});
150+
151+
// Publish Story
152+
publishBtn?.addEventListener('click', () => {
153+
const title = storyTitle.value.trim();
154+
const author = storyAuthor.value.trim();
155+
const genre = storyGenre.value;
156+
const content = storyContent.value.trim();
157+
158+
if (!title || !author || !genre || !content) {
159+
alert('Please fill out all fields before publishing.');
160+
return;
161+
}
162+
163+
const newStory = {
164+
id: Date.now(),
165+
title,
166+
author,
167+
genre,
168+
content,
169+
date: new Date().toLocaleDateString()
170+
};
171+
172+
stories.push(newStory);
173+
saveStories();
174+
renderStories(document.querySelector('.filter-btn.active').dataset.genre);
175+
176+
closeFormModal();
177+
178+
// Show toast
179+
toast.classList.add('show');
180+
setTimeout(() => toast.classList.remove('show'), 3000);
181+
});
182+
183+
// Filter Logic
184+
filterBtns.forEach(btn => {
185+
btn.addEventListener('click', () => {
186+
filterBtns.forEach(b => b.classList.remove('active'));
187+
btn.classList.add('active');
188+
renderStories(btn.dataset.genre);
189+
});
190+
});
191+
192+
// Initial render
193+
renderStories();
194+
});

‎frontend/pages/auth.html‎

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -346,43 +346,47 @@
346346
</head>
347347

348348
<body class="auth-page">
349-
<header>
350-
<a href="index.html" class="logo">
351-
<img style="height: 40px" src="../assets/images/biblioDrift_favicon.png" alt="BiblioDrift Logo"> BiblioDrift
352-
</a>
353-
<div class="header-controls">
354-
<div class="search-bar">
355-
<input type="text" id="searchInput" class="search-input" placeholder="Search for a feeling...">
356-
<i class="fa-solid fa-search search-icon" id="searchIcon"></i>
357-
</div>
358-
<nav class="nav-links">
359-
<div class="tooltip">
360-
<a href="index.html" >Discovery</a>
361-
<span class="tooltip-text"><i class="fa-solid fa-wand-magic-sparkles"></i> Explore books</span>
362-
</div>
363-
<div class="tooltip">
364-
<a href="vault.html" >My Vault</a>
365-
<span class="tooltip-text"><i class="fa-solid fa-vault"></i> My Vault</span>
366-
</div>
367-
<div class="tooltip">
368-
<a href="library.html" >My Library</a>
369-
<span class="tooltip-text"><i class="fa-solid fa-book"></i> My Library</span>
370-
</div>
371-
<div class="tooltip">
372-
<a href="chat.html" >Chat</a>
373-
<span class="tooltip-text"><i class="fa-solid fa-comments"></i> Chat</span>
374-
</div>
375-
<!-- DYNAMIC AUTH LINK -->
376-
<div class="tooltip">
377-
<a href="auth.html" id="navAuthLink" class="active">Sign In</a>
378-
<span class="tooltip-text" id="navAuthTooltip"><i class="fa-solid fa-key"></i> Access account</span>
349+
<header>
350+
<a href="index.html" class="logo">
351+
<img style="height: 40px" src="../assets/images/biblioDrift_favicon.png" alt="BiblioDrift Logo"> BiblioDrift
352+
</a>
353+
<div class="header-controls">
354+
<div class="search-bar">
355+
<input type="text" id="searchInput" class="search-input" placeholder="Search for a feeling...">
356+
<i class="fa-solid fa-search search-icon" id="searchIcon"></i>
357+
</div>
358+
<nav class="nav-links">
359+
<div class="tooltip">
360+
<a href="index.html">Discovery</a>
361+
<span class="tooltip-text"><i class="fa-solid fa-wand-magic-sparkles"></i> Explore books</span>
362+
</div>
363+
<div class="tooltip">
364+
<a href="vault.html">My Vault</a>
365+
<span class="tooltip-text"><i class="fa-solid fa-vault"></i> My Vault</span>
366+
</div>
367+
<div class="tooltip">
368+
<a href="library.html">My Library</a>
369+
<span class="tooltip-text"><i class="fa-solid fa-book"></i> My Library</span>
370+
</div>
371+
<div class="tooltip">
372+
<a href="community-stories.html">Community</a>
373+
<span class="tooltip-text"><i class="fa-solid fa-pen-nib"></i> Community Stories</span>
374+
</div>
375+
<div class="tooltip">
376+
<a href="chat.html">Chat</a>
377+
<span class="tooltip-text"><i class="fa-solid fa-comments"></i> Chat</span>
378+
</div>
379+
<!-- DYNAMIC AUTH LINK -->
380+
<div class="tooltip">
381+
<a href="auth.html" id="navAuthLink" class="active">Sign In</a>
382+
<span class="tooltip-text" id="navAuthTooltip"><i class="fa-solid fa-key"></i> Access account</span>
383+
</div>
384+
</nav>
385+
<button id="themeToggle" class="btn-icon" title="Toggle Theme">
386+
<i class="fa-solid fa-moon"></i>
387+
</button>
379388
</div>
380-
</nav>
381-
<button id="themeToggle" class="btn-icon" title="Toggle Theme">
382-
<i class="fa-solid fa-moon"></i>
383-
</button>
384-
</div>
385-
</header>
389+
</header>
386390

387391
<main class="auth-main">
388392
<section class="auth-shell">

0 commit comments

Comments
 (0)