250 lines
7.0 KiB
JavaScript
Raw Normal View History

2025-12-04 20:29:35 +01:00
/**
* @fileoverview Post Form Component for Rantii
* @author retoor <retoor@molodetz.nl>
* @description Form for creating new rants
* @keywords post, form, create, rant, new
*/
import { BaseComponent } from './base-component.js';
class PostForm extends BaseComponent {
init() {
this.isSubmitting = false;
this.render();
this.bindEvents();
this.loadDraft();
}
render() {
const isLoggedIn = this.isLoggedIn();
this.addClass('post-form');
if (!isLoggedIn) {
this.setHtml(`
<div class="post-form-auth">
<p>Sign in to post</p>
<button class="btn btn-primary login-btn">Sign In</button>
</div>
`);
return;
}
this.setHtml(`
<form class="post-form-inner">
<header class="form-header">
<h2>Create Rant</h2>
<button type="button" class="close-btn" aria-label="Close">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
</header>
<div class="form-group">
<textarea
class="post-input"
placeholder="What's on your mind?"
rows="6"
maxlength="5000"
${this.isSubmitting ? 'disabled' : ''}></textarea>
</div>
<div class="form-group">
<input
type="text"
class="tags-input"
placeholder="Tags (comma separated)"
maxlength="100"
${this.isSubmitting ? 'disabled' : ''}>
</div>
<div class="form-actions">
<span class="char-count">0 / 5000</span>
<button type="submit"
class="btn btn-primary submit-btn"
${this.isSubmitting ? 'disabled' : ''}>
${this.isSubmitting ? '<loading-spinner size="small"></loading-spinner>' : 'Post'}
</button>
</div>
</form>
`);
}
bindEvents() {
this.on(this, 'click', this.handleClick);
this.on(this, 'submit', this.handleSubmit);
this.on(this, 'input', this.handleInput);
}
handleClick(e) {
const loginBtn = e.target.closest('.login-btn');
const closeBtn = e.target.closest('.close-btn');
if (loginBtn) {
this.getRouter()?.goToLogin();
return;
}
if (closeBtn) {
this.emit('close');
}
}
async handleSubmit(e) {
e.preventDefault();
if (this.isSubmitting) return;
const textarea = this.$('.post-input');
const tagsInput = this.$('.tags-input');
if (!textarea) return;
const text = textarea.value.trim();
if (!text) {
this.getApp()?.toast?.error('Please enter some text');
return;
}
const tags = tagsInput ? tagsInput.value.trim() : '';
this.isSubmitting = true;
this.render();
try {
const result = await this.getApi()?.postRant(text, tags);
if (result?.success) {
this.clearDraft();
this.emit('post-created', { rantId: result.rantId });
this.getApp()?.toast?.success('Rant posted successfully');
this.isSubmitting = false;
this.render();
this.getRouter()?.goToRant(result.rantId);
} else {
this.getApp()?.toast?.error(result?.error || 'Failed to post');
this.isSubmitting = false;
this.render();
}
} catch (error) {
this.getApp()?.toast?.error('Failed to post');
this.isSubmitting = false;
this.render();
}
}
handleInput(e) {
if (e.target.classList.contains('post-input')) {
const textarea = e.target;
const count = textarea.value.length;
const countEl = this.$('.char-count');
if (countEl) {
countEl.textContent = `${count} / 5000`;
}
this.saveDraft(textarea.value);
}
}
loadDraft() {
const draft = this.getStorage()?.getDraftRant();
if (draft) {
const textarea = this.$('.post-input');
if (textarea) {
textarea.value = draft;
const countEl = this.$('.char-count');
if (countEl) {
countEl.textContent = `${draft.length} / 5000`;
}
}
}
}
saveDraft(text) {
this.getStorage()?.setDraftRant(text);
}
clearDraft() {
this.getStorage()?.clearDraftRant();
}
reset() {
const textarea = this.$('.post-input');
if (textarea) {
textarea.value = '';
}
const tagsInput = this.$('.tags-input');
if (tagsInput) {
tagsInput.value = '';
}
const countEl = this.$('.char-count');
if (countEl) {
countEl.textContent = '0 / 5000';
}
this.clearDraft();
}
focus() {
const textarea = this.$('.post-input');
if (textarea) {
textarea.focus();
}
}
}
customElements.define('post-form', PostForm);
class PostModal extends BaseComponent {
init() {
this.keydownHandler = (e) => this.handleKeydown(e);
this.render();
this.bindEvents();
}
render() {
this.addClass('modal', 'post-modal');
this.setHtml(`
<div class="modal-backdrop"></div>
<div class="modal-content">
<post-form></post-form>
</div>
`);
requestAnimationFrame(() => this.addClass('modal-visible'));
}
bindEvents() {
this.on(this, 'click', this.handleClick);
this.on(this, 'close', this.close);
this.on(this, 'post-created', this.close);
document.addEventListener('keydown', this.keydownHandler);
}
handleClick(e) {
if (e.target.classList.contains('modal-backdrop')) {
this.close();
}
}
handleKeydown(e) {
if (e.key === 'Escape') {
this.close();
}
}
close() {
document.removeEventListener('keydown', this.keydownHandler);
this.removeClass('modal-visible');
setTimeout(() => this.remove(), 300);
}
open() {
document.body.appendChild(this);
const form = this.$('post-form');
if (form) {
form.focus();
}
}
}
customElements.define('post-modal', PostModal);
export { PostForm, PostModal };