Select a structure to begin outlining.
';
return;
}
structure.points.forEach((point, index) => {
const container = document.createElement('div');
container.className = 'ff-form-group full-width plot-point-group';
container.innerHTML = `
`;
plotPointsContainer.appendChild(container);
});
};
// --- Config Tab Functions (CRUD for Structures) ---
const populateStructureDropdown = () => {
plotStructureSelect.innerHTML = '';
if (structures.length === 0) {
plotStructureSelect.innerHTML = '
';
return;
}
structures.forEach(struct => {
const option = new Option(struct.name, struct.id);
plotStructureSelect.appendChild(option);
});
loadStructure(); // Re-load inputs for the selected structure
};
const renderStructureList = () => {
structureListBody.innerHTML = '';
structures.forEach((struct) => {
const pointsPreview = struct.points.slice(0, 3).join('; ') + (struct.points.length > 3 ? '...' : '');
const row = document.createElement('tr');
row.innerHTML = `
${struct.name} |
${pointsPreview} |
|
`;
structureListBody.appendChild(row);
});
populateStructureDropdown();
};
structureForm.addEventListener('submit', (e) => {
e.preventDefault();
const isEditing = !!structEditIdInput.value;
const currentId = structEditIdInput.value || `struct_${Date.now()}`;
const pointsText = document.getElementById('struct-points').value;
const newPoints = pointsText.split('\n').map(p => p.trim()).filter(p => p.length > 0);
if (newPoints.length === 0) {
alert("Please enter at least one plot point label.");
return;
}
const newStruct = {
id: currentId,
name: document.getElementById('struct-name').value,
points: newPoints
};
if (isEditing) {
const index = structures.findIndex(t => t.id === currentId);
if (index !== -1) { structures[index] = newStruct; }
} else {
structures.push(newStruct);
}
// Reset form state
structureForm.reset();
structEditIdInput.value = '';
document.getElementById('struct-legend').textContent = 'Add New Structure';
structAddUpdateBtn.textContent = 'Add Structure';
renderStructureList();
});
window.editStructure = (id) => {
const struct = structures.find(t => t.id === id);
if (struct) {
structEditIdInput.value = struct.id;
document.getElementById('struct-name').value = struct.name;
document.getElementById('struct-points').value = struct.points.join('\n');
document.getElementById('struct-legend').textContent = `Editing Structure: ${struct.name}`;
structAddUpdateBtn.textContent = 'Update Structure';
switchTab(1); // Switch to config tab
}
};
window.deleteStructure = (id) => {
if (confirm('Are you sure you want to delete this plot structure?')) {
structures = structures.filter(t => t.id !== id);
renderStructureList();
}
};
// --- Outline Generation (Tab 3) ---
const getOutlineData = () => {
const metadata = getMetadata();
const selectedId = plotStructureSelect.value;
const structure = structures.find(s => s.id === selectedId);
const plotPoints = [];
if (structure) {
structure.points.forEach((point, index) => {
const textarea = document.getElementById(`plot-point-${index}`);
if (textarea) {
plotPoints.push({
label: point,
content: textarea.value
});
}
});
}
return { metadata, structure: structure ? structure.name : 'None Selected', plotPoints };
};
const generateOutline = () => {
const data = getOutlineData();
reviewContent.innerHTML = '';
if (!data.structure || data.plotPoints.length === 0) {
reviewContent.innerHTML = '
Please select a structure and fill out the plot points in the Builder tab.
';
pdfDownloadBtn.disabled = true;
return;
}
// --- Metadata ---
let html = `
${data.structure} Outline
`;
// --- Plot Points ---
data.plotPoints.forEach((point, index) => {
html += `
${index + 1}. ${point.label}
${point.content || '(Plot point details missing)'}
`;
});
reviewContent.innerHTML = html;
pdfDownloadBtn.disabled = false;
switchTab(2); // Switch to review tab
};
/**
* PDF Generation Function (Fully Functional)
*/
const downloadPDF = () => {
const data = getOutlineData();
const jsPDF = window.jspdf.jsPDF;
const doc = new jsPDF('p', 'pt', 'a4');
let currentY = 40;
const margin = 40;
const pageWidth = doc.internal.pageSize.width;
const maxWidth = pageWidth - (margin * 2);
const checkPageBreak = (spaceNeeded) => {
if (currentY + spaceNeeded > doc.internal.pageSize.height - margin) {
doc.addPage();
currentY = margin;
}
};
const addText = (text, size = 10, style = 'normal', indent = 0) => {
doc.setFontSize(size);
doc.setFont('Helvetica', style);
const lines = doc.splitTextToSize(text, maxWidth - indent);
checkPageBreak(lines.length * (size * 1.2));
doc.text(lines, margin + indent, currentY);
currentY += (lines.length * (size * 1.2));
};
// --- PDF Content ---
// Title Block
doc.setFontSize(22);
doc.setFont('Helvetica', 'bold');
doc.setTextColor(106, 90, 205);
doc.text(data.metadata.title.toUpperCase() || 'UNTITLED FANFICTION', pageWidth / 2, currentY, { align: 'center' });
currentY += 15;
doc.setFontSize(12);
doc.setTextColor(74, 78, 105);
addText(`Fandom: ${data.metadata.fandom || 'N/A'} | Pairing/MC: ${data.metadata.pairing || 'N/A'} | Genre: ${data.metadata.genre || 'N/A'}`, 12, 'normal', 0);
currentY += 5;
doc.setFontSize(10);
addText(`Premise: ${data.metadata.summary || 'No summary provided.'}`, 10, 'italic', 0);
currentY += 20;
// Outline Header
doc.setFontSize(16);
doc.setFont('Helvetica', 'bold');
doc.setTextColor(106, 90, 205);
doc.text(`${data.structure} Outline`, margin, currentY);
currentY += 5;
doc.setLineWidth(0.5);
doc.setDrawColor(200);
doc.line(margin, currentY, pageWidth - margin, currentY);
currentY += 15;
doc.setTextColor(0);
// Plot Points
data.plotPoints.forEach((point, index) => {
checkPageBreak(30);
// Point Label (Header)
doc.setFontSize(12);
doc.setFont('Helvetica', 'bold');
doc.setTextColor(74, 78, 105);
addText(`${index + 1}. ${point.label}`, 12, 'bold', 0);
// Point Content
doc.setFontSize(11);
doc.setFont('Helvetica', 'normal');
addText(point.content || '(Plot point details missing)', 11, 'normal', 10);
currentY += 5;
});
doc.save(`${data.metadata.title.replace(/\s/g, '_') || 'Fanfiction'}_Outline.pdf`);
};
// --- Event Listeners and Initial Load ---
plotStructureSelect.addEventListener('change', loadStructure);
generateOutlineBtn.addEventListener('click', generateOutline);
pdfDownloadBtn.addEventListener('click', downloadPDF);
// Tab Navigation
const switchTab = (tabIndex) => {
tabs.forEach((tab, index) => {
tab.classList.toggle('active', index === tabIndex);
contents[index].classList.toggle('active', index === tabIndex);
});
currentTab = tabIndex;
updateNavButtons();
if (tabIndex === 2) {
generateOutline();
} else if (tabIndex === 1) {
renderStructureList();
}
};
const updateNavButtons = () => {
prevBtn.disabled = currentTab === 0;
nextBtn.disabled = currentTab === tabs.length - 1;
};
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
const tabNode = tab.closest('.ff-tab-button');
const newIndex = Array.from(tabNode.parentNode.children).indexOf(tabNode);
switchTab(newIndex);
});
});
nextBtn.addEventListener('click', () => { if (currentTab < tabs.length - 1) switchTab(currentTab + 1); });
prevBtn.addEventListener('click', () => { if (currentTab > 0) switchTab(currentTab - 1); });
// Initial Setup
renderStructureList();
updateNavButtons();
});