339 lines
15 KiB
Plaintext
339 lines
15 KiB
Plaintext
class SISTimelineTool {
|
|
static get toolbox() {
|
|
return {
|
|
title: 'Timeline Group',
|
|
icon: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>'
|
|
};
|
|
}
|
|
|
|
constructor({ data, api, readOnly }) {
|
|
this.api = api;
|
|
this.readOnly = readOnly;
|
|
this.data = {
|
|
globalAttributes: (data && data.globalAttributes) || (data && data.attributes) || '',
|
|
items: (data && data.items && Array.isArray(data.items)) ? data.items : []
|
|
};
|
|
// Add an initial empty item if list is empty
|
|
if (this.data.items.length === 0) {
|
|
this.data.items.push({ date: '', description: '', imageUrl: '' });
|
|
}
|
|
this.wrapper = undefined;
|
|
}
|
|
|
|
render() {
|
|
this.wrapper = document.createElement('div');
|
|
this.wrapper.className = 'timeline-tool-wrapper';
|
|
this.wrapper.style.border = '1px solid #e3e6f0';
|
|
this.wrapper.style.borderRadius = '8px';
|
|
this.wrapper.style.padding = '14px';
|
|
this.wrapper.style.background = '#f8f9fc';
|
|
this.wrapper.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-stream"></i> Timeline Group Settings';
|
|
this.wrapper.appendChild(headerLabel);
|
|
|
|
const globalAttrGroup = this._createInput('e.g. data-aos="fade-up" style="..."', this.data.globalAttributes, 'global-attr', '<i class="fas fa-sliders-h"></i> Global Custom Attributes');
|
|
this.wrapper.appendChild(globalAttrGroup);
|
|
|
|
this.itemsContainer = document.createElement('div');
|
|
this.wrapper.appendChild(this.itemsContainer);
|
|
|
|
this.data.items.forEach((item, index) => {
|
|
this._renderItem(item, index);
|
|
});
|
|
|
|
if (!this.readOnly) {
|
|
const addButton = document.createElement('button');
|
|
addButton.type = 'button';
|
|
addButton.className = 'btn btn-sm btn-outline-primary mt-2';
|
|
addButton.innerHTML = '<i class="fas fa-plus"></i> Add Event';
|
|
addButton.addEventListener('click', () => {
|
|
const newItem = { date: '', description: '', imageUrl: '' };
|
|
this.data.items.push(newItem);
|
|
this._renderItem(newItem, this.data.items.length - 1);
|
|
});
|
|
this.wrapper.appendChild(addButton);
|
|
}
|
|
|
|
return this.wrapper;
|
|
}
|
|
|
|
_renderItem(item, index) {
|
|
const itemBox = document.createElement('div');
|
|
itemBox.className = 'timeline-item-edit-box mb-3 p-3 bg-white border rounded';
|
|
itemBox.style.position = 'relative';
|
|
|
|
if (!this.readOnly) {
|
|
const removeBtn = document.createElement('button');
|
|
removeBtn.type = 'button';
|
|
removeBtn.className = 'btn btn-sm btn-danger';
|
|
removeBtn.style.position = 'absolute';
|
|
removeBtn.style.top = '10px';
|
|
removeBtn.style.right = '10px';
|
|
removeBtn.innerHTML = '<i class="fas fa-times"></i>';
|
|
removeBtn.addEventListener('click', () => {
|
|
itemBox.remove();
|
|
});
|
|
itemBox.appendChild(removeBtn);
|
|
}
|
|
|
|
const dateGroup = this._createInput('Date (e.g. 10/04/1994)', item.date, 'date', '<i class="fas fa-calendar-alt"></i> Date / Milestone');
|
|
const descGroup = this._createTextarea('Description (e.g. Bệnh viện khai trương)', item.description, 'desc', '<i class="fas fa-align-left"></i> Description');
|
|
|
|
// Image input with preview, upload, and media library buttons
|
|
const imageGroup = document.createElement('div');
|
|
imageGroup.className = 'form-group mb-2';
|
|
|
|
const imageLabel = document.createElement('label');
|
|
imageLabel.className = 'small font-weight-bold text-secondary mb-1 d-block';
|
|
imageLabel.innerHTML = '<i class="fas fa-image"></i> Image URL (Optional)';
|
|
|
|
const inputRow = document.createElement('div');
|
|
inputRow.className = 'input-group input-group-sm mb-2';
|
|
|
|
const imageInput = document.createElement('input');
|
|
imageInput.type = 'text';
|
|
imageInput.className = 'form-control ce-timeline-input-image';
|
|
imageInput.placeholder = 'Paste image URL here...';
|
|
imageInput.value = item.imageUrl || '';
|
|
if (this.readOnly) imageInput.disabled = true;
|
|
|
|
const appendDiv = document.createElement('div');
|
|
appendDiv.className = 'input-group-append';
|
|
|
|
const chooseBtn = document.createElement('button');
|
|
chooseBtn.type = 'button';
|
|
chooseBtn.className = 'btn btn-info';
|
|
chooseBtn.innerHTML = '<i class="fas fa-folder-open"></i> Choose';
|
|
if (this.readOnly) chooseBtn.disabled = true;
|
|
|
|
const uploadBtn = document.createElement('button');
|
|
uploadBtn.type = 'button';
|
|
uploadBtn.className = 'btn btn-primary';
|
|
uploadBtn.innerHTML = '<i class="fas fa-upload"></i> Upload';
|
|
if (this.readOnly) uploadBtn.disabled = true;
|
|
|
|
// Hidden file input for uploading
|
|
const fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.accept = 'image/*';
|
|
fileInput.style.display = 'none';
|
|
|
|
chooseBtn.addEventListener('click', () => {
|
|
if (window.SISMediaPicker) {
|
|
SISMediaPicker.open((url, mediaObj, config) => {
|
|
imageInput.value = url;
|
|
// Store config in hidden inputs on the item box
|
|
let styleInp = itemBox.querySelector('.ce-timeline-input-imgstyle');
|
|
if (!styleInp) {
|
|
styleInp = document.createElement('input'); styleInp.type = 'hidden';
|
|
styleInp.className = 'ce-timeline-input-imgstyle'; itemBox.appendChild(styleInp);
|
|
}
|
|
let classInp = itemBox.querySelector('.ce-timeline-input-imgclass');
|
|
if (!classInp) {
|
|
classInp = document.createElement('input'); classInp.type = 'hidden';
|
|
classInp.className = 'ce-timeline-input-imgclass'; itemBox.appendChild(classInp);
|
|
}
|
|
let altInp = itemBox.querySelector('.ce-timeline-input-imgalt');
|
|
if (!altInp) {
|
|
altInp = document.createElement('input'); altInp.type = 'hidden';
|
|
altInp.className = 'ce-timeline-input-imgalt'; itemBox.appendChild(altInp);
|
|
}
|
|
let attrsInp = itemBox.querySelector('.ce-timeline-input-imgattrs');
|
|
if (!attrsInp) {
|
|
attrsInp = document.createElement('input'); attrsInp.type = 'hidden';
|
|
attrsInp.className = 'ce-timeline-input-imgattrs'; itemBox.appendChild(attrsInp);
|
|
}
|
|
let arInp = itemBox.querySelector('.ce-timeline-input-imgar');
|
|
if (!arInp) {
|
|
arInp = document.createElement('input'); arInp.type = 'hidden';
|
|
arInp.className = 'ce-timeline-input-imgar'; itemBox.appendChild(arInp);
|
|
}
|
|
if (config) {
|
|
styleInp.value = config.style || '';
|
|
classInp.value = config.cssClass || '';
|
|
altInp.value = config.alt || '';
|
|
attrsInp.value = config.customAttributes || '';
|
|
arInp.value = config.aspectRatio || '';
|
|
}
|
|
updatePreview();
|
|
});
|
|
} else {
|
|
alert('SISMediaPicker is not loaded.');
|
|
}
|
|
});
|
|
|
|
uploadBtn.addEventListener('click', () => {
|
|
fileInput.click();
|
|
});
|
|
|
|
const updatePreview = () => {
|
|
const url = imageInput.value.trim();
|
|
if (url) {
|
|
imagePreview.style.backgroundImage = 'url("' + url + '")';
|
|
imagePreview.style.display = 'block';
|
|
} else {
|
|
imagePreview.style.backgroundImage = 'none';
|
|
imagePreview.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
fileInput.addEventListener('change', (e) => {
|
|
if (e.target.files && e.target.files[0]) {
|
|
const formData = new FormData();
|
|
formData.append('file', e.target.files[0]);
|
|
|
|
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
|
uploadBtn.disabled = true;
|
|
|
|
fetch('/api/manage/media/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success === 1 && data.file && data.file.url) {
|
|
imageInput.value = data.file.url;
|
|
updatePreview();
|
|
} else {
|
|
alert('Upload failed: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('Error uploading image', err);
|
|
alert('Upload failed: ' + err.message);
|
|
})
|
|
.finally(() => {
|
|
uploadBtn.innerHTML = '<i class="fas fa-upload"></i> Upload';
|
|
uploadBtn.disabled = false;
|
|
fileInput.value = ''; // Reset
|
|
});
|
|
}
|
|
});
|
|
|
|
appendDiv.appendChild(chooseBtn);
|
|
appendDiv.appendChild(uploadBtn);
|
|
inputRow.appendChild(imageInput);
|
|
inputRow.appendChild(appendDiv);
|
|
inputRow.appendChild(fileInput);
|
|
|
|
// Image Preview Container
|
|
const imagePreview = document.createElement('div');
|
|
imagePreview.style.width = '100%';
|
|
imagePreview.style.height = '180px';
|
|
imagePreview.style.backgroundColor = '#f8f9fc';
|
|
imagePreview.style.border = '1px dashed #d1d3e2';
|
|
imagePreview.style.borderRadius = '6px';
|
|
imagePreview.style.backgroundSize = 'cover';
|
|
imagePreview.style.backgroundPosition = 'center';
|
|
imagePreview.style.display = 'none';
|
|
|
|
imageInput.addEventListener('input', updatePreview);
|
|
updatePreview();
|
|
|
|
imageGroup.appendChild(imageLabel);
|
|
imageGroup.appendChild(inputRow);
|
|
imageGroup.appendChild(imagePreview);
|
|
|
|
const attrGroup = this._createInput('e.g. data-aos="fade-right" style="..."', item.attributes, 'attr', '<i class="fas fa-sliders-h"></i> Step Custom Attributes');
|
|
|
|
itemBox.appendChild(dateGroup);
|
|
itemBox.appendChild(descGroup);
|
|
itemBox.appendChild(imageGroup);
|
|
itemBox.appendChild(attrGroup);
|
|
|
|
this.itemsContainer.appendChild(itemBox);
|
|
}
|
|
|
|
_createInput(placeholder, value, type, labelHtml) {
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'form-group mb-2';
|
|
|
|
if (labelHtml) {
|
|
const label = document.createElement('label');
|
|
label.className = 'small font-weight-bold text-secondary mb-1 d-block';
|
|
label.innerHTML = labelHtml;
|
|
wrapper.appendChild(label);
|
|
}
|
|
|
|
const inp = document.createElement('input');
|
|
inp.type = 'text';
|
|
inp.className = 'form-control form-control-sm ce-timeline-input-' + type;
|
|
inp.placeholder = placeholder;
|
|
inp.value = value || '';
|
|
if (this.readOnly) inp.disabled = true;
|
|
|
|
wrapper.appendChild(inp);
|
|
return wrapper;
|
|
}
|
|
|
|
_createTextarea(placeholder, value, type, labelHtml) {
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'form-group mb-2';
|
|
|
|
if (labelHtml) {
|
|
const label = document.createElement('label');
|
|
label.className = 'small font-weight-bold text-secondary mb-1 d-block';
|
|
label.innerHTML = labelHtml;
|
|
wrapper.appendChild(label);
|
|
}
|
|
|
|
const inp = document.createElement('textarea');
|
|
inp.className = 'form-control form-control-sm ce-timeline-input-' + type;
|
|
inp.placeholder = placeholder;
|
|
inp.value = value || '';
|
|
inp.style.minHeight = '70px';
|
|
if (this.readOnly) inp.disabled = true;
|
|
|
|
wrapper.appendChild(inp);
|
|
return wrapper;
|
|
}
|
|
|
|
save(blockContent) {
|
|
const itemBoxes = this.itemsContainer.querySelectorAll('.timeline-item-edit-box');
|
|
const items = [];
|
|
itemBoxes.forEach(box => {
|
|
const date = box.querySelector('.ce-timeline-input-date').value.trim();
|
|
const desc = box.querySelector('.ce-timeline-input-desc').value.trim();
|
|
const img = box.querySelector('.ce-timeline-input-image').value.trim();
|
|
const attrEl = box.querySelector('.ce-timeline-input-attr');
|
|
const attr = attrEl ? attrEl.value.trim() : '';
|
|
const imgStyle = (box.querySelector('.ce-timeline-input-imgstyle') || {}).value || '';
|
|
const imgClass = (box.querySelector('.ce-timeline-input-imgclass') || {}).value || '';
|
|
const imgAlt = (box.querySelector('.ce-timeline-input-imgalt') || {}).value || '';
|
|
const imgAttrs = (box.querySelector('.ce-timeline-input-imgattrs') || {}).value || '';
|
|
const imgAspectRatio = (box.querySelector('.ce-timeline-input-imgar') || {}).value || '';
|
|
if (date || desc || img || attr) {
|
|
items.push({
|
|
date: date, description: desc, imageUrl: img, attributes: attr,
|
|
imageStyle: imgStyle, imageClass: imgClass, imageAlt: imgAlt,
|
|
imageAttrs: imgAttrs, imageAspectRatio: imgAspectRatio
|
|
});
|
|
}
|
|
});
|
|
const globalAttrEl = this.wrapper.querySelector('.ce-timeline-input-global-attr');
|
|
return {
|
|
globalAttributes: globalAttrEl ? globalAttrEl.value.trim() : (this.data.globalAttributes || ''),
|
|
items: items
|
|
};
|
|
}
|
|
}
|
|
|
|
// Register the plugin globally
|
|
window.SISEditorPlugins = window.SISEditorPlugins || {};
|
|
window.SISEditorPlugins['timeline'] = {
|
|
class: SISTimelineTool
|
|
};
|
|
|
|
// openMediaPickerModal — legacy stub, replaced by SISMediaPicker
|
|
function openMediaPickerModal(onSelectCallback) {
|
|
if (window.SISMediaPicker) {
|
|
SISMediaPicker.open(function(url) { onSelectCallback(url); });
|
|
} else {
|
|
var url = prompt('Enter image URL:');
|
|
if (url) onSelectCallback(url);
|
|
}
|
|
}
|