# 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 `` w base.html ### Pola z WYSIWYG | Pole | Zakładka | Obecny element | |------|----------|---------------| | `description_full` | Opis | `

Główny opis na stronie profilu firmy. Dozwolone tagi HTML: <p>, <strong>, <em>, <ul>, <li>, <a>

``` na: ```html

Użyj paska narzędzi do formatowania tekstu

``` **Step 3: Powtórz dla founding_history, core_values, services_offered** Analogicznie zamień textareas na Quill kontenery dla: `founding_history`: ```html
``` `core_values`: ```html
``` `services_offered` (w zakładce Usługi): ```html

Lista usług pomagająca klientom znaleźć Twoją firmę

``` **Step 4: Dodaj JS inicjalizacji Quill w `{% block extra_js %}`** Na początku bloku `extra_js` (przed istniejącym kodem tab switching), dodaj: ```javascript // ============================================ // 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


textarea.value = (html === '


') ? '' : 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** ```bash 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: ```javascript // ============================================ // 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 !== '


') { el.innerHTML = value; el.classList.remove('preview-empty'); } else { el.innerHTML = '' + (emptyTexts[fieldName] || '') + ''; } } // 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 += '
' + email + '
'; if (phone) html += '
' + phone + '
'; el.innerHTML = html || 'Brak danych kontaktowych'; } ['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: ```javascript // 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** ```javascript // 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** ```bash 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** ```bash git push origin master && git push inpi master ``` **Step 2: Deploy na staging** ```bash 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: 1. Zaloguj się jako admin/manager 2. Przejdź do `/firma/edytuj/` dowolnej firmy 3. 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 `` | | `templates/company_edit.html` | CSS + HTML + JS | Layout grid, Quill init, preview panel, mobile sheet | **Zero zmian backendowych** — routes, models, sanitization bez zmian.