/** * SISHeroBannerTool — Hero Banner Plugin for Editor.js * Allows content managers to build dynamic hero banners with live preview. */ class SISHeroBannerTool { static get toolbox() { return { title: 'Hero Banner', icon: '' }; } constructor({ data, api, readOnly }) { this.api = api; this.readOnly = readOnly; this.data = { title: (data && data.title) ? data.title : '', subtitle: (data && data.subtitle) ? data.subtitle : '', bgImage: (data && data.bgImage) ? data.bgImage : '', btnText: (data && data.btnText) ? data.btnText : '', btnLink: (data && data.btnLink) ? data.btnLink : '', height: (data && data.height) ? data.height : '350px', textAlign: (data && data.textAlign) ? data.textAlign : 'center', overlayOpacity: (data && data.overlayOpacity !== undefined) ? data.overlayOpacity : '0.4', stretched: (data && data.stretched !== undefined) ? !!data.stretched : true }; } render() { const container = document.createElement('div'); container.style.border = '1px solid #e3e6f0'; container.style.borderRadius = '8px'; container.style.padding = '14px'; container.style.background = '#fff'; container.style.marginBottom = '12px'; const headerLabel = document.createElement('label'); headerLabel.className = 'font-weight-bold text-primary small mb-2 d-block'; headerLabel.innerHTML = ' Hero Banner Block Settings'; container.appendChild(headerLabel); // Stretch toggle const stretchWrapper = document.createElement('div'); stretchWrapper.className = 'custom-control custom-switch mb-3'; const stretchCheck = document.createElement('input'); stretchCheck.type = 'checkbox'; stretchCheck.className = 'custom-control-input'; stretchCheck.id = 'hero_stretch_' + Math.random().toString(36).substring(7); stretchCheck.checked = !!this.data.stretched; const stretchLabel = document.createElement('label'); stretchLabel.className = 'custom-control-label small font-weight-bold text-secondary'; stretchLabel.htmlFor = stretchCheck.id; stretchLabel.innerHTML = ' Stretch Banner to Full Screen Width'; stretchCheck.addEventListener('change', () => { this.data.stretched = stretchCheck.checked; }); stretchWrapper.appendChild(stretchCheck); stretchWrapper.appendChild(stretchLabel); container.appendChild(stretchWrapper); // Inputs this.titleInput = this._createInput('Banner Title', 'e.g. Bệnh Viện Đa Khoa Quốc Tế S.I.S Cần Thơ', this.data.title); this.subtitleInput = this._createInput('Subtitle / Description', 'e.g. Trao niềm tin - Nhận sức khỏe...', this.data.subtitle); this.bgImageInput = this._createImageInput('Background Image URL', 'https://example.com/hero-banner.jpg', this.data.bgImage); // Row for Height & Button Link const configRow = document.createElement('div'); configRow.className = 'form-row'; const heightCol = document.createElement('div'); heightCol.className = 'col-md-4 mb-2'; this.heightInput = this._createInput('Banner Height (e.g. 350px, 500px, 60vh)', 'e.g. 350px', this.data.height); heightCol.appendChild(this.heightInput); const btnCol1 = document.createElement('div'); btnCol1.className = 'col-md-4 mb-2'; this.btnTextInput = this._createInput('Button Text (Optional)', 'e.g. Đặt Lịch Khám', this.data.btnText); btnCol1.appendChild(this.btnTextInput); const btnCol2 = document.createElement('div'); btnCol2.className = 'col-md-4 mb-2'; this.btnLinkInput = this._createInput('Button Link URL (Optional)', 'e.g. /dat-lich', this.data.btnLink); btnCol2.appendChild(this.btnLinkInput); configRow.appendChild(heightCol); configRow.appendChild(btnCol1); configRow.appendChild(btnCol2); // Live Preview Box const previewBox = document.createElement('div'); previewBox.className = 'hero-banner-preview p-4 rounded text-white my-2'; previewBox.style.position = 'relative'; previewBox.style.minHeight = this.data.height || '350px'; previewBox.style.display = 'flex'; previewBox.style.flexDirection = 'column'; previewBox.style.justifyContent = 'center'; previewBox.style.alignItems = 'center'; previewBox.style.textAlign = 'center'; previewBox.style.backgroundSize = 'cover'; previewBox.style.backgroundPosition = 'center'; previewBox.style.overflow = 'hidden'; const overlay = document.createElement('div'); overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.right = '0'; overlay.style.bottom = '0'; overlay.style.background = '#000'; overlay.style.zIndex = '1'; previewBox.appendChild(overlay); const contentBox = document.createElement('div'); contentBox.style.position = 'relative'; contentBox.style.zIndex = '2'; previewBox.appendChild(contentBox); const updatePreview = () => { const bg = this.bgImageInput.querySelector('input').value.trim(); const title = this.titleInput.querySelector('input').value.trim() || 'Hero Banner Title'; const sub = this.subtitleInput.querySelector('input').value.trim(); const btnT = this.btnTextInput.querySelector('input').value.trim(); const bannerH = this.heightInput.querySelector('input').value.trim() || '350px'; previewBox.style.minHeight = bannerH; previewBox.style.backgroundImage = bg ? 'url("' + bg + '")' : 'linear-gradient(135deg, #002554, #881C1C)'; overlay.style.opacity = this.data.overlayOpacity || '0.4'; let html = '

' + title + '

'; if (sub) html += '

' + sub + '

'; if (btnT) html += '' + btnT + ''; contentBox.innerHTML = html; }; [this.titleInput, this.subtitleInput, this.bgImageInput].forEach(wrapper => { const input = wrapper.querySelector('input'); input.addEventListener('input', updatePreview); if (this.readOnly) input.disabled = true; container.appendChild(wrapper); }); [this.heightInput, this.btnTextInput, this.btnLinkInput].forEach(wrapper => { const input = wrapper.querySelector('input'); input.addEventListener('input', updatePreview); if (this.readOnly) input.disabled = true; }); container.appendChild(configRow); container.appendChild(previewBox); updatePreview(); return container; } _createInput(labelText, placeholder, value) { const wrapper = document.createElement('div'); wrapper.className = 'form-group mb-2'; const lbl = document.createElement('label'); lbl.className = 'small font-weight-bold text-secondary mb-1 d-block'; lbl.innerText = labelText; const inp = document.createElement('input'); inp.type = 'text'; inp.className = 'form-control form-control-sm'; inp.placeholder = placeholder; inp.value = value || ''; wrapper.appendChild(lbl); wrapper.appendChild(inp); return wrapper; } _createImageInput(labelText, placeholder, value) { const wrapper = document.createElement('div'); wrapper.className = 'form-group mb-2'; const lbl = document.createElement('label'); lbl.className = 'small font-weight-bold text-secondary mb-1 d-block'; lbl.innerText = labelText; wrapper.appendChild(lbl); const inputGroup = document.createElement('div'); inputGroup.className = 'input-group input-group-sm'; const inp = document.createElement('input'); inp.type = 'text'; inp.className = 'form-control'; inp.placeholder = placeholder; inp.value = value || ''; if (this.readOnly) inp.disabled = true; inputGroup.appendChild(inp); if (!this.readOnly) { const appendDiv = document.createElement('div'); appendDiv.className = 'input-group-append'; const mediaBtn = document.createElement('button'); mediaBtn.type = 'button'; mediaBtn.className = 'btn btn-outline-info'; mediaBtn.innerHTML = ' Media Library'; mediaBtn.addEventListener('click', () => { if (window.SISMediaPicker) { SISMediaPicker.open((selectedUrl, mediaObj, config) => { inp.value = selectedUrl; if (config) { this._bgImageConfig = config; } // Dispatch input event to trigger preview update inp.dispatchEvent(new Event('input', { bubbles: true })); }); } else { alert('Media modal function openSISMediaModal is not available.'); } }); appendDiv.appendChild(mediaBtn); inputGroup.appendChild(appendDiv); } wrapper.appendChild(inputGroup); return wrapper; } save(blockContent) { const stretchCheck = blockContent.querySelector('.custom-control-input'); return { title: this.titleInput ? this.titleInput.querySelector('input').value : this.data.title, subtitle: this.subtitleInput ? this.subtitleInput.querySelector('input').value : this.data.subtitle, bgImage: this.bgImageInput ? this.bgImageInput.querySelector('input').value : this.data.bgImage, bgImageStyle: (this._bgImageConfig && this._bgImageConfig.style) || this.data.bgImageStyle || '', bgImageClass: (this._bgImageConfig && this._bgImageConfig.cssClass) || this.data.bgImageClass || '', bgImageAlt: (this._bgImageConfig && this._bgImageConfig.alt) || this.data.bgImageAlt || '', bgImageAttrs: (this._bgImageConfig && this._bgImageConfig.customAttributes) || this.data.bgImageAttrs || '', bgImageAspectRatio: (this._bgImageConfig && this._bgImageConfig.aspectRatio) || this.data.bgImageAspectRatio || '', btnText: this.btnTextInput ? this.btnTextInput.querySelector('input').value : this.data.btnText, btnLink: this.btnLinkInput ? this.btnLinkInput.querySelector('input').value : this.data.btnLink, height: this.heightInput ? this.heightInput.querySelector('input').value : this.data.height, textAlign: this.data.textAlign || 'center', overlayOpacity: this.data.overlayOpacity || '0.4', stretched: stretchCheck ? stretchCheck.checked : !!this.data.stretched }; } } // Register plugin globally window.SISEditorPlugins = window.SISEditorPlugins || {}; window.SISEditorPlugins['hero'] = { class: SISHeroBannerTool };