Files
sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/hero-banner.js.download
T

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 };