Files
f3-simple-blog/public/assets/app.js
2026-03-27 14:43:08 +01:00

235 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
const on = (selector, handler) => document.querySelectorAll(selector).forEach(handler);
on('[data-copy-text]', (button) => {
button.addEventListener('click', async () => {
const text = button.getAttribute('data-copy-text') || '';
if (!text) {
return;
}
try {
await navigator.clipboard.writeText(text);
const previous = button.textContent;
button.textContent = 'Copié';
window.setTimeout(() => {
button.textContent = previous;
}, 1200);
} catch {
window.prompt('Copie ce texte :', text);
}
});
});
on('[data-confirm]', (form) => {
form.addEventListener('submit', (event) => {
const message = form.getAttribute('data-confirm') || 'Confirmer cette action ?';
if (!window.confirm(message)) {
event.preventDefault();
}
});
});
on('[data-char-count]', (field) => {
const counter = field.parentElement?.querySelector('[data-char-count-value]');
if (!counter) {
return;
}
const update = () => {
counter.textContent = String(field.value.length);
};
field.addEventListener('input', update);
update();
});
const editor = document.querySelector('[data-markdown-editor]');
const picker = document.querySelector('[data-media-picker]');
const editorLayout = document.querySelector('[data-editor-layout]');
const pickerTitle = document.querySelector('[data-media-picker-title]');
const pickerHelp = document.querySelector('[data-media-picker-help]');
const pickerClose = document.querySelector('[data-media-picker-close]');
const coverInput = document.querySelector('[data-cover-input]');
const coverPreview = document.querySelector('[data-cover-preview]');
const coverPlaceholder = document.querySelector('[data-cover-placeholder]');
const coverClear = document.querySelector('[data-cover-clear]');
let pickerMode = 'markdown';
const focusEditor = () => editor?.focus();
const updateCoverPreview = (item = null) => {
if (!coverInput || !coverPreview || !coverPlaceholder) {
return;
}
if (item && item.id && item.url) {
coverInput.value = item.id;
coverPreview.src = item.url;
coverPreview.alt = 'Image de couverture';
coverPreview.classList.remove('is-hidden');
coverPlaceholder.classList.add('is-hidden');
if (coverClear) {
coverClear.disabled = false;
}
return;
}
coverInput.value = '';
coverPreview.removeAttribute('src');
coverPreview.classList.add('is-hidden');
coverPlaceholder.classList.remove('is-hidden');
if (coverClear) {
coverClear.disabled = true;
}
};
coverClear?.addEventListener('click', () => updateCoverPreview());
if (editor) {
const replaceSelection = (before, after = '', placeholder = '') => {
const start = editor.selectionStart;
const end = editor.selectionEnd;
const selected = editor.value.slice(start, end);
const content = selected || placeholder;
const insertion = before + content + after;
editor.setRangeText(insertion, start, end, 'end');
if (!selected && placeholder) {
const cursorStart = start + before.length;
editor.setSelectionRange(cursorStart, cursorStart + placeholder.length);
}
focusEditor();
};
const prefixLines = (prefix, placeholder) => {
const start = editor.selectionStart;
const end = editor.selectionEnd;
const selected = editor.value.slice(start, end) || placeholder;
const prefixed = selected
.split('\n')
.map((line) => (line ? prefix + line : prefix.trimEnd()))
.join('\n');
editor.setRangeText(prefixed, start, end, 'end');
focusEditor();
};
on('[data-md-action]', (button) => {
button.addEventListener('click', () => {
switch (button.getAttribute('data-md-action')) {
case 'bold':
replaceSelection('**', '**', 'texte');
break;
case 'italic':
replaceSelection('*', '*', 'texte');
break;
case 'heading':
prefixLines('## ', 'Titre');
break;
case 'list':
prefixLines('- ', 'Élément');
break;
case 'quote':
prefixLines('> ', 'Citation');
break;
case 'link':
replaceSelection('[', '](https://)', 'texte');
break;
case 'code': {
const selected = editor.value.slice(editor.selectionStart, editor.selectionEnd);
replaceSelection(
selected.includes('\n') ? '```\n' : '`',
selected.includes('\n') ? '\n```' : '`',
'code'
);
break;
}
}
});
});
}
const togglePicker = (open, mode = pickerMode) => {
if (!picker) {
return;
}
pickerMode = mode;
picker.classList.toggle('is-hidden', !open);
editorLayout?.classList.toggle('is-picker-open', open);
if (!open) {
focusEditor();
return;
}
const isCover = pickerMode === 'cover';
if (pickerTitle) {
pickerTitle.textContent = isCover ? 'Choisir une couverture' : 'Insérer une image';
}
if (pickerHelp) {
pickerHelp.textContent = isCover
? 'Clique sur une image pour lutiliser comme couverture.'
: 'Clique sur une image pour linsérer dans larticle.';
}
picker.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
};
on('[data-media-picker-open]', (button) => {
button.addEventListener('click', () => {
togglePicker(true, button.getAttribute('data-media-picker-open') || 'markdown');
});
});
pickerClose?.addEventListener('click', () => togglePicker(false));
on('[data-media-picker-select]', (button) => {
button.addEventListener('click', () => {
const item = {
id: button.getAttribute('data-media-id') || '',
url: button.getAttribute('data-media-url') || '',
markdown: button.getAttribute('data-media-markdown') || '',
};
if (pickerMode === 'cover') {
updateCoverPreview(item);
togglePicker(false);
return;
}
if (!editor || !item.markdown) {
return;
}
const start = editor.selectionStart;
const end = editor.selectionEnd;
const prefix = start > 0 && !editor.value.slice(0, start).endsWith('\n\n') ? '\n\n' : '';
const suffix = end < editor.value.length && !editor.value.slice(end).startsWith('\n\n') ? '\n\n' : '';
editor.setRangeText(prefix + item.markdown + suffix, start, end, 'end');
togglePicker(false);
});
});
// Synchronise data-copy-text du bouton Markdown quand l'alt est modifié.
on('[data-alt-input]', (input) => {
const card = input.closest('.card');
const button = card?.querySelector('[data-markdown-template]');
if (!button) {
return;
}
const update = () => {
const template = button.getAttribute('data-markdown-template') || '';
const alt = input.value;
button.setAttribute('data-copy-text', template.replace('![](', '![' + alt + ']('));
};
input.addEventListener('input', update);
update();
});
})();