Ohjeet: screenshot-upload paste & drag-drop + kuva-lightbox

Screenshottien lisääminen ohjeisiin nyt helpompaa:
- Ctrl+V / Cmd+V: liitä kuvakaappaus suoraan leikepöydältä editoriin
- Drag & drop: raahaa kuvatiedostoja suoraan textarea-editoriin
- Upload-placeholder näkyy latauksen aikana (![Ladataan: ...]())
- Vihjeet editorin alla kertovat käytettävissä olevat tavat
- Kuva-lightbox: klikkaa kuvaa lukunäkymässä → avautuu isona
- Kuvien hover-efekti (zoom-in kursori, kevyt varjo)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 12:48:21 +02:00
parent 9cb2eeeb62
commit 42b815116b
3 changed files with 91 additions and 18 deletions

104
script.js
View File

@@ -3498,6 +3498,20 @@ let guideCategories = [];
let currentGuideId = null;
// Markdown-renderöijä (kevyt, ei ulkoisia kirjastoja)
// Kuva-lightbox ohjeissa
function openGuideLightbox(src, alt) {
let overlay = document.getElementById('guide-lightbox');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'guide-lightbox';
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);display:flex;align-items:center;justify-content:center;z-index:10000;cursor:zoom-out;padding:2rem;';
overlay.addEventListener('click', () => overlay.style.display = 'none');
document.body.appendChild(overlay);
}
overlay.innerHTML = `<img src="${src}" alt="${alt}" style="max-width:95%;max-height:95%;border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,0.5);">`;
overlay.style.display = 'flex';
}
function renderMarkdown(md) {
if (!md) return '';
let html = esc(md);
@@ -3513,8 +3527,8 @@ function renderMarkdown(md) {
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
// Kuvat (ennen linkkejä!)
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;border-radius:8px;margin:0.5rem 0;">');
// Kuvat (ennen linkkejä!) — klikkaa avataksesi isompana
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" class="guide-img" onclick="openGuideLightbox(this.src, this.alt)" title="Klikkaa suurentaaksesi">');
// Linkit
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
// Lainaukset
@@ -3746,13 +3760,15 @@ document.getElementById('btn-guide-preview-toggle')?.addEventListener('click', (
}
});
// Kuva-upload
document.getElementById('btn-guide-image')?.addEventListener('click', () => {
document.getElementById('guide-image-input')?.click();
});
document.getElementById('guide-image-input')?.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
// Kuva-upload: yhteinen upload-funktio
async function guideUploadImage(file) {
const ta = document.getElementById('guide-form-content');
if (!ta) return;
const pos = ta.selectionStart;
// Näytä upload-placeholder
const placeholder = `![Ladataan: ${file.name}...]()`;
ta.value = ta.value.substring(0, pos) + placeholder + ta.value.substring(ta.selectionEnd);
ta.focus();
const formData = new FormData();
formData.append('image', file);
try {
@@ -3761,16 +3777,70 @@ document.getElementById('guide-image-input')?.addEventListener('change', async (
});
const result = await res.json();
if (!res.ok) throw new Error(result.error || 'Virhe');
// Lisää Markdown-kuvatagi editoriin
const ta = document.getElementById('guide-form-content');
const pos = ta.selectionStart;
const mdImg = `![${file.name}](${result.url})`;
ta.value = ta.value.substring(0, pos) + mdImg + ta.value.substring(ta.selectionEnd);
ta.focus();
ta.selectionStart = ta.selectionEnd = pos + mdImg.length;
} catch (err) { alert(err.message); }
e.target.value = ''; // nollaa input
ta.value = ta.value.replace(placeholder, mdImg);
} catch (err) {
ta.value = ta.value.replace(placeholder, '');
alert('Kuvan lataus epäonnistui: ' + err.message);
}
}
// Toolbar-nappi
document.getElementById('btn-guide-image')?.addEventListener('click', () => {
document.getElementById('guide-image-input')?.click();
});
document.getElementById('guide-image-input')?.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) await guideUploadImage(file);
e.target.value = '';
});
// Paste screenshot leikepöydältä (Ctrl+V / Cmd+V)
document.getElementById('guide-form-content')?.addEventListener('paste', async (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
if (file) {
// Anna tiedostolle nimi aikaleimalla
const ext = file.type.split('/')[1] || 'png';
const named = new File([file], `screenshot-${Date.now()}.${ext}`, { type: file.type });
await guideUploadImage(named);
}
return;
}
}
});
// Drag & drop kuvat editoriin
const guideTA = document.getElementById('guide-form-content');
if (guideTA) {
guideTA.addEventListener('dragover', (e) => {
if (e.dataTransfer?.types?.includes('Files')) {
e.preventDefault();
guideTA.style.borderColor = 'var(--primary-color)';
guideTA.style.background = '#f0f7ff';
}
});
guideTA.addEventListener('dragleave', () => {
guideTA.style.borderColor = '';
guideTA.style.background = '';
});
guideTA.addEventListener('drop', async (e) => {
guideTA.style.borderColor = '';
guideTA.style.background = '';
const files = e.dataTransfer?.files;
if (!files?.length) return;
for (const file of files) {
if (file.type.startsWith('image/')) {
e.preventDefault();
await guideUploadImage(file);
}
}
});
}
// Kategorianhallinta
document.getElementById('btn-manage-guide-cats')?.addEventListener('click', () => {