(function() {
var __cfg = document.getElementById('partial-item-text-editor-cfg');
var ITEM_ID = __cfg ? __cfg.dataset.itemId : '';
// The Write/Preview pair, the preview pane and the markdown rendering behind
// them are the described field's now (crate::quasi::rich_field, bound by
// markdown-editor.js). What used to be here was a regex over h1-h3, bold,
// italic and inline code, which is not what publishing does; the pane is
// filled from /api/preview/markdown instead. What stays is this screen's own:
// the word count, the explicit save, and the autosave clock.
function updateWordCount() {
const body = document.getElementById('text-body').value;
const words = body.trim().split(/\s+/).filter(w => w).length;
const readingTime = Math.max(1, Math.ceil(words / 200));
document.getElementById('word-count').textContent = words + ' words';
document.querySelector('.reading-time').textContent = readingTime + ' min read';
}
function saveTextContent() {
const body = document.getElementById('text-body').value;
const btn = document.getElementById('save-text-btn');
const status = document.getElementById('text-save-status');
const itemId = ITEM_ID;
btn.disabled = true;
status.innerHTML = '';
fetch('/api/items/' + itemId + '/text', {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
body: JSON.stringify({ body: body })
})
.then(res => res.json())
.then(data => {
status.innerHTML = 'Saved';
if (data.word_count !== undefined) {
document.getElementById('word-count').textContent = data.word_count + ' words';
}
})
.catch(err => {
status.innerHTML = 'Error saving';
})
.finally(() => {
btn.disabled = false;
});
}
document.getElementById('text-body').addEventListener('input', updateWordCount);
// Auto-save: debounce text body changes (30s after last keystroke)
var autoSaveStatus = document.getElementById('text-save-status');
document.getElementById('text-body').addEventListener('input', function() {
// Thirty seconds is a save cadence rather than a wait for the typing to
// stop, and `makeover-timing` names no such intent, so the number stays
// here. The closure that spent it does not.
window.timing.debounce('text-autosave', function() {
autoSaveStatus.innerHTML = 'Saving...';
var body = document.getElementById('text-body').value;
fetch('/api/items/' + ITEM_ID + '/text', {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
body: JSON.stringify({ body: body })
})
.then(function(res) { return res.json(); })
.then(function(data) {
autoSaveStatus.innerHTML = 'Auto-saved';
if (data.word_count !== undefined) {
document.getElementById('word-count').textContent = data.word_count + ' words';
}
window.timing.clearStatusLater(autoSaveStatus, 'Auto-saved');
})
.catch(function() {
autoSaveStatus.innerHTML = 'Auto-save failed';
});
}, 30000);
});
window.saveTextContent = saveTextContent;
})();