fix contrib system
This commit is contained in:
@@ -412,6 +412,8 @@ const title = isEdit ? `Edit ${editTool?.name}` : 'Contribute New Tool';
|
||||
domainAgnosticSoftware,
|
||||
existingConcepts: existingTools.filter(t => t.type === 'concept')
|
||||
}}>
|
||||
// REPLACE the JavaScript section at the bottom of tool.astro with this:
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('contribution-form');
|
||||
const typeSelect = document.getElementById('tool-type');
|
||||
@@ -447,23 +449,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const descriptionCount = document.getElementById('description-count');
|
||||
const reasonCount = document.getElementById('reason-count');
|
||||
|
||||
descriptionTextarea.addEventListener('input', () => updateCharacterCounter(descriptionTextarea, descriptionCount, 1000));
|
||||
reasonTextarea.addEventListener('input', () => updateCharacterCounter(reasonTextarea, reasonCount, 500));
|
||||
if (descriptionTextarea && descriptionCount) {
|
||||
descriptionTextarea.addEventListener('input', () => updateCharacterCounter(descriptionTextarea, descriptionCount, 1000));
|
||||
updateCharacterCounter(descriptionTextarea, descriptionCount, 1000);
|
||||
}
|
||||
|
||||
// Initial counter update
|
||||
updateCharacterCounter(descriptionTextarea, descriptionCount, 1000);
|
||||
updateCharacterCounter(reasonTextarea, reasonCount, 500);
|
||||
if (reasonTextarea && reasonCount) {
|
||||
reasonTextarea.addEventListener('input', () => updateCharacterCounter(reasonTextarea, reasonCount, 500));
|
||||
updateCharacterCounter(reasonTextarea, reasonCount, 500);
|
||||
}
|
||||
|
||||
// Handle type-specific field visibility
|
||||
function updateFieldVisibility() {
|
||||
const selectedType = typeSelect.value;
|
||||
|
||||
// Hide all type-specific fields
|
||||
softwareFields.style.display = 'none';
|
||||
relatedConceptsField.style.display = 'none';
|
||||
if (softwareFields) softwareFields.style.display = 'none';
|
||||
if (relatedConceptsField) relatedConceptsField.style.display = 'none';
|
||||
|
||||
// Show project URL for software only
|
||||
projectUrlField.style.display = selectedType === 'software' ? 'block' : 'none';
|
||||
if (projectUrlField) {
|
||||
projectUrlField.style.display = selectedType === 'software' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Handle required fields
|
||||
const platformsCheckboxes = document.querySelectorAll('input[name="platforms"]');
|
||||
@@ -471,34 +478,38 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
if (selectedType === 'software') {
|
||||
// Show software-specific fields
|
||||
softwareFields.style.display = 'block';
|
||||
relatedConceptsField.style.display = 'block';
|
||||
if (softwareFields) softwareFields.style.display = 'block';
|
||||
if (relatedConceptsField) relatedConceptsField.style.display = 'block';
|
||||
|
||||
// Make platforms and license required
|
||||
platformsRequired.style.display = 'inline';
|
||||
licenseRequired.style.display = 'inline';
|
||||
if (platformsRequired) platformsRequired.style.display = 'inline';
|
||||
if (licenseRequired) licenseRequired.style.display = 'inline';
|
||||
platformsCheckboxes.forEach(cb => cb.setAttribute('required', 'required'));
|
||||
licenseSelect.setAttribute('required', 'required');
|
||||
if (licenseSelect) licenseSelect.setAttribute('required', 'required');
|
||||
|
||||
} else {
|
||||
// Hide required indicators and remove requirements
|
||||
platformsRequired.style.display = 'none';
|
||||
licenseRequired.style.display = 'none';
|
||||
if (platformsRequired) platformsRequired.style.display = 'none';
|
||||
if (licenseRequired) licenseRequired.style.display = 'none';
|
||||
platformsCheckboxes.forEach(cb => cb.removeAttribute('required'));
|
||||
licenseSelect.removeAttribute('required');
|
||||
if (licenseSelect) licenseSelect.removeAttribute('required');
|
||||
|
||||
// Show related concepts for methods
|
||||
if (selectedType === 'method') {
|
||||
if (selectedType === 'method' && relatedConceptsField) {
|
||||
relatedConceptsField.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Update YAML preview
|
||||
updateYAMLPreview();
|
||||
if (typeof updateYAMLPreview === 'function') {
|
||||
updateYAMLPreview();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate YAML preview
|
||||
function updateYAMLPreview() {
|
||||
if (!yamlPreview) return;
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const toolData = {
|
||||
@@ -533,62 +544,64 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Handle related concepts
|
||||
if (toolData.type !== 'concept') {
|
||||
toolData.related_concepts = formData.getAll('relatedConcepts') || null;
|
||||
const relatedConcepts = formData.getAll('relatedConcepts');
|
||||
toolData.related_concepts = relatedConcepts.length > 0 ? relatedConcepts : null;
|
||||
}
|
||||
|
||||
// Convert to YAML-like format for preview
|
||||
let yamlContent = `- name: "${toolData.name}"\n`;
|
||||
if (toolData.icon) yamlContent += ` icon: "${toolData.icon}"\n`;
|
||||
yamlContent += ` type: ${toolData.type}\n`;
|
||||
yamlContent += ` description: >\n ${toolData.description}\n`;
|
||||
if (toolData.domains.length > 0) {
|
||||
yamlContent += ` domains:\n${toolData.domains.map(d => ` - ${d}`).join('\n')}\n`;
|
||||
}
|
||||
if (toolData.phases.length > 0) {
|
||||
yamlContent += ` phases:\n${toolData.phases.map(p => ` - ${p}`).join('\n')}\n`;
|
||||
}
|
||||
if (toolData.platforms.length > 0) {
|
||||
yamlContent += ` platforms:\n${toolData.platforms.map(p => ` - ${p}`).join('\n')}\n`;
|
||||
}
|
||||
yamlContent += ` skillLevel: ${toolData.skillLevel}\n`;
|
||||
if (toolData.accessType) yamlContent += ` accessType: ${toolData.accessType}\n`;
|
||||
yamlContent += ` url: ${toolData.url}\n`;
|
||||
if (toolData.projectUrl) yamlContent += ` projectUrl: ${toolData.projectUrl}\n`;
|
||||
if (toolData.license) yamlContent += ` license: ${toolData.license}\n`;
|
||||
if (toolData.knowledgebase) yamlContent += ` knowledgebase: ${toolData.knowledgebase}\n`;
|
||||
if (toolData.related_concepts && toolData.related_concepts.length > 0) {
|
||||
yamlContent += ` related_concepts:\n${toolData.related_concepts.map(c => ` - ${c}`).join('\n')}\n`;
|
||||
}
|
||||
if (toolData.tags.length > 0) {
|
||||
yamlContent += ` tags:\n${toolData.tags.map(t => ` - ${t}`).join('\n')}\n`;
|
||||
}
|
||||
// Generate YAML
|
||||
yamlPreview.textContent = `name: "${toolData.name}"
|
||||
${toolData.icon ? `icon: "${toolData.icon}"` : '# icon: "📦"'}
|
||||
type: ${toolData.type}
|
||||
description: "${toolData.description}"
|
||||
domains: [${toolData.domains.map(d => `"${d}"`).join(', ')}]
|
||||
phases: [${toolData.phases.map(p => `"${p}"`).join(', ')}]
|
||||
skillLevel: ${toolData.skillLevel}
|
||||
url: "${toolData.url}"${toolData.platforms.length > 0 ? `
|
||||
platforms: [${toolData.platforms.map(p => `"${p}"`).join(', ')}]` : ''}${toolData.license ? `
|
||||
license: "${toolData.license}"` : ''}${toolData.accessType ? `
|
||||
accessType: ${toolData.accessType}` : ''}${toolData.projectUrl ? `
|
||||
projectUrl: "${toolData.projectUrl}"` : ''}${toolData.knowledgebase ? `
|
||||
knowledgebase: true` : ''}${toolData.tags.length > 0 ? `
|
||||
tags: [${toolData.tags.map(t => `"${t}"`).join(', ')}]` : ''}${toolData.related_concepts ? `
|
||||
related_concepts: [${toolData.related_concepts.map(c => `"${c}"`).join(', ')}]` : ''}`;
|
||||
|
||||
yamlPreview.textContent = yamlContent;
|
||||
} catch (error) {
|
||||
yamlPreview.textContent = `# Error generating preview: ${error.message}`;
|
||||
yamlPreview.textContent = '# Error generating preview';
|
||||
console.error('YAML preview error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function validateForm() {
|
||||
const errors = [];
|
||||
const formData = new FormData(form);
|
||||
const selectedType = typeSelect.value;
|
||||
|
||||
// Basic validation
|
||||
if (!nameInput.value.trim()) errors.push('Name is required');
|
||||
if (!descriptionTextarea.value.trim() || descriptionTextarea.value.length < 10) {
|
||||
errors.push('Description must be at least 10 characters');
|
||||
if (!formData.get('name')?.trim()) {
|
||||
errors.push('Tool name is required');
|
||||
}
|
||||
|
||||
const selectedType = typeSelect.value;
|
||||
if (!formData.get('description')?.trim()) {
|
||||
errors.push('Description is required');
|
||||
}
|
||||
|
||||
if (!formData.get('url')?.trim()) {
|
||||
errors.push('URL is required');
|
||||
}
|
||||
|
||||
if (!formData.get('skillLevel')) {
|
||||
errors.push('Skill level is required');
|
||||
}
|
||||
|
||||
// Type-specific validation
|
||||
if (selectedType === 'software') {
|
||||
const platforms = new FormData(form).getAll('platforms');
|
||||
const platforms = formData.getAll('platforms');
|
||||
if (platforms.length === 0) {
|
||||
errors.push('At least one platform is required for software');
|
||||
}
|
||||
|
||||
if (!document.getElementById('tool-license').value.trim()) {
|
||||
if (!document.getElementById('tool-license')?.value?.trim()) {
|
||||
errors.push('License is required for software');
|
||||
}
|
||||
}
|
||||
@@ -597,124 +610,171 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
typeSelect.addEventListener('change', updateFieldVisibility);
|
||||
refreshPreviewBtn.addEventListener('click', updateYAMLPreview);
|
||||
if (typeSelect) {
|
||||
typeSelect.addEventListener('change', updateFieldVisibility);
|
||||
}
|
||||
|
||||
if (refreshPreviewBtn) {
|
||||
refreshPreviewBtn.addEventListener('click', updateYAMLPreview);
|
||||
}
|
||||
|
||||
// Update preview on form changes
|
||||
form.addEventListener('input', debounce(updateYAMLPreview, 500));
|
||||
form.addEventListener('change', updateYAMLPreview);
|
||||
if (form) {
|
||||
form.addEventListener('input', debounce(updateYAMLPreview, 500));
|
||||
form.addEventListener('change', updateYAMLPreview);
|
||||
}
|
||||
|
||||
// Form submission
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const errors = validateForm();
|
||||
if (errors.length > 0) {
|
||||
alert('Please fix the following errors:\n' + errors.join('\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true;
|
||||
submitText.textContent = isEdit ? 'Updating...' : 'Submitting...';
|
||||
submitSpinner.style.display = 'inline-block';
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Prepare submission data
|
||||
const submissionData = {
|
||||
action: isEdit ? 'edit' : 'add',
|
||||
tool: {
|
||||
name: formData.get('name'),
|
||||
icon: formData.get('icon') || null,
|
||||
type: formData.get('type'),
|
||||
description: formData.get('description'),
|
||||
domains: formData.getAll('domains'),
|
||||
phases: formData.getAll('phases'),
|
||||
skillLevel: formData.get('skillLevel'),
|
||||
url: formData.get('url'),
|
||||
tags: formData.get('tags') ? formData.get('tags').split(',').map(tag => tag.trim()).filter(Boolean) : []
|
||||
},
|
||||
metadata: {
|
||||
reason: formData.get('reason') || null
|
||||
}
|
||||
};
|
||||
|
||||
// Add type-specific fields
|
||||
if (submissionData.tool.type === 'software') {
|
||||
submissionData.tool.platforms = formData.getAll('platforms');
|
||||
submissionData.tool.license = formData.get('license').trim();
|
||||
submissionData.tool.accessType = formData.get('accessType');
|
||||
submissionData.tool.projectUrl = formData.get('projectUrl') || null;
|
||||
const errors = validateForm();
|
||||
if (errors.length > 0) {
|
||||
alert('Please fix the following errors:\n' + errors.join('\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Add optional fields
|
||||
submissionData.tool.knowledgebase = formData.has('knowledgebase') || null;
|
||||
|
||||
if (submissionData.tool.type !== 'concept') {
|
||||
const relatedConcepts = formData.getAll('relatedConcepts');
|
||||
submissionData.tool.related_concepts = relatedConcepts.length > 0 ? relatedConcepts : null;
|
||||
}
|
||||
// Show loading state
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
if (submitText) submitText.textContent = isEdit ? 'Updating...' : 'Submitting...';
|
||||
if (submitSpinner) submitSpinner.style.display = 'inline-block';
|
||||
|
||||
const response = await fetch('/api/contribute/tool', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(submissionData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Show success modal
|
||||
document.getElementById('success-message').textContent =
|
||||
`Your ${isEdit ? 'update' : 'contribution'} has been submitted successfully and will be reviewed by the maintainers.`;
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
|
||||
if (result.prUrl) {
|
||||
const prLink = document.getElementById('pr-link');
|
||||
prLink.href = result.prUrl;
|
||||
prLink.style.display = 'inline-flex';
|
||||
// FIXED: Prepare submission data with proper metadata.reason handling
|
||||
const submissionData = {
|
||||
action: isEdit ? 'edit' : 'add',
|
||||
tool: {
|
||||
name: formData.get('name'),
|
||||
icon: formData.get('icon') || null,
|
||||
type: formData.get('type'),
|
||||
description: formData.get('description'),
|
||||
domains: formData.getAll('domains'),
|
||||
phases: formData.getAll('phases'),
|
||||
skillLevel: formData.get('skillLevel'),
|
||||
url: formData.get('url'),
|
||||
tags: formData.get('tags') ? formData.get('tags').split(',').map(tag => tag.trim()).filter(Boolean) : []
|
||||
},
|
||||
metadata: {
|
||||
// FIXED: Always provide a string, never null
|
||||
reason: formData.get('reason')?.trim() || ''
|
||||
}
|
||||
};
|
||||
|
||||
// Add type-specific fields
|
||||
if (submissionData.tool.type === 'software') {
|
||||
submissionData.tool.platforms = formData.getAll('platforms');
|
||||
submissionData.tool.license = formData.get('license')?.trim() || null;
|
||||
submissionData.tool.accessType = formData.get('accessType') || null;
|
||||
submissionData.tool.projectUrl = formData.get('projectUrl')?.trim() || null;
|
||||
}
|
||||
|
||||
// Add optional fields
|
||||
submissionData.tool.knowledgebase = formData.has('knowledgebase') || null;
|
||||
|
||||
if (submissionData.tool.type !== 'concept') {
|
||||
const relatedConcepts = formData.getAll('relatedConcepts');
|
||||
submissionData.tool.related_concepts = relatedConcepts.length > 0 ? relatedConcepts : null;
|
||||
}
|
||||
|
||||
console.log('Submitting:', submissionData); // Debug log
|
||||
|
||||
const response = await fetch('/api/contribute/tool', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(submissionData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Show success modal
|
||||
if (successModal) {
|
||||
const successMessage = document.getElementById('success-message');
|
||||
if (successMessage) {
|
||||
successMessage.textContent = `Your ${isEdit ? 'update' : 'contribution'} has been submitted successfully and will be reviewed by the maintainers.`;
|
||||
}
|
||||
|
||||
if (result.prUrl) {
|
||||
const prLink = document.getElementById('pr-link');
|
||||
if (prLink) {
|
||||
prLink.href = result.prUrl;
|
||||
prLink.style.display = 'inline-flex';
|
||||
}
|
||||
}
|
||||
|
||||
successModal.style.display = 'flex';
|
||||
}
|
||||
} else {
|
||||
let errorMessage = result.error || 'Submission failed';
|
||||
if (result.details && Array.isArray(result.details)) {
|
||||
errorMessage += '\n\nDetails:\n' + result.details.join('\n');
|
||||
}
|
||||
alert(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Submission error:', error);
|
||||
alert('An error occurred while submitting your contribution. Please try again.');
|
||||
} finally {
|
||||
// Reset loading state
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
if (submitText) submitText.textContent = isEdit ? 'Update Tool' : 'Submit Contribution';
|
||||
if (submitSpinner) submitSpinner.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// FIXED: Initialize form with proper order
|
||||
function initializeForm() {
|
||||
if (isEdit && editTool) {
|
||||
console.log('Initializing edit form for:', editTool.name);
|
||||
|
||||
// Set basic fields first
|
||||
if (typeSelect) {
|
||||
typeSelect.value = editTool.type;
|
||||
}
|
||||
|
||||
// Update field visibility FIRST
|
||||
updateFieldVisibility();
|
||||
|
||||
// THEN set the platform checkboxes and access type after a brief delay
|
||||
setTimeout(() => {
|
||||
// FIXED: Set platform checkboxes with more specific selector
|
||||
if (editTool.platforms) {
|
||||
editTool.platforms.forEach(platform => {
|
||||
const checkbox = document.querySelector(`input[name="platforms"][value="${platform}"]`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
console.log('Set platform checkbox:', platform);
|
||||
} else {
|
||||
console.warn('Platform checkbox not found:', platform);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
successModal.style.display = 'flex';
|
||||
} else {
|
||||
let errorMessage = result.error || 'Submission failed';
|
||||
if (result.details && Array.isArray(result.details)) {
|
||||
errorMessage += '\n\nDetails:\n' + result.details.join('\n');
|
||||
// FIXED: Set access type value
|
||||
if (editTool.accessType) {
|
||||
const accessTypeSelect = document.getElementById('access-type');
|
||||
if (accessTypeSelect) {
|
||||
accessTypeSelect.value = editTool.accessType;
|
||||
console.log('Set access type:', editTool.accessType);
|
||||
}
|
||||
}
|
||||
alert(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Submission error:', error);
|
||||
alert('An error occurred while submitting your contribution. Please try again.');
|
||||
} finally {
|
||||
// Reset loading state
|
||||
submitBtn.disabled = false;
|
||||
submitText.textContent = isEdit ? 'Update Tool' : 'Submit Contribution';
|
||||
submitSpinner.style.display = 'none';
|
||||
|
||||
// Update YAML preview after all values are set
|
||||
updateYAMLPreview();
|
||||
}, 100);
|
||||
} else {
|
||||
updateFieldVisibility();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize form
|
||||
if (isEdit && editTool) {
|
||||
// Pre-fill edit form
|
||||
typeSelect.value = editTool.type;
|
||||
updateFieldVisibility();
|
||||
|
||||
// Set checkboxes for platforms
|
||||
if (editTool.platforms) {
|
||||
editTool.platforms.forEach(platform => {
|
||||
const checkbox = document.querySelector(`input[value="${platform}"]`);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
updateYAMLPreview();
|
||||
} else {
|
||||
updateFieldVisibility();
|
||||
}
|
||||
initializeForm();
|
||||
|
||||
// Debounce utility
|
||||
function debounce(func, wait) {
|
||||
|
||||
Reference in New Issue
Block a user