Initial commit.

This commit is contained in:
2025-12-04 20:29:35 +01:00
commit ae789a5b40
87 changed files with 9798 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
/**
* @fileoverview Collabs Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Collaboration projects page
* @keywords collabs, page, projects, collaboration, feed
*/
import { BaseComponent } from '../components/base-component.js';
class CollabsPage extends BaseComponent {
init() {
this.render();
}
render() {
this.addClass('page', 'collabs-page');
this.setHtml(`
<header class="page-header">
<h1>Collabs</h1>
</header>
<rant-feed sort="recent" feed-type="collabs"></rant-feed>
`);
}
refresh() {
const feed = this.$('rant-feed');
if (feed) {
feed.refresh();
}
}
}
customElements.define('collabs-page', CollabsPage);
export { CollabsPage };
+48
View File
@@ -0,0 +1,48 @@
/**
* @fileoverview Home Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Main feed page displaying latest rants
* @keywords home, page, feed, main, landing
*/
import { BaseComponent } from '../components/base-component.js';
class HomePage extends BaseComponent {
static get observedAttributes() {
return ['sort'];
}
init() {
this.render();
}
render() {
const sort = this.getAttr('sort') || 'recent';
this.addClass('page', 'home-page');
this.setHtml(`
<rant-feed sort="${sort}" feed-type="rants"></rant-feed>
`);
}
onAttributeChanged(name, oldValue, newValue) {
if (name === 'sort') {
const feed = this.$('rant-feed');
if (feed) {
feed.setSort(newValue);
}
}
}
refresh() {
const feed = this.$('rant-feed');
if (feed) {
feed.refresh();
}
}
}
customElements.define('home-page', HomePage);
export { HomePage };
+93
View File
@@ -0,0 +1,93 @@
/**
* @fileoverview Login Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description User authentication page
* @keywords login, page, auth, signin, authentication
*/
import { BaseComponent } from '../components/base-component.js';
class LoginPage extends BaseComponent {
init() {
this.render();
this.bindEvents();
}
render() {
const isLoggedIn = this.isLoggedIn();
const user = this.getCurrentUser();
this.addClass('page', 'login-page');
if (isLoggedIn) {
this.setHtml(`
<div class="login-container">
<header class="login-header">
<button class="back-btn" aria-label="Go back">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
</svg>
</button>
<h1 class="login-logo">Rantii</h1>
</header>
<div class="logged-in-info">
<user-avatar size="large" username="${user?.username || ''}"></user-avatar>
<p class="logged-in-text">Logged in as <strong>${user?.username || ''}</strong></p>
<div class="logged-in-actions">
<button class="btn btn-primary home-btn">Go Home</button>
<button class="btn btn-secondary logout-btn">Logout</button>
</div>
<p class="switch-text">Want to use a different account?</p>
<button class="btn btn-link switch-btn">Switch Account</button>
</div>
</div>
`);
} else {
this.setHtml(`
<div class="login-container">
<header class="login-header">
<button class="back-btn" aria-label="Go back">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
</svg>
</button>
<h1 class="login-logo">Rantii</h1>
</header>
<login-form></login-form>
</div>
`);
}
}
bindEvents() {
this.on(this, 'click', this.handleClick);
this.on(this, 'login-success', this.handleLoginSuccess);
}
handleClick(e) {
const backBtn = e.target.closest('.back-btn');
const logoutBtn = e.target.closest('.logout-btn');
const switchBtn = e.target.closest('.switch-btn');
const homeBtn = e.target.closest('.home-btn');
if (backBtn) {
window.history.back();
} else if (logoutBtn) {
this.getAuth()?.logout();
this.render();
} else if (switchBtn) {
this.getAuth()?.logout();
this.render();
} else if (homeBtn) {
this.getRouter()?.goHome();
}
}
handleLoginSuccess() {
this.getRouter()?.goHome();
}
}
customElements.define('login-page', LoginPage);
export { LoginPage };
+33
View File
@@ -0,0 +1,33 @@
/**
* @fileoverview Notifications Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description User notifications view page
* @keywords notifications, page, alerts, mentions, updates
*/
import { BaseComponent } from '../components/base-component.js';
class NotificationsPage extends BaseComponent {
init() {
this.render();
}
render() {
this.addClass('page', 'notifications-page');
this.setHtml(`
<notification-list></notification-list>
`);
}
refresh() {
const list = this.$('notification-list');
if (list) {
list.refresh();
}
}
}
customElements.define('notifications-page', NotificationsPage);
export { NotificationsPage };
+49
View File
@@ -0,0 +1,49 @@
/**
* @fileoverview Profile Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description User profile view page
* @keywords profile, page, user, view, account
*/
import { BaseComponent } from '../components/base-component.js';
class ProfilePage extends BaseComponent {
static get observedAttributes() {
return ['username'];
}
init() {
this.render();
}
render() {
const username = this.getAttr('username');
this.addClass('page', 'profile-page');
this.setHtml(`
<user-profile username="${username || ''}"></user-profile>
`);
if (username) {
this.load(username);
}
}
async load(username) {
const profile = this.$('user-profile');
if (profile) {
await profile.load(username);
}
}
onAttributeChanged(name, oldValue, newValue) {
if (name === 'username' && newValue) {
this.load(newValue);
}
}
}
customElements.define('profile-page', ProfilePage);
export { ProfilePage };
+62
View File
@@ -0,0 +1,62 @@
/**
* @fileoverview Rant Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Single rant view page with comments
* @keywords rant, page, detail, view, single
*/
import { BaseComponent } from '../components/base-component.js';
class RantPage extends BaseComponent {
static get observedAttributes() {
return ['rant-id', 'comment-id'];
}
init() {
this.render();
}
render() {
const rantId = this.getAttr('rant-id');
this.addClass('page', 'rant-page');
this.setHtml(`
<rant-detail rant-id="${rantId || ''}"></rant-detail>
`);
if (rantId) {
this.load(rantId);
}
}
async load(rantId) {
const detail = this.$('rant-detail');
if (detail) {
await detail.load(rantId);
const commentId = this.getAttr('comment-id');
if (commentId) {
requestAnimationFrame(() => {
detail.scrollToComment(commentId);
});
}
}
}
onAttributeChanged(name, oldValue, newValue) {
if (name === 'rant-id' && newValue) {
this.load(newValue);
}
if (name === 'comment-id' && newValue) {
const detail = this.$('rant-detail');
if (detail) {
detail.scrollToComment(newValue);
}
}
}
}
customElements.define('rant-page', RantPage);
export { RantPage };
+88
View File
@@ -0,0 +1,88 @@
/**
* @fileoverview Search Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Search results and search interface page
* @keywords search, page, results, find, query
*/
import { BaseComponent } from '../components/base-component.js';
class SearchPage extends BaseComponent {
static get observedAttributes() {
return ['query'];
}
init() {
this.render();
this.bindEvents();
}
render() {
const query = this.getAttr('query') || '';
this.addClass('page', 'search-page');
this.setHtml(`
<header class="search-header">
<form class="search-form">
<input type="search"
class="search-input"
placeholder="Search rants..."
value="${query}"
autofocus>
<button type="submit" class="search-btn">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 0 0 1.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 0 0-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 0 0 5.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
</button>
</form>
</header>
<section class="search-results">
${query ? `<rant-feed feed-type="search"></rant-feed>` : `
<div class="search-empty">
<svg viewBox="0 0 24 24" width="64" height="64">
<path fill="currentColor" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 0 0 1.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 0 0-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 0 0 5.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
<p>Enter a search term</p>
</div>
`}
</section>
`);
if (query) {
this.search(query);
}
}
bindEvents() {
this.on(this, 'submit', this.handleSubmit);
}
handleSubmit(e) {
e.preventDefault();
const input = this.$('.search-input');
if (input && input.value.trim()) {
this.getRouter()?.goToSearch(input.value.trim());
}
}
async search(query) {
const feed = this.$('rant-feed');
if (feed) {
await feed.search(query);
}
}
onAttributeChanged(name, oldValue, newValue) {
if (name === 'query') {
this.render();
if (newValue) {
this.search(newValue);
}
}
}
}
customElements.define('search-page', SearchPage);
export { SearchPage };
+116
View File
@@ -0,0 +1,116 @@
/**
* @fileoverview Settings Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Application settings and preferences page
* @keywords settings, page, preferences, options, config
*/
import { BaseComponent } from '../components/base-component.js';
class SettingsPage extends BaseComponent {
init() {
this.render();
this.bindEvents();
}
render() {
const isLoggedIn = this.isLoggedIn();
const user = this.getCurrentUser();
this.addClass('page', 'settings-page');
this.setHtml(`
<header class="settings-header">
<button class="back-btn" aria-label="Go back">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
</svg>
</button>
<h1>Settings</h1>
</header>
<div class="settings-content">
${isLoggedIn ? `
<section class="settings-section">
<h2>Account</h2>
<div class="setting-item">
<span class="setting-label">Logged in as</span>
<span class="setting-value">${user?.username || ''}</span>
</div>
<button class="btn btn-danger logout-btn">Sign Out</button>
</section>
` : `
<section class="settings-section">
<h2>Account</h2>
<p>Sign in to access all features</p>
<button class="btn btn-primary login-btn">Sign In</button>
</section>
`}
<section class="settings-section">
<h2>Appearance</h2>
<theme-selector></theme-selector>
</section>
<section class="settings-section">
<h2>About</h2>
<div class="about-info">
<p><strong>Rantii</strong> - A DevRant Client</p>
<p>Version 1.0.0</p>
<p>Made with care by retoor</p>
</div>
</section>
<section class="settings-section">
<h2>Data</h2>
<button class="btn btn-secondary clear-cache-btn">Clear Cache</button>
</section>
</div>
`);
}
bindEvents() {
this.on(this, 'click', this.handleClick);
}
handleClick(e) {
const backBtn = e.target.closest('.back-btn');
const loginBtn = e.target.closest('.login-btn');
const logoutBtn = e.target.closest('.logout-btn');
const clearCacheBtn = e.target.closest('.clear-cache-btn');
if (backBtn) {
window.history.back();
return;
}
if (loginBtn) {
this.getRouter()?.goToLogin();
return;
}
if (logoutBtn) {
this.logout();
return;
}
if (clearCacheBtn) {
this.clearCache();
}
}
logout() {
this.getAuth()?.logout();
this.render();
this.getApp()?.toast?.success('Signed out successfully');
}
clearCache() {
const storage = this.getStorage();
if (storage) {
storage.clear();
storage.setTheme(this.getTheme()?.getTheme() || 'dark');
}
this.getApp()?.toast?.success('Cache cleared');
}
}
customElements.define('settings-page', SettingsPage);
export { SettingsPage };
+36
View File
@@ -0,0 +1,36 @@
/**
* @fileoverview Stories Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Developer stories page
* @keywords stories, page, devstories, feed, articles
*/
import { BaseComponent } from '../components/base-component.js';
class StoriesPage extends BaseComponent {
init() {
this.render();
}
render() {
this.addClass('page', 'stories-page');
this.setHtml(`
<header class="page-header">
<h1>Stories</h1>
</header>
<rant-feed sort="recent" feed-type="stories"></rant-feed>
`);
}
refresh() {
const feed = this.$('rant-feed');
if (feed) {
feed.refresh();
}
}
}
customElements.define('stories-page', StoriesPage);
export { StoriesPage };
+36
View File
@@ -0,0 +1,36 @@
/**
* @fileoverview Weekly Page Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Weekly rant challenge page
* @keywords weekly, page, challenge, rants, feed
*/
import { BaseComponent } from '../components/base-component.js';
class WeeklyPage extends BaseComponent {
init() {
this.render();
}
render() {
this.addClass('page', 'weekly-page');
this.setHtml(`
<header class="page-header">
<h1>Weekly Rant</h1>
</header>
<rant-feed sort="recent" feed-type="weekly"></rant-feed>
`);
}
refresh() {
const feed = this.$('rant-feed');
if (feed) {
feed.refresh();
}
}
}
customElements.define('weekly-page', WeeklyPage);
export { WeeklyPage };