simplify tool contribution
This commit is contained in:
@@ -30,8 +30,8 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
<form id="kb-form" style="padding: 2rem;">
|
||||
<!-- Tool Selection -->
|
||||
<div class="form-group">
|
||||
<label for="tool-name" class="form-label required">Related Tool</label>
|
||||
<select id="tool-name" name="toolName" required class="form-input">
|
||||
<label for="tool-name" class="form-label">Related Tool</label>
|
||||
<select id="tool-name" name="toolName" class="form-input">
|
||||
<option value="">Select a tool</option>
|
||||
{sortedTools.map(tool => (
|
||||
<option value={tool.name}>{tool.name} ({tool.type})</option>
|
||||
@@ -41,12 +41,11 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
|
||||
<!-- Article Title -->
|
||||
<div class="form-group">
|
||||
<label for="title" class="form-label required">Article Title</label>
|
||||
<label for="title" class="form-label">Article Title</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
name="title"
|
||||
required
|
||||
name="title"
|
||||
maxlength="100"
|
||||
placeholder="Clear, descriptive title for your article"
|
||||
class="form-input"
|
||||
@@ -55,11 +54,10 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
|
||||
<!-- Description -->
|
||||
<div class="form-group">
|
||||
<label for="description" class="form-label required">Description</label>
|
||||
<label for="description" class="form-label">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
required
|
||||
name="description"
|
||||
maxlength="300"
|
||||
rows="3"
|
||||
placeholder="Brief summary of what this article covers (20-300 characters)"
|
||||
@@ -117,8 +115,8 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
|
||||
<!-- Difficulty Level -->
|
||||
<div class="form-group">
|
||||
<label for="difficulty" class="form-label required">Difficulty Level</label>
|
||||
<select id="difficulty" name="difficulty" required class="form-input">
|
||||
<label for="difficulty" class="form-label">Difficulty Level</label>
|
||||
<select id="difficulty" name="difficulty" class="form-input">
|
||||
<option value="">Select difficulty</option>
|
||||
<option value="novice">Novice - No prior experience needed</option>
|
||||
<option value="beginner">Beginner - Basic familiarity helpful</option>
|
||||
@@ -168,7 +166,7 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div style="display: flex; gap: 1rem; margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--color-border);">
|
||||
<button type="submit" id="submit-btn" class="btn btn-accent" style="flex: 1;" disabled>
|
||||
<button type="submit" id="submit-btn" class="btn btn-accent" style="flex: 1;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||||
</svg>
|
||||
@@ -204,85 +202,116 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
// State management
|
||||
let uploadedFiles: Array<{id: string, file: File, name: string, uploaded: boolean, url?: string}> = [];
|
||||
|
||||
// DOM elements
|
||||
const form = document.getElementById('kb-form') as HTMLFormElement;
|
||||
const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement;
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const uploadArea = document.getElementById('upload-area') as HTMLElement;
|
||||
const fileList = document.getElementById('file-list') as HTMLElement;
|
||||
const filesContainer = document.getElementById('files-container') as HTMLElement;
|
||||
|
||||
// Form validation
|
||||
function validateForm(): boolean {
|
||||
const toolName = (document.getElementById('tool-name') as HTMLSelectElement).value;
|
||||
const title = (document.getElementById('title') as HTMLInputElement).value;
|
||||
const description = (document.getElementById('description') as HTMLTextAreaElement).value;
|
||||
const content = (document.getElementById('content') as HTMLTextAreaElement).value;
|
||||
const externalLink = (document.getElementById('external-link') as HTMLInputElement).value;
|
||||
const difficulty = (document.getElementById('difficulty') as HTMLSelectElement).value;
|
||||
|
||||
const hasContent = content.trim().length > 0 || uploadedFiles.length > 0 || externalLink.trim().length > 0;
|
||||
|
||||
return Boolean(toolName) && Boolean(title) && Boolean(description) && Boolean(difficulty) && hasContent;
|
||||
// FIXED: Properly typed interfaces for TypeScript compliance
|
||||
interface UploadedFile {
|
||||
id: string;
|
||||
file: File;
|
||||
name: string;
|
||||
uploaded: boolean;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// Update submit button state
|
||||
function updateSubmitButton() {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = !validateForm();
|
||||
// Extend Window interface for global functions
|
||||
declare global {
|
||||
interface Window {
|
||||
removeFile: (fileId: string) => void;
|
||||
}
|
||||
}
|
||||
|
||||
// File upload handling
|
||||
function setupFileUpload() {
|
||||
if (!fileInput || !uploadArea) return;
|
||||
// FIXED: State management with proper typing
|
||||
let uploadedFiles: UploadedFile[] = [];
|
||||
|
||||
uploadArea.addEventListener('click', () => fileInput.click());
|
||||
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.borderColor = 'var(--color-accent)';
|
||||
// FIXED: Properly typed element selection with specific HTML element types
|
||||
const elements = {
|
||||
form: document.getElementById('kb-form') as HTMLFormElement | null,
|
||||
submitBtn: document.getElementById('submit-btn') as HTMLButtonElement | null,
|
||||
fileInput: document.getElementById('file-input') as HTMLInputElement | null,
|
||||
uploadArea: document.getElementById('upload-area') as HTMLElement | null,
|
||||
fileList: document.getElementById('file-list') as HTMLElement | null,
|
||||
filesContainer: document.getElementById('files-container') as HTMLElement | null
|
||||
};
|
||||
|
||||
// Check for critical elements
|
||||
const criticalElements: Array<keyof typeof elements> = ['form', 'submitBtn'];
|
||||
const missingElements = criticalElements.filter(key => !elements[key]);
|
||||
|
||||
if (missingElements.length > 0) {
|
||||
console.error('[KB FORM ERROR] Missing critical elements:', missingElements);
|
||||
} else {
|
||||
console.log('[KB FORM DEBUG] All critical elements found, initializing form');
|
||||
}
|
||||
|
||||
function validateForm(): boolean {
|
||||
return true; // Always return true - no validation
|
||||
}
|
||||
|
||||
// Update submit button state with null checks
|
||||
function updateSubmitButton(): void {
|
||||
if (elements.submitBtn) {
|
||||
const isValid = validateForm();
|
||||
elements.submitBtn.disabled = !isValid;
|
||||
console.log('[KB FORM DEBUG] Button state:', isValid ? 'enabled' : 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// File upload handling with proper null checks
|
||||
function setupFileUpload(): void {
|
||||
if (!elements.fileInput || !elements.uploadArea) return;
|
||||
|
||||
elements.uploadArea.addEventListener('click', () => {
|
||||
if (elements.fileInput) {
|
||||
elements.fileInput.click();
|
||||
}
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', () => {
|
||||
uploadArea.style.borderColor = 'var(--color-border)';
|
||||
elements.uploadArea.addEventListener('dragover', (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (elements.uploadArea) {
|
||||
elements.uploadArea.style.borderColor = 'var(--color-accent)';
|
||||
}
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
elements.uploadArea.addEventListener('dragleave', () => {
|
||||
if (elements.uploadArea) {
|
||||
elements.uploadArea.style.borderColor = 'var(--color-border)';
|
||||
}
|
||||
});
|
||||
|
||||
elements.uploadArea.addEventListener('drop', (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.borderColor = 'var(--color-border)';
|
||||
if (elements.uploadArea) {
|
||||
elements.uploadArea.style.borderColor = 'var(--color-border)';
|
||||
}
|
||||
if (e.dataTransfer?.files) {
|
||||
handleFiles(Array.from(e.dataTransfer.files));
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
elements.fileInput.addEventListener('change', (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.files) {
|
||||
if (target?.files) {
|
||||
handleFiles(Array.from(target.files));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleFiles(files: File[]) {
|
||||
function handleFiles(files: File[]): void {
|
||||
files.forEach(file => {
|
||||
const fileId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||
uploadedFiles.push({
|
||||
const newFile: UploadedFile = {
|
||||
id: fileId,
|
||||
file,
|
||||
name: file.name,
|
||||
uploaded: false
|
||||
});
|
||||
};
|
||||
uploadedFiles.push(newFile);
|
||||
uploadFile(fileId);
|
||||
});
|
||||
renderFileList();
|
||||
updateSubmitButton();
|
||||
}
|
||||
|
||||
async function uploadFile(fileId: string) {
|
||||
async function uploadFile(fileId: string): Promise<void> {
|
||||
const fileItem = uploadedFiles.find(f => f.id === fileId);
|
||||
if (!fileItem) return;
|
||||
|
||||
@@ -310,18 +339,18 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile(fileId: string) {
|
||||
function removeFile(fileId: string): void {
|
||||
uploadedFiles = uploadedFiles.filter(f => f.id !== fileId);
|
||||
renderFileList();
|
||||
updateSubmitButton();
|
||||
}
|
||||
|
||||
function renderFileList() {
|
||||
if (!filesContainer || !fileList) return;
|
||||
function renderFileList(): void {
|
||||
if (!elements.filesContainer || !elements.fileList) return;
|
||||
|
||||
if (uploadedFiles.length > 0) {
|
||||
fileList.style.display = 'block';
|
||||
filesContainer.innerHTML = uploadedFiles.map(file => `
|
||||
elements.fileList.style.display = 'block';
|
||||
elements.filesContainer.innerHTML = uploadedFiles.map(file => `
|
||||
<div class="file-item" style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; border: 1px solid var(--color-border); border-radius: 0.25rem; margin-bottom: 0.5rem;">
|
||||
<div style="flex: 1;">
|
||||
<strong>${file.name}</strong>
|
||||
@@ -333,29 +362,32 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onclick="removeFile('${file.id}')" class="btn btn-small" style="background: var(--color-danger); color: white;">Remove</button>
|
||||
<button type="button" onclick="window.removeFile('${file.id}')" class="btn btn-small" style="background: var(--color-danger); color: white;">Remove</button>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
fileList.style.display = 'none';
|
||||
elements.fileList.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Form submission
|
||||
async function handleSubmit(e: Event) {
|
||||
async function handleSubmit(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
console.log('[KB FORM DEBUG] Form submitted');
|
||||
|
||||
if (!submitBtn || !form || submitBtn.disabled) return;
|
||||
if (!elements.submitBtn || !elements.form) {
|
||||
console.log('[KB FORM DEBUG] Submission blocked - form missing');
|
||||
return;
|
||||
}
|
||||
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.innerHTML = '⏳ Submitting...';
|
||||
elements.submitBtn.classList.add('loading');
|
||||
elements.submitBtn.innerHTML = '⏳ Submitting...';
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const formData = new FormData(elements.form);
|
||||
|
||||
// Process categories and tags
|
||||
const categoriesValue = formData.get('categories') as string || '';
|
||||
const tagsValue = formData.get('tags') as string || '';
|
||||
// Process categories and tags with proper null handling
|
||||
const categoriesValue = (formData.get('categories') as string) || '';
|
||||
const tagsValue = (formData.get('tags') as string) || '';
|
||||
|
||||
const categories = categoriesValue.split(',').map(s => s.trim()).filter(s => s);
|
||||
const tags = tagsValue.split(',').map(s => s.trim()).filter(s => s);
|
||||
@@ -365,12 +397,15 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
// Add uploaded files
|
||||
formData.set('uploadedFiles', JSON.stringify(uploadedFiles.filter(f => f.uploaded)));
|
||||
|
||||
console.log('[KB FORM DEBUG] Submitting to API...');
|
||||
const response = await fetch('/api/contribute/knowledgebase', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
console.log('[KB FORM DEBUG] Response status:', response.status);
|
||||
const result = await response.json();
|
||||
console.log('[KB FORM DEBUG] Response data:', result);
|
||||
|
||||
if (result.success) {
|
||||
// Show success modal
|
||||
@@ -389,8 +424,10 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
successModal.style.display = 'flex';
|
||||
}
|
||||
|
||||
// Reset form
|
||||
form.reset();
|
||||
// Reset form with proper typing
|
||||
if (elements.form) {
|
||||
elements.form.reset();
|
||||
}
|
||||
uploadedFiles = [];
|
||||
renderFileList();
|
||||
updateSubmitButton();
|
||||
@@ -399,14 +436,17 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[KB FORM ERROR] Submission error:', error);
|
||||
showMessage('error', 'An error occurred during submission');
|
||||
} finally {
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/></svg> Submit Article';
|
||||
if (elements.submitBtn) {
|
||||
elements.submitBtn.classList.remove('loading');
|
||||
elements.submitBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/></svg> Submit Article';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(type: 'success' | 'error' | 'warning', message: string) {
|
||||
function showMessage(type: 'success' | 'error' | 'warning', message: string): void {
|
||||
const container = document.getElementById('message-container');
|
||||
if (!container) return;
|
||||
|
||||
@@ -423,57 +463,10 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
||||
setTimeout(() => messageEl.remove(), 5000);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
if (form) {
|
||||
form.addEventListener('submit', handleSubmit);
|
||||
form.addEventListener('input', updateSubmitButton);
|
||||
form.addEventListener('change', updateSubmitButton);
|
||||
}
|
||||
|
||||
// Make removeFile available globally
|
||||
(window as any).removeFile = removeFile;
|
||||
// Make removeFile available globally with proper typing
|
||||
window.removeFile = removeFile;
|
||||
|
||||
// Initialize
|
||||
setupFileUpload();
|
||||
updateSubmitButton();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.upload-area {
|
||||
border: 2px dashed var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.upload-placeholder svg {
|
||||
color: var(--color-text-tertiary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.btn.loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
console.log('[KB FORM DEBUG] Form initialization complete');
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user