28 KiB
Company Edit WYSIWYG + Live Preview — Implementation Plan
Status: ✅ UKOŃCZONY (2026-02-18)
Goal: Dodać edytor wizualny Quill.js i panel podglądu na żywo do formularza edycji profilu firmy.
Architecture: Split-view layout (60% edytor / 40% preview) na desktopie, mobile bottom sheet. Quill.js v2 z CDN jako WYSIWYG dla 4 pól tekstowych (description_full, founding_history, core_values, services_offered). Preview panel ze sticky positioning, aktualizowany live via Quill text-change event.
Tech Stack: Quill.js v2 (CDN), Vanilla JS, CSS Grid, Jinja2
Staging: Wszystkie zmiany testowane na staging.nordabiznes.pl
Ważne konteksty
Blokady template w base.html
{% block extra_css %}jest WEWNĄTRZ<style>(linia 1251){% block extra_js %}jest WEWNĄTRZ<script>(linia 2024)- NIE MOŻNA dodać
<link>ani<script src>w tych blokach! - Rozwiązanie: nowy
{% block head_extra %}po</style>w base.html
Pola z WYSIWYG
| Pole | Zakładka | Obecny element |
|---|---|---|
description_full |
Opis | <textarea id="description_full" rows="10"> |
founding_history |
Opis | <textarea id="founding_history" rows="5"> |
core_values |
Opis | <textarea id="core_values" rows="4"> |
services_offered |
Usługi | <textarea id="services_offered" rows="8"> |
Pola BEZ WYSIWYG (bez zmian)
description_short(plain text, 500 znaków)technologies_used,operational_area,languages_offered(krótkie pola)
Task 1: Dodać {% block head_extra %} do base.html
Files:
- Modify:
templates/base.html:1252(po</style>, przed</head>)
Step 1: Dodaj nowy block w base.html
W templates/base.html, po linii 1252 (</style>), przed linią z analytics script, dodaj:
{% block head_extra %}{% endblock %}
To pozwala child templates dodawać zewnętrzne <link> i <script> tagi.
Step 2: Zweryfikuj że nic się nie zepsuło
Uruchom lokalnie: python3 app.py i sprawdź główną stronę / — nowy empty block nie powinien nic zmienić.
Step 3: Commit
git add templates/base.html
git commit -m "feat: add head_extra block to base.html for external CSS/JS"
Task 2: Załadować Quill.js CDN w company_edit.html
Files:
- Modify:
templates/company_edit.html(dodać block head_extra)
Step 1: Dodaj block head_extra z Quill CDN
Na początku pliku, po {% block title %}...{% endblock %} a przed {% block extra_css %}, dodaj:
{% block head_extra %}
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
{% endblock %}
Step 2: Zweryfikuj ładowanie
Uruchom lokalnie, otwórz /firma/edytuj/<id>, w DevTools Console wpisz typeof Quill — powinno zwrócić "function".
Step 3: Commit
git add templates/company_edit.html
git commit -m "feat: load Quill.js CDN in company edit template"
Task 3: Zmienić layout na grid 60/40 z panelem preview
Files:
- Modify:
templates/company_edit.html(CSS + HTML structure)
Step 1: Zmień CSS layout
W sekcji {% block extra_css %}, zmień:
.ce-container {
max-width: 860px;
margin: 0 auto;
padding: var(--spacing-md) var(--spacing-lg);
}
na:
.ce-container {
max-width: 1400px;
margin: 0 auto;
padding: var(--spacing-md) var(--spacing-lg);
}
/* Split layout: editor + preview */
.ce-layout {
display: grid;
grid-template-columns: 1fr 380px;
gap: var(--spacing-lg);
align-items: start;
}
/* Preview panel */
.ce-preview {
position: sticky;
top: 80px;
max-height: calc(100vh - 100px);
overflow-y: auto;
background: var(--surface, #fff);
border-radius: var(--radius-lg, 0.75rem);
box-shadow: var(--shadow);
padding: var(--spacing-lg);
}
.ce-preview-title {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border, #e0e4eb);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.ce-preview-title svg { width: 16px; height: 16px; }
/* Preview sections - match company_detail.html styling */
.preview-company-name {
font-size: var(--font-size-xl, 1.25rem);
font-weight: 700;
color: var(--text-primary, #303030);
margin-bottom: var(--spacing-sm);
}
.preview-short-desc {
font-size: var(--font-size-sm);
color: var(--text-secondary, #464646);
margin-bottom: var(--spacing-lg);
font-style: italic;
}
.preview-section {
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--border, #e0e4eb);
}
.preview-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.preview-section-label {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--primary, #2E4872);
margin-bottom: var(--spacing-sm);
}
.preview-section-content {
font-size: var(--font-size-sm);
color: var(--text-primary, #303030);
line-height: 1.7;
}
.preview-section-content p { margin-bottom: var(--spacing-sm); }
.preview-section-content ul, .preview-section-content ol {
padding-left: var(--spacing-lg);
margin-bottom: var(--spacing-sm);
}
.preview-section-content li { margin-bottom: 2px; }
.preview-section-content a { color: var(--primary, #2E4872); }
.preview-section-content strong { font-weight: 600; }
.preview-empty {
color: var(--text-secondary, #464646);
font-style: italic;
font-size: var(--font-size-sm);
opacity: 0.6;
}
/* Preview contact items */
.preview-contact-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-sm);
margin-bottom: var(--spacing-xs);
}
.preview-contact-item svg { width: 14px; height: 14px; color: var(--primary); flex-shrink: 0; }
/* Preview social icons */
.preview-social-item {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: var(--font-size-sm);
color: var(--primary, #2E4872);
margin-right: var(--spacing-md);
margin-bottom: var(--spacing-xs);
}
Step 2: Dodaj responsive CSS (mobile)
Zamień istniejący @media (max-width: 768px) na:
@media (max-width: 1024px) {
.ce-layout {
grid-template-columns: 1fr;
}
.ce-preview {
display: none;
}
.ce-preview-mobile-btn {
display: flex;
}
}
@media (max-width: 768px) {
.ce-header { flex-direction: column; text-align: center; }
.ce-header-actions { margin-left: 0; }
.ce-card .form-row { grid-template-columns: 1fr; }
.contact-row, .social-row { flex-wrap: wrap; }
.contact-type-select, .social-platform-select { flex: 1 1 100%; }
.contact-purpose-input { flex: 1 1 100%; }
.ce-tab { padding: var(--spacing-sm) var(--spacing-md); font-size: 13px; }
.ce-tab span.tab-label { display: none; }
}
Dodaj CSS dla mobile preview button i bottom sheet:
/* Mobile preview button & bottom sheet */
.ce-preview-mobile-btn {
display: none;
position: fixed;
bottom: var(--spacing-lg);
right: var(--spacing-lg);
z-index: 900;
padding: 12px 20px;
background: var(--primary, #2E4872);
color: white;
border: none;
border-radius: 50px;
font-size: var(--font-size-sm);
font-weight: 600;
font-family: var(--font-family);
cursor: pointer;
box-shadow: 0 4px 12px rgba(46, 72, 114, 0.3);
align-items: center;
gap: var(--spacing-sm);
transition: all 0.2s;
}
.ce-preview-mobile-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(46, 72, 114, 0.4);
}
.ce-preview-mobile-btn svg { width: 18px; height: 18px; }
.ce-preview-sheet-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 1100;
}
.ce-preview-sheet-overlay.active { display: block; }
.ce-preview-sheet {
position: fixed;
bottom: 0; left: 0; right: 0;
max-height: 80vh;
background: var(--surface, #fff);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
z-index: 1200;
overflow-y: auto;
padding: var(--spacing-lg);
transform: translateY(100%);
transition: transform 0.3s ease;
}
.ce-preview-sheet-overlay.active .ce-preview-sheet {
transform: translateY(0);
}
.ce-preview-sheet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border, #e0e4eb);
}
.ce-preview-sheet-close {
background: none;
border: 1px solid var(--border, #e0e4eb);
border-radius: var(--radius);
padding: 6px 12px;
cursor: pointer;
font-size: var(--font-size-sm);
font-family: var(--font-family);
color: var(--text-secondary);
}
Step 3: Zmień HTML structure
W {% block content %}, zamień fragment od <!-- Main card --> do końca formularza. Owiń .ce-card i nowy .ce-preview w .ce-layout:
<!-- Layout: Editor + Preview -->
<div class="ce-layout">
<!-- Left: Edit form -->
<div class="ce-card">
<!-- Tabs -->
...istniejące tabs i form bez zmian...
</div>
<!-- Right: Live Preview -->
<div class="ce-preview" id="livePreview">
<div class="ce-preview-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
Podgląd profilu
</div>
<div class="preview-company-name">{{ company.name }}</div>
<div class="preview-short-desc" id="previewShortDesc">{{ company.description_short or 'Brak krótkiego opisu' }}</div>
<div class="preview-section" id="previewDescriptionSection">
<div class="preview-section-label">Opis firmy</div>
<div class="preview-section-content" id="previewDescFull">
{% if company.description_full %}{{ company.description_full | safe }}{% else %}<span class="preview-empty">Uzupełnij opis firmy...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewHistorySection">
<div class="preview-section-label">Historia i doświadczenie</div>
<div class="preview-section-content" id="previewHistory">
{% if company.founding_history %}{{ company.founding_history | safe }}{% else %}<span class="preview-empty">Uzupełnij historię...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewValuesSection">
<div class="preview-section-label">Wartości i misja</div>
<div class="preview-section-content" id="previewValues">
{% if company.core_values %}{{ company.core_values | safe }}{% else %}<span class="preview-empty">Uzupełnij wartości...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewServicesSection">
<div class="preview-section-label">Oferowane usługi</div>
<div class="preview-section-content" id="previewServices">
{% if company.services_offered %}{{ company.services_offered | safe }}{% else %}<span class="preview-empty">Uzupełnij usługi...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewContactSection">
<div class="preview-section-label">Dane kontaktowe</div>
<div class="preview-section-content" id="previewContact">
{% if company.email %}<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>{{ company.email }}</div>{% endif %}
{% if company.phone %}<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>{{ company.phone }}</div>{% endif %}
{% if company.website %}<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>{{ company.website }}</div>{% endif %}
</div>
</div>
<div class="preview-section" id="previewSocialSection">
<div class="preview-section-label">Social Media</div>
<div class="preview-section-content" id="previewSocial">
{% for sm in social_media %}
<span class="preview-social-item">{{ sm.platform | capitalize }}</span>
{% endfor %}
{% if not social_media %}<span class="preview-empty">Brak profili social media</span>{% endif %}
</div>
</div>
</div>
</div><!-- /.ce-layout -->
<!-- Mobile preview button -->
<button type="button" class="ce-preview-mobile-btn" id="mobilePreviewBtn" onclick="openMobilePreview()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
Podgląd
</button>
<!-- Mobile preview sheet -->
<div class="ce-preview-sheet-overlay" id="previewSheetOverlay">
<div class="ce-preview-sheet" id="previewSheet">
<div class="ce-preview-sheet-header">
<span style="font-weight: 600;">Podgląd profilu</span>
<button type="button" class="ce-preview-sheet-close" onclick="closeMobilePreview()">Zamknij</button>
</div>
<div id="mobilePreviewContent">
<!-- Populated dynamically from desktop preview -->
</div>
</div>
</div>
Step 4: Zweryfikuj layout
Uruchom lokalnie, otwórz /firma/edytuj/<id>:
- Desktop: formularz po lewej (60%), preview po prawej (40%)
- Resize do <1024px: preview znika, pojawia się floating button "Podgląd"
Step 5: Commit
git add templates/company_edit.html
git commit -m "feat: split layout with live preview panel for company edit"
Task 4: Zamienić textareas na Quill.js edytory
Files:
- Modify:
templates/company_edit.html(HTML formularza + JS inicjalizacja)
Step 1: Dodaj CSS dla Quill kontenerów
W sekcji {% block extra_css %} dodaj:
/* Quill editor overrides */
.quill-container {
border: 1px solid var(--border, #e0e4eb);
border-radius: var(--radius, 0.5rem);
overflow: hidden;
background: var(--surface, #fff);
transition: var(--transition);
}
.quill-container:focus-within {
border-color: var(--primary, #2E4872);
box-shadow: 0 0 0 3px rgba(46, 72, 114, 0.1);
}
.quill-container .ql-toolbar {
border: none !important;
border-bottom: 1px solid var(--border, #e0e4eb) !important;
background: var(--background, #EDF0F5);
font-family: var(--font-family) !important;
}
.quill-container .ql-container {
border: none !important;
font-family: var(--font-family) !important;
font-size: var(--font-size-base, 1rem) !important;
}
.quill-container .ql-editor {
min-height: 120px;
line-height: 1.7;
color: var(--text-primary, #303030);
}
.quill-container .ql-editor.ql-blank::before {
color: var(--text-secondary, #464646);
opacity: 0.5;
font-style: italic;
}
.quill-container.quill-tall .ql-editor {
min-height: 200px;
}
fieldset[disabled] .quill-container {
opacity: 0.5;
pointer-events: none;
}
Step 2: Zamień textarea na Quill kontener + hidden textarea dla description_full
Zastąp obecny fragment:
<div class="form-group">
<label for="description_full" class="form-label">Pełny opis działalności</label>
<textarea id="description_full" name="description_full" class="form-input" rows="10" placeholder="Szczegółowy opis tego czym zajmuje się firma, jakie ma doświadczenie i co ją wyróżnia...">{{ company.description_full or '' }}</textarea>
<p class="form-help">Główny opis na stronie profilu firmy. Dozwolone tagi HTML: <p>, <strong>, <em>, <ul>, <li>, <a></p>
</div>
na:
<div class="form-group">
<label class="form-label">Pełny opis działalności</label>
<div class="quill-container quill-tall" id="quill-description_full"></div>
<textarea id="description_full" name="description_full" style="display:none;">{{ company.description_full or '' }}</textarea>
<p class="form-help">Użyj paska narzędzi do formatowania tekstu</p>
</div>
Step 3: Powtórz dla founding_history, core_values, services_offered
Analogicznie zamień textareas na Quill kontenery dla:
founding_history:
<div class="form-group">
<label class="form-label">Historia i doświadczenie</label>
<div class="quill-container" id="quill-founding_history"></div>
<textarea id="founding_history" name="founding_history" style="display:none;">{{ company.founding_history or '' }}</textarea>
</div>
core_values:
<div class="form-group">
<label class="form-label">Wartości i misja</label>
<div class="quill-container" id="quill-core_values"></div>
<textarea id="core_values" name="core_values" style="display:none;">{{ company.core_values or '' }}</textarea>
</div>
services_offered (w zakładce Usługi):
<div class="form-group">
<label class="form-label">Oferowane usługi i produkty</label>
<div class="quill-container quill-tall" id="quill-services_offered"></div>
<textarea id="services_offered" name="services_offered" style="display:none;">{{ company.services_offered or '' }}</textarea>
<p class="form-help">Lista usług pomagająca klientom znaleźć Twoją firmę</p>
</div>
Step 4: Dodaj JS inicjalizacji Quill w {% block extra_js %}
Na początku bloku extra_js (przed istniejącym kodem tab switching), dodaj:
// ============================================
// Quill.js WYSIWYG Initialization
// ============================================
var quillInstances = {};
var QUILL_TOOLBAR = [
['bold', 'italic'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['link'],
['clean']
];
function initQuillEditor(fieldName, placeholder) {
var container = document.getElementById('quill-' + fieldName);
var textarea = document.getElementById(fieldName);
if (!container || !textarea) return null;
var quill = new Quill(container, {
theme: 'snow',
modules: { toolbar: QUILL_TOOLBAR },
placeholder: placeholder || 'Wpisz tekst...'
});
// Load existing content from textarea
var existing = textarea.value.trim();
if (existing) {
quill.root.innerHTML = existing;
}
// Sync to hidden textarea on every change
quill.on('text-change', function() {
var html = quill.root.innerHTML;
// Quill sets empty content as <p><br></p>
textarea.value = (html === '<p><br></p>') ? '' : html;
updatePreview(fieldName, textarea.value);
});
quillInstances[fieldName] = quill;
return quill;
}
// Initialize all Quill editors (only if Quill loaded)
if (typeof Quill !== 'undefined') {
initQuillEditor('description_full', 'Szczegółowy opis tego czym zajmuje się firma...');
initQuillEditor('founding_history', 'Kiedy firma powstała, jakie ma doświadczenie...');
initQuillEditor('core_values', 'Kluczowe wartości firmy, misja...');
initQuillEditor('services_offered', 'Wymień główne usługi i produkty...');
}
Step 5: Zweryfikuj edytor
Uruchom lokalnie:
- 4 pola mają pasek narzędzi (Bold, Italic, Lista, Link, Wyczyść)
- Istniejąca treść jest załadowana
- Po edycji i kliknięciu "Zapisz" — treść zapisuje się poprawnie (hidden textarea sync)
Step 6: Commit
git add templates/company_edit.html
git commit -m "feat: replace textareas with Quill.js WYSIWYG editors"
Task 5: Podłączyć live preview do edytorów
Files:
- Modify:
templates/company_edit.html(JS w bloku extra_js)
Step 1: Dodaj funkcję updatePreview i podłącz zwykłe pola
W bloku extra_js, dodaj po inicjalizacji Quill:
// ============================================
// Live Preview Updates
// ============================================
var previewDebounceTimers = {};
function updatePreview(fieldName, value) {
clearTimeout(previewDebounceTimers[fieldName]);
previewDebounceTimers[fieldName] = setTimeout(function() {
doUpdatePreview(fieldName, value);
}, 300);
}
function doUpdatePreview(fieldName, value) {
var mapping = {
'description_short': 'previewShortDesc',
'description_full': 'previewDescFull',
'founding_history': 'previewHistory',
'core_values': 'previewValues',
'services_offered': 'previewServices'
};
var emptyTexts = {
'description_short': 'Brak krótkiego opisu',
'description_full': 'Uzupełnij opis firmy...',
'founding_history': 'Uzupełnij historię...',
'core_values': 'Uzupełnij wartości...',
'services_offered': 'Uzupełnij usługi...'
};
var targetId = mapping[fieldName];
if (!targetId) return;
var el = document.getElementById(targetId);
if (!el) return;
if (value && value.trim() && value !== '<p><br></p>') {
el.innerHTML = value;
el.classList.remove('preview-empty');
} else {
el.innerHTML = '<span class="preview-empty">' + (emptyTexts[fieldName] || '') + '</span>';
}
}
// Hook plain text fields to preview
var shortDescField = document.getElementById('description_short');
if (shortDescField) {
shortDescField.addEventListener('input', function() {
updatePreview('description_short', this.value);
});
}
// Hook contact fields to preview
function updateContactPreview() {
var email = (document.getElementById('email') || {}).value || '';
var phone = (document.getElementById('phone') || {}).value || '';
var el = document.getElementById('previewContact');
if (!el) return;
var html = '';
if (email) html += '<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>' + email + '</div>';
if (phone) html += '<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>' + phone + '</div>';
el.innerHTML = html || '<span class="preview-empty">Brak danych kontaktowych</span>';
}
['email', 'phone'].forEach(function(id) {
var field = document.getElementById(id);
if (field) field.addEventListener('input', updateContactPreview);
});
Step 2: Dodaj logikę podświetlania aktywnej sekcji w preview
Rozszerz istniejący tab switching handler — gdy użytkownik zmienia zakładkę, podświetl odpowiednią sekcję preview:
// Highlight preview section matching active tab
function highlightPreviewTab(tabName) {
var sections = document.querySelectorAll('.ce-preview .preview-section');
sections.forEach(function(s) { s.style.opacity = '0.4'; s.style.transition = 'opacity 0.3s'; });
var tabToSections = {
'description': ['previewDescriptionSection', 'previewHistorySection', 'previewValuesSection'],
'services': ['previewServicesSection'],
'contacts': ['previewContactSection'],
'social': ['previewSocialSection']
};
var active = tabToSections[tabName] || [];
active.forEach(function(id) {
var el = document.getElementById(id);
if (el) el.style.opacity = '1';
});
// Visibility tab — show all
if (tabName === 'visibility') {
sections.forEach(function(s) { s.style.opacity = '1'; });
}
}
W istniejącym tab switching callback (w tabs.forEach(function(tab)...), dodaj wywołanie highlightPreviewTab(target) po przełączeniu.
Step 3: Dodaj mobile preview JS
// Mobile preview
function openMobilePreview() {
var mobileContent = document.getElementById('mobilePreviewContent');
var desktopPreview = document.getElementById('livePreview');
if (mobileContent && desktopPreview) {
// Clone preview content (skip the title)
var clone = desktopPreview.cloneNode(true);
var title = clone.querySelector('.ce-preview-title');
if (title) title.remove();
mobileContent.innerHTML = clone.innerHTML;
}
document.getElementById('previewSheetOverlay').classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeMobilePreview() {
document.getElementById('previewSheetOverlay').classList.remove('active');
document.body.style.overflow = '';
}
// Close on overlay click
var overlay = document.getElementById('previewSheetOverlay');
if (overlay) {
overlay.addEventListener('click', function(e) {
if (e.target === this) closeMobilePreview();
});
}
Step 4: Zweryfikuj preview
- Wpisz tekst w WYSIWYG → preview aktualizuje się po 300ms
- Zmień email/telefon → preview kontaktu się aktualizuje
- Przełącz zakładki → odpowiednie sekcje preview się podświetlają
- Na mobile → przycisk "Podgląd" otwiera bottom sheet z aktualną treścią
Step 5: Commit
git add templates/company_edit.html
git commit -m "feat: connect live preview to WYSIWYG editors and form fields"
Task 6: Deploy na staging i weryfikacja
Files: Brak zmian w plikach
Step 1: Push do repozytoriów
git push origin master && git push inpi master
Step 2: Deploy na staging
ssh maciejpi@10.22.68.248 "cd /var/www/nordabiznes && sudo -u www-data git pull && sudo systemctl restart nordabiznes"
Step 3: Weryfikacja na staging
Otwórz https://staging.nordabiznes.pl w przeglądarce:
- Zaloguj się jako admin/manager
- Przejdź do
/firma/edytuj/<id>dowolnej firmy - Sprawdź:
- Quill toolbar widoczny dla 4 pól (opis, historia, wartości, usługi)
- Istniejąca treść załadowana w edytorach
- Formatowanie działa (bold, italic, listy, linki)
- Preview panel po prawej stronie (desktop)
- Preview aktualizuje się live podczas pisania
- Przełączanie zakładek podświetla sekcje preview
- Zapis formularza działa poprawnie (treść z Quill trafia do bazy)
- Po zapisie i ponownym otwarciu — formatowanie zachowane
- Na mobile (<1024px): preview ukryty, floating button "Podgląd" widoczny
- Kliknięcie "Podgląd" na mobile otwiera bottom sheet
- Zakładka "Widoczność" działa bez zmian (AJAX toggle)
- Tab "Kontakt" i "Social Media" działają bez zmian
Step 4: Commit ewentualnych poprawek po testach
Jeśli znaleziono problemy, napraw i powtórz deploy.
Podsumowanie zmian
| Plik | Typ zmiany | Opis |
|---|---|---|
templates/base.html |
1 linia | Nowy {% block head_extra %} po </style> |
templates/company_edit.html |
CSS + HTML + JS | Layout grid, Quill init, preview panel, mobile sheet |
Zero zmian backendowych — routes, models, sanitization bez zmian.