247 lines
12 KiB
Plaintext
247 lines
12 KiB
Plaintext
/**
|
|
* 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: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>'
|
|
};
|
|
}
|
|
|
|
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 = '<i class="fas fa-image"></i> 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 = '<i class="fas fa-arrows-alt-h"></i> 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 = '<h4 class="font-weight-bold mb-1 text-white">' + title + '</h4>';
|
|
if (sub) html += '<p class="small mb-2 text-light">' + sub + '</p>';
|
|
if (btnT) html += '<span class="btn btn-sm btn-danger font-weight-bold px-3">' + btnT + '</span>';
|
|
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 = '<i class="fas fa-images"></i> 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 };
|