235 lines
7.1 KiB
JavaScript
235 lines
7.1 KiB
JavaScript
(() => {
|
||
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 l’utiliser comme couverture.'
|
||
: 'Clique sur une image pour l’insérer dans l’article.';
|
||
}
|
||
|
||
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(');
|
||
};
|
||
|
||
input.addEventListener('input', update);
|
||
update();
|
||
});
|
||
})();
|