Ohjeet: kuva-upload -toiminto Markdown-editoriin
- "Kuva" -nappi toolbarissa avaa tiedostovalitsimen - Kuva uploadataan serverille (max 5MB, PNG/JPG/GIF/WebP) - Markdown  -tagi lisätään automaattisesti editoriin - Kuva renderöityy lukunäkymässä ja esikatselussa - API: guide_image_upload (upload) + guide_image (serve) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
62
api.php
62
api.php
@@ -2006,6 +2006,68 @@ switch ($action) {
|
|||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'guide_image_upload':
|
||||||
|
requireAuth();
|
||||||
|
requireAdmin();
|
||||||
|
$companyId = requireCompany();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
if (empty($_FILES['image'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Kuva puuttuu']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$file = $_FILES['image'];
|
||||||
|
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Kuvan lähetys epäonnistui']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($file['size'] > 5 * 1024 * 1024) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Kuva on liian suuri (max 5 MB)']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$allowedExt = ['png', 'jpg', 'jpeg', 'gif', 'webp'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($ext, $allowedExt)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Sallitut tiedostotyypit: PNG, JPG, GIF, WebP']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$imgDir = getCompanyDir($companyId) . '/guide_images';
|
||||||
|
if (!file_exists($imgDir)) mkdir($imgDir, 0755, true);
|
||||||
|
$filename = uniqid() . '.' . ($ext === 'jpeg' ? 'jpg' : $ext);
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $imgDir . '/' . $filename)) {
|
||||||
|
$url = 'api.php?action=guide_image&file=' . urlencode($filename);
|
||||||
|
echo json_encode(['success' => true, 'url' => $url, 'filename' => $filename]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Tallennusvirhe']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'guide_image':
|
||||||
|
requireAuth();
|
||||||
|
$companyId = requireCompany();
|
||||||
|
$filename = basename($_GET['file'] ?? '');
|
||||||
|
if (!$filename || !preg_match('/^[a-f0-9]+\.(png|jpg|gif|webp)$/', $filename)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo 'Virheellinen tiedostonimi';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$path = getCompanyDir($companyId) . '/guide_images/' . $filename;
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo 'Kuvaa ei löydy';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$mimes = ['png' => 'image/png', 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'webp' => 'image/webp'];
|
||||||
|
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||||
|
header('Content-Type: ' . ($mimes[$ext] ?? 'application/octet-stream'));
|
||||||
|
header('Cache-Control: public, max-age=86400');
|
||||||
|
readfile($path);
|
||||||
|
exit;
|
||||||
|
|
||||||
// ---------- ARCHIVE ----------
|
// ---------- ARCHIVE ----------
|
||||||
case 'archived_customers':
|
case 'archived_customers':
|
||||||
requireAuth();
|
requireAuth();
|
||||||
|
|||||||
@@ -407,6 +407,8 @@
|
|||||||
<button type="button" class="guide-tb-btn" data-md="link" title="Linkki">🔗</button>
|
<button type="button" class="guide-tb-btn" data-md="link" title="Linkki">🔗</button>
|
||||||
<button type="button" class="guide-tb-btn" data-md="code" title="Koodi"></></button>
|
<button type="button" class="guide-tb-btn" data-md="code" title="Koodi"></></button>
|
||||||
<button type="button" class="guide-tb-btn" data-md="quote" title="Lainaus">❝</button>
|
<button type="button" class="guide-tb-btn" data-md="quote" title="Lainaus">❝</button>
|
||||||
|
<button type="button" class="guide-tb-btn" id="btn-guide-image" title="Lisää kuva">📷 Kuva</button>
|
||||||
|
<input type="file" id="guide-image-input" accept="image/*" style="display:none;">
|
||||||
<span style="flex:1;"></span>
|
<span style="flex:1;"></span>
|
||||||
<button type="button" class="guide-tb-btn" id="btn-guide-preview-toggle">Esikatselu</button>
|
<button type="button" class="guide-tb-btn" id="btn-guide-preview-toggle">Esikatselu</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
28
script.js
28
script.js
@@ -3500,6 +3500,8 @@ function renderMarkdown(md) {
|
|||||||
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
||||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
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;">');
|
||||||
// Linkit
|
// Linkit
|
||||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||||
// Lainaukset
|
// Lainaukset
|
||||||
@@ -3731,6 +3733,32 @@ 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;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('image', file);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API}?action=guide_image_upload`, {
|
||||||
|
method: 'POST', credentials: 'include', body: formData
|
||||||
|
});
|
||||||
|
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 = ``;
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
// Kategorianhallinta
|
// Kategorianhallinta
|
||||||
document.getElementById('btn-manage-guide-cats')?.addEventListener('click', () => {
|
document.getElementById('btn-manage-guide-cats')?.addEventListener('click', () => {
|
||||||
renderGuideCatList();
|
renderGuideCatList();
|
||||||
|
|||||||
Reference in New Issue
Block a user