Spaces:
Runtime error
Runtime error
export default class TextEditor { | |
constructor(container, options = {}) { | |
this.options = { | |
charsPerPage: 500, // Default to 500 chars per page | |
onTextChange: null, | |
...options | |
}; | |
this.container = container; | |
this.currentPage = 1; | |
this.pages = ['']; | |
this.charCount = 0; | |
this.fullText = ''; | |
this.isTyping = false; | |
this.setupDOM(); | |
this.bindEvents(); | |
} | |
setupDOM() { | |
this.container.innerHTML = ` | |
<div class="text-editor"> | |
<div class="editor-view"> | |
<div class="page-navigation"> | |
<div class="pagination"> | |
<button class="prev-btn">← Previous</button> | |
<span class="page-info">Page 1 of 1</span> | |
<button class="next-btn">Next →</button> | |
</div> | |
</div> | |
<textarea | |
class="page-content" | |
placeholder="Enter text to convert to speech..." | |
></textarea> | |
<div class="editor-footer"> | |
<div class="file-controls"> | |
<input type="file" class="file-input" accept=".txt" style="display: none;"> | |
<button class="upload-btn">Upload Text</button> | |
<button class="clear-btn">Clear Text</button> | |
</div> | |
<div class="chars-per-page"> | |
<input | |
type="number" | |
class="chars-input" | |
value="500" | |
min="100" | |
max="2000" | |
title="Characters per page" | |
> | |
<span class="chars-label">chars/page</span> | |
<button class="format-btn">Format Pages</button> | |
</div> | |
<div class="char-count">0 characters</div> | |
</div> | |
</div> | |
</div> | |
`; | |
// Cache DOM elements | |
this.elements = { | |
pageContent: this.container.querySelector('.page-content'), | |
prevBtn: this.container.querySelector('.prev-btn'), | |
nextBtn: this.container.querySelector('.next-btn'), | |
pageInfo: this.container.querySelector('.page-info'), | |
fileInput: this.container.querySelector('.file-input'), | |
uploadBtn: this.container.querySelector('.upload-btn'), | |
clearBtn: this.container.querySelector('.clear-btn'), | |
charCount: this.container.querySelector('.char-count'), | |
charsPerPage: this.container.querySelector('.chars-input'), | |
formatBtn: this.container.querySelector('.format-btn') | |
}; | |
// Set initial chars per page value | |
this.elements.charsPerPage.value = this.options.charsPerPage; | |
} | |
bindEvents() { | |
// Handle page content changes | |
this.elements.pageContent.addEventListener('input', (e) => { | |
const newContent = e.target.value; | |
this.pages[this.currentPage - 1] = newContent; | |
// Only handle empty pages, otherwise just update the text | |
if (!newContent.trim() && this.pages.length > 1) { | |
// Remove the empty page and adjust | |
this.pages.splice(this.currentPage - 1, 1); | |
this.currentPage = Math.min(this.currentPage, this.pages.length); | |
this.updatePageDisplay(); | |
} | |
// Update full text and char count - join with space since pages are just for UI | |
this.fullText = this.pages.join(' '); | |
this.updateCharCount(); | |
if (this.options.onTextChange) { | |
this.options.onTextChange(this.fullText); | |
} | |
}); | |
// Navigation | |
this.elements.prevBtn.addEventListener('click', () => { | |
if (this.currentPage > 1) { | |
this.currentPage--; | |
this.updatePageDisplay(); | |
} | |
}); | |
this.elements.nextBtn.addEventListener('click', () => { | |
if (this.currentPage < this.pages.length) { | |
this.currentPage++; | |
this.updatePageDisplay(); | |
} | |
}); | |
// File upload | |
this.elements.uploadBtn.addEventListener('click', () => { | |
this.elements.fileInput.click(); | |
}); | |
this.elements.fileInput.addEventListener('change', (e) => { | |
const file = e.target.files[0]; | |
if (file) { | |
const reader = new FileReader(); | |
reader.onload = (e) => { | |
this.setText(e.target.result); | |
if (this.options.onTextChange) { | |
this.options.onTextChange(this.getText()); | |
} | |
}; | |
reader.readAsText(file); | |
} | |
}); | |
// Clear text | |
this.elements.clearBtn.addEventListener('click', () => { | |
this.clear(); | |
if (this.options.onTextChange) { | |
this.options.onTextChange(''); | |
} | |
}); | |
// Cache format button | |
this.elements.formatBtn = this.container.querySelector('.format-btn'); | |
// Characters per page control - just update the value | |
this.elements.charsPerPage.addEventListener('change', (e) => { | |
const value = parseInt(e.target.value); | |
if (value >= 100 && value <= 2000) { | |
this.options.charsPerPage = value; | |
} | |
}); | |
// Format pages button - trigger the split | |
this.elements.formatBtn.addEventListener('click', () => { | |
const value = parseInt(this.elements.charsPerPage.value); | |
if (value >= 100 && value <= 2000) { | |
this.options.charsPerPage = value; | |
this.splitIntoPages(this.fullText); | |
} | |
}); | |
} | |
splitIntoPages(text) { | |
if (!text || !text.trim()) { | |
this.pages = ['']; | |
this.fullText = ''; | |
this.currentPage = 1; | |
this.updatePageDisplay(); | |
this.updateCharCount(); | |
return; | |
} | |
// Store original text to preserve natural line breaks | |
this.fullText = text.trim(); | |
const words = text.trim().split(/\s+/); | |
this.pages = []; | |
let currentPage = ''; | |
for (let i = 0; i < words.length; i++) { | |
const word = words[i]; | |
const potentialPage = currentPage + (currentPage ? ' ' : '') + word; | |
if (potentialPage.length >= this.options.charsPerPage && currentPage) { | |
this.pages.push(currentPage); | |
currentPage = word; | |
} else { | |
currentPage = potentialPage; | |
} | |
} | |
if (currentPage) { | |
this.pages.push(currentPage); | |
} | |
if (this.pages.length === 0) { | |
this.pages = ['']; | |
this.currentPage = 1; | |
} else { | |
// Keep current page in bounds | |
this.currentPage = Math.min(this.currentPage, this.pages.length); | |
} | |
this.updatePageDisplay(); | |
this.updateCharCount(); | |
} | |
setText(text) { | |
// Just set the text without splitting into pages | |
this.fullText = text; | |
this.pages = [text]; | |
this.currentPage = 1; | |
this.updatePageDisplay(); | |
this.updateCharCount(); | |
} | |
updatePageDisplay() { | |
this.elements.pageContent.value = this.pages[this.currentPage - 1] || ''; | |
this.elements.pageInfo.textContent = `Page ${this.currentPage} of ${this.pages.length}`; | |
// Update button states | |
this.elements.prevBtn.disabled = this.currentPage === 1; | |
this.elements.nextBtn.disabled = this.currentPage === this.pages.length; | |
} | |
updateCharCount() { | |
const totalChars = this.fullText.length; | |
this.elements.charCount.textContent = `${totalChars} characters`; | |
} | |
prevPage() { | |
if (this.currentPage > 1) { | |
this.currentPage--; | |
this.updatePageDisplay(); | |
} | |
} | |
nextPage() { | |
if (this.currentPage < this.pages.length) { | |
this.currentPage++; | |
this.updatePageDisplay(); | |
} | |
} | |
getText() { | |
return this.fullText; | |
} | |
clear() { | |
this.setText(''); | |
} | |
} |