CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
- home.json gains a partners block (title/subtitle/items: name, url, logo),
rendered on the homepage under the services section (next/image logos
linking out with rel=noopener)
- CMS: partner logos uploadable from the 🎨 Logó page via POST /partner-logo
(PNG, 1 MiB cap, filename sanitized to a slug, written to public/partners/)
- schema + types extended; guide updated
Closes MITHOME-83
203 lines
12 KiB
JavaScript
203 lines
12 KiB
JavaScript
// Branding page for the Content Editor: upload/replace and edit logos with
|
|
// interactive Canvas editor (crop, zoom/pan, rotate/flip, padding, filters).
|
|
// The browser-side editor script is inlined from cms-logo-client.js.
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { LOGO_TARGETS } = require('./cms-logo');
|
|
const logoClientJs = fs.readFileSync(path.join(__dirname, 'cms-logo-client.js'), 'utf8');
|
|
|
|
|
|
const LOGO_PAGE = (csrfToken) => `<!DOCTYPE html>
|
|
<html lang="hu">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>mozdIT — Logó kezelése</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e2e8f0; line-height: 1.6; padding-bottom: 64px; }
|
|
header { background: linear-gradient(135deg,#1a1f2e,#252d40); border-bottom: 1px solid #2d3748; padding: 14px 32px; display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 10; }
|
|
header h1 { font-size: 17px; font-weight: 700; background: linear-gradient(135deg,#60a5fa,#a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
header a { color: #94a3b8; text-decoration: none; font-size: 14px; margin-left: auto; }
|
|
header a:hover { color: #e2e8f0; }
|
|
main { max-width: 720px; margin: 0 auto; padding: 28px 24px; }
|
|
.note { color: #94a3b8; font-size: 14px; margin-bottom: 22px; }
|
|
.card { background: #1a2035; border: 1px solid #2d3748; border-radius: 12px; padding: 20px 22px; margin-bottom: 18px; }
|
|
.card h2 { font-size: 16px; color: #93c5fd; margin-bottom: 4px; }
|
|
.card .where { color: #64748b; font-size: 13px; margin-bottom: 14px; }
|
|
.field-label { display: block; font-size: 13px; color: #94a3b8; margin: 12px 0 6px; }
|
|
#partner-name { width: 100%; background: #0f1420; border: 1px solid #2d3748; border-radius: 8px; color: #e2e8f0; padding: 9px 12px; font-size: 14px; margin-bottom: 10px; }
|
|
.path-out { font-family: monospace; font-size: 13px; color: #6ee7b7; margin-top: 10px; word-break: break-all; }
|
|
.preview { background: repeating-conic-gradient(#1e293b 0% 25%, #0f1420 0% 50%) 50% / 22px 22px; border: 1px solid #2d3748; border-radius: 10px; padding: 16px; margin-bottom: 14px; text-align: center; min-height: 90px; }
|
|
.preview img { max-width: 100%; max-height: 72px; }
|
|
input[type=file] { color: #94a3b8; font-size: 14px; margin-bottom: 12px; width: 100%; }
|
|
.meta { font-size: 13px; color: #94a3b8; min-height: 20px; margin-bottom: 12px; }
|
|
.actions-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
|
.btn-save { background: linear-gradient(135deg,#059669,#10b981); color: #fff; border: none; border-radius: 8px; padding: 9px 18px; font-size: 14px; font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
.btn-edit { background: linear-gradient(135deg,#3b82f6,#6366f1); color: #fff; border: none; border-radius: 8px; padding: 9px 16px; font-size: 14px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
|
|
.btn-secondary { background: #334155; color: #e2e8f0; border: 1px solid #475569; border-radius: 8px; padding: 9px 16px; font-size: 14px; font-weight: 600; cursor: pointer; }
|
|
button:hover { filter: brightness(1.1); }
|
|
button:disabled { opacity: .5; cursor: wait; }
|
|
.msg { font-size: 14px; margin-top: 12px; min-height: 20px; }
|
|
.ok { color: #6ee7b7; } .err { color: #fca5a5; }
|
|
|
|
/* Modal Styles */
|
|
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(4px); z-index: 1000; display: none; align-items: center; justify-content: center; padding: 14px; }
|
|
.modal-backdrop.open { display: flex; }
|
|
.modal-box { background: #1a2035; border: 1px solid #334155; border-radius: 14px; width: 100%; max-width: 860px; max-height: 92vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 40px rgba(0,0,0,0.6); }
|
|
.modal-header { padding: 12px 20px; border-bottom: 1px solid #2d3748; display: flex; align-items: center; justify-content: space-between; background: #141824; }
|
|
.modal-header h3 { font-size: 16px; color: #93c5fd; }
|
|
.btn-close { background: transparent; border: none; color: #94a3b8; font-size: 18px; cursor: pointer; padding: 4px 8px; }
|
|
.modal-body { display: grid; grid-template-columns: 1fr 280px; gap: 16px; padding: 16px; overflow-y: auto; max-height: calc(92vh - 120px); }
|
|
@media (max-width: 720px) { .modal-body { grid-template-columns: 1fr; } }
|
|
.canvas-container { background: repeating-conic-gradient(#1e293b 0% 25%, #0f1420 0% 50%) 50% / 20px 20px; border: 1px solid #334155; border-radius: 10px; display: flex; align-items: center; justify-content: center; min-height: 320px; position: relative; overflow: hidden; cursor: grab; user-select: none; }
|
|
.canvas-container.grabbing { cursor: grabbing; }
|
|
canvas { max-width: 100%; max-height: 100%; display: block; }
|
|
.editor-controls { display: flex; flex-direction: column; gap: 14px; font-size: 13px; color: #cbd5e1; }
|
|
.ctrl-group { background: #141824; border: 1px solid #2d3748; border-radius: 8px; padding: 10px 12px; }
|
|
.ctrl-group h4 { font-size: 12px; text-transform: uppercase; color: #94a3b8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
|
.btn-row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
|
|
.btn-tool { background: #1e293b; border: 1px solid #475569; color: #cbd5e1; border-radius: 6px; padding: 5px 9px; font-size: 12px; cursor: pointer; }
|
|
.btn-tool.active { background: #2563eb; color: #fff; border-color: #3b82f6; }
|
|
.slider-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
|
|
.slider-row label { width: 68px; font-size: 12px; color: #94a3b8; }
|
|
.slider-row input[type=range] { flex: 1; accent-color: #3b82f6; }
|
|
.slider-row span { width: 38px; font-size: 11px; text-align: right; color: #cbd5e1; }
|
|
.modal-footer { padding: 12px 20px; border-top: 1px solid #2d3748; background: #141824; display: flex; justify-content: flex-end; gap: 10px; align-items: center; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<h1>🎨 Logó kezelése</h1>
|
|
<a href="/">← Vissza a szerkesztőhöz</a>
|
|
</header>
|
|
|
|
<main>
|
|
<p class="note">Csak <strong>PNG</strong> fájl tölthető fel (max. 1 MB). A régi logóról automatikus biztonsági mentés készül. A szerkesztővel közvetlenül vágatod, méretezheted, forgathatod és korrigálhatod a logókat a mentés előtt. A változás a <strong>weboldalon a Publikálás (deploy) után</strong> jelenik meg.</p>
|
|
|
|
<div class="card">
|
|
<h2>Weboldal fejléc logója (szöveges)</h2>
|
|
<p class="where">Használat: weboldal fejléc — jelenlegi fájl: /${LOGO_TARGETS.header}</p>
|
|
<div class="preview"><img id="prev-header" src="/logo.png?variant=header&t=${Date.now()}" alt="fejléc logó előnézet"></div>
|
|
<input type="file" id="file-header" accept="image/png">
|
|
<div class="meta" id="meta-header"></div>
|
|
<div class="actions-row">
|
|
<button class="btn-save" id="btn-header" onclick="upload('header')">💾 Fejléc logó mentése</button>
|
|
<button class="btn-edit" onclick="openEditor('header')">✏️ Logó szerkesztése</button>
|
|
</div>
|
|
<p class="msg" id="msg-header"></p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>CMS logó (ikon)</h2>
|
|
<p class="where">Használat: CMS bejelentkező oldal — jelenlegi fájl: /${LOGO_TARGETS.icon}</p>
|
|
<div class="preview"><img id="prev-icon" src="/logo.png?t=${Date.now()}" alt="ikon logó előnézet"></div>
|
|
<input type="file" id="file-icon" accept="image/png">
|
|
<div class="meta" id="meta-icon"></div>
|
|
<div class="actions-row">
|
|
<button class="btn-save" id="btn-icon" onclick="upload('icon')">💾 Ikon logó mentése</button>
|
|
<button class="btn-edit" onclick="openEditor('icon')">✏️ Logó szerkesztése</button>
|
|
</div>
|
|
<p class="msg" id="msg-icon"></p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Partner logó feltöltése</h2>
|
|
<p class="where">Használat: a Kezdőlap „Partnereink" szekciójához. A feltöltés után a visszaadott elérési utat másold a partner „logo" mezőjébe (pl. /partners/nev.png).</p>
|
|
<label for="partner-name" class="field-label">Fájlnév (szóközök nélkül, pl. „acme")</label>
|
|
<input type="text" id="partner-name" placeholder="acme">
|
|
<input type="file" id="file-partner" accept="image/png">
|
|
<div class="meta" id="meta-partner"></div>
|
|
<button class="btn-save" id="btn-partner" onclick="uploadPartner()">⬆ Partner logó feltöltése</button>
|
|
<p class="msg" id="msg-partner"></p>
|
|
<p class="path-out" id="path-partner"></p>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Editor Modal -->
|
|
<div class="modal-backdrop" id="editor-modal">
|
|
<div class="modal-box">
|
|
<div class="modal-header">
|
|
<h3 id="modal-title">🎨 Logó szerkesztése</h3>
|
|
<button class="btn-close" onclick="closeEditor()">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="canvas-container" id="canvas-wrap">
|
|
<canvas id="edit-canvas" width="500" height="350"></canvas>
|
|
</div>
|
|
<div class="editor-controls">
|
|
<div class="ctrl-group">
|
|
<h4>Képarány / Vágás</h4>
|
|
<div class="btn-row" id="aspect-btns">
|
|
<button class="btn-tool" data-aspect="0" onclick="setAspect(0)">Szabad</button>
|
|
<button class="btn-tool active" data-aspect="1" onclick="setAspect(1)">1:1</button>
|
|
<button class="btn-tool" data-aspect="3" onclick="setAspect(3)">3:1</button>
|
|
<button class="btn-tool" data-aspect="4" onclick="setAspect(4)">4:1</button>
|
|
<button class="btn-tool" data-aspect="1.777" onclick="setAspect(1.777)">16:9</button>
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-group">
|
|
<h4>Méret és Pozíció</h4>
|
|
<div class="slider-row">
|
|
<label>Nagyítás:</label>
|
|
<input type="range" id="zoom-range" min="0.3" max="3" step="0.05" value="1" oninput="setZoom(this.value)">
|
|
<span id="zoom-val">100%</span>
|
|
</div>
|
|
<div class="btn-row" style="margin-top:8px;">
|
|
<button class="btn-tool" onclick="resetPan()">✛ Középre</button>
|
|
<button class="btn-tool" onclick="fitToCrop()">⊡ Keretbe illesztés</button>
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-group">
|
|
<h4>Forgatás & Tükrözés</h4>
|
|
<div class="btn-row">
|
|
<button class="btn-tool" onclick="rotate(-90)">↺ Balra 90°</button>
|
|
<button class="btn-tool" onclick="rotate(90)">↻ Jobbra 90°</button>
|
|
<button class="btn-tool" onclick="toggleFlip('h')">⇋ Vízszintes</button>
|
|
<button class="btn-tool" onclick="toggleFlip('v')">⇅ Függőleges</button>
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-group">
|
|
<h4>Margó / Padding</h4>
|
|
<div class="slider-row">
|
|
<label>Margó:</label>
|
|
<input type="range" id="pad-range" min="0" max="60" step="2" value="0" oninput="setPadding(this.value)">
|
|
<span id="pad-val">0px</span>
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-group">
|
|
<h4>Képkorrekció</h4>
|
|
<div class="slider-row">
|
|
<label>Fényerő:</label>
|
|
<input type="range" id="bright-range" min="50" max="200" value="100" oninput="setFilter('brightness', this.value)">
|
|
<span id="bright-val">100%</span>
|
|
</div>
|
|
<div class="slider-row">
|
|
<label>Kontraszt:</label>
|
|
<input type="range" id="contrast-range" min="50" max="200" value="100" oninput="setFilter('contrast', this.value)">
|
|
<span id="contrast-val">100%</span>
|
|
</div>
|
|
<div class="btn-row" style="margin-top:8px;">
|
|
<button class="btn-tool" id="btn-invert" onclick="toggleInvert()">🌓 Invertálás</button>
|
|
<button class="btn-tool" onclick="resetFilters()">↺ Alaphelyzet</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-secondary" onclick="closeEditor()">Mégse</button>
|
|
<button class="btn-save" id="modal-save-btn" onclick="saveEditedLogo()">💾 Szerkesztett logó mentése</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const CSRF_TOKEN = "${csrfToken}";
|
|
${logoClientJs}
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
module.exports = { LOGO_PAGE };
|