+
+
+
+
+
+
+
+
+ Edit Page
+ + Back to All Pages + +
+
+
+
+
+
+
+ Update Page Details
+
+
+
+ diff --git a/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage.html b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage.html new file mode 100644 index 00000000..62c01e8d --- /dev/null +++ b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage.html @@ -0,0 +1,1350 @@ + + +
+ + + + + + + +"+(l.trim()?l:a)+"
");const c=Object.keys(this.toolsTags).reduce((p,g)=>(p[g.toLowerCase()]=this.toolsTags[g].sanitizationConfig??{},p),{}),u=Object.assign({},c,o.getAllInlineToolsSanitizeConfig(),{br:{}}),h=q(l,u);!h.trim()||h.trim()===a||!d.isHTMLString(h)?await this.processText(a):await this.processText(h,!0)}async processText(e,t=!1){const{Caret:o,BlockManager:i}=this.Editor,s=t?this.processHTML(e):this.processPlain(e);if(!s.length)return;if(s.length===1){s[0].isBlock?this.processSingleBlock(s.pop()):this.processInlinePaste(s.pop());return}const a=i.currentBlock&&i.currentBlock.tool.isDefault&&i.currentBlock.isEmpty;s.map(async(l,c)=>this.insertBlock(l,c===0&&a)),i.currentBlock&&o.setToBlock(i.currentBlock,o.positions.END)}setCallback(){this.listeners.on(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}unsetCallback(){this.listeners.off(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}processTools(){const e=this.Editor.Tools.blockTools;Array.from(e.values()).forEach(this.processTool)}collectTagNames(e){return Q(e)?[e]:N(e)?Object.keys(e):[]}getTagsConfig(e){if(e.pasteConfig===!1)return;const t=e.pasteConfig.tags||[],o=[];t.forEach(i=>{const s=this.collectTagNames(i);o.push(...s),s.forEach(r=>{if(Object.prototype.hasOwnProperty.call(this.toolsTags,r)){T(`Paste handler for «${e.name}» Tool on «${r}» tag is skipped because it is already used by «${this.toolsTags[r].tool.name}» Tool.`,"warn");return}const a=N(i)?i[r]:null;this.toolsTags[r.toUpperCase()]={tool:e,sanitizationConfig:a}})}),this.tagsByTool[e.name]=o.map(i=>i.toUpperCase())}getFilesConfig(e){if(e.pasteConfig===!1)return;const{files:t={}}=e.pasteConfig;let{extensions:o,mimeTypes:i}=t;!o&&!i||(o&&!Array.isArray(o)&&(T(`«extensions» property of the onDrop config for «${e.name}» Tool should be an array`),o=[]),i&&!Array.isArray(i)&&(T(`«mimeTypes» property of the onDrop config for «${e.name}» Tool should be an array`),i=[]),i&&(i=i.filter(s=>Jn(s)?!0:(T(`MIME type value «${s}» for the «${e.name}» Tool is not a valid MIME type`,"warn"),!1))),this.toolsFiles[e.name]={extensions:o||[],mimeTypes:i||[]})}getPatternsConfig(e){e.pasteConfig===!1||!e.pasteConfig.patterns||V(e.pasteConfig.patterns)||Object.entries(e.pasteConfig.patterns).forEach(([t,o])=>{o instanceof RegExp||T(`Pattern ${o} for «${e.name}» Tool is skipped because it should be a Regexp instance.`,"warn"),this.toolsPatterns.push({key:t,pattern:o,tool:e})})}isNativeBehaviour(e){return d.isNativeInput(e)}async processFiles(e){const{BlockManager:t}=this.Editor;let o;o=await Promise.all(Array.from(e).map(r=>this.processFile(r))),o=o.filter(r=>!!r);const s=t.currentBlock.tool.isDefault&&t.currentBlock.isEmpty;o.forEach((r,a)=>{t.paste(r.type,r.event,a===0&&s)})}async processFile(e){const t=Gn(e),o=Object.entries(this.toolsFiles).find(([r,{mimeTypes:a,extensions:l}])=>{const[c,u]=e.type.split("/"),h=l.find(g=>g.toLowerCase()===t.toLowerCase()),p=a.find(g=>{const[f,v]=g.split("/");return f===c&&(v===u||v==="*")});return!!h||!!p});if(!o)return;const[i]=o;return{event:this.composePasteEvent("file",{file:e}),type:i}}processHTML(e){const{Tools:t}=this.Editor,o=d.make("DIV");return o.innerHTML=e,this.getNodes(o).map(s=>{let r,a=t.defaultTool,l=!1;switch(s.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:r=d.make("div"),r.appendChild(s);break;case Node.ELEMENT_NODE:r=s,l=!0,this.toolsTags[r.tagName]&&(a=this.toolsTags[r.tagName].tool);break}const{tags:c}=a.pasteConfig||{tags:[]},u=c.reduce((g,f)=>(this.collectTagNames(f).forEach(O=>{const S=N(f)?f[O]:null;g[O.toLowerCase()]=S||{}}),g),{}),h=Object.assign({},u,a.baseSanitizeConfig);if(r.tagName.toLowerCase()==="table"){const g=q(r.outerHTML,h);r=d.make("div",void 0,{innerHTML:g}).firstChild}else r.innerHTML=q(r.innerHTML,h);const p=this.composePasteEvent("tag",{data:r});return{content:r,isBlock:l,tool:a.name,event:p}}).filter(s=>{const r=d.isEmpty(s.content),a=d.isSingleTag(s.content);return!r||a})}processPlain(e){const{defaultBlock:t}=this.config;if(!e)return[];const o=t;return e.split(/\r?\n/).filter(i=>i.trim()).map(i=>{const s=d.make("div");s.textContent=i;const r=this.composePasteEvent("tag",{data:s});return{content:s,tool:o,isBlock:!1,event:r}})}async processSingleBlock(e){const{Caret:t,BlockManager:o}=this.Editor,{currentBlock:i}=o;if(!i||e.tool!==i.name||!d.containsOnlyInlineElements(e.content.innerHTML)){this.insertBlock(e,(i==null?void 0:i.tool.isDefault)&&i.isEmpty);return}t.insertContentAtCaretPosition(e.content.innerHTML)}async processInlinePaste(e){const{BlockManager:t,Caret:o}=this.Editor,{content:i}=e;if(t.currentBlock&&t.currentBlock.tool.isDefault&&i.textContent.length' + 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 }; diff --git a/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/hover-card.js.download b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/hover-card.js.download new file mode 100644 index 00000000..53427d87 --- /dev/null +++ b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/hover-card.js.download @@ -0,0 +1,473 @@ +/** + * SISHoverCardTool — Dedicated Hover Cards Block Tool for Editor.js + * Allows content managers to freely add, remove, reorder, and configure interactive hover cards. + */ +class SISHoverCardTool { + static get toolbox() { + return { + title: 'Hover Cards', + icon: '' + }; + } + + constructor({ data, api, readOnly }) { + this.api = api; + this.readOnly = readOnly; + this.instanceId = 'hc_' + Math.random().toString(36).substring(7); + this.data = { + cols: parseInt(data && data.cols) || 4, + styleVariant: (data && data.styleVariant) || 'v1', + globalClass: (data && data.globalClass) || 'sis-hover-card-section', + globalId: (data && data.globalId) || '', + globalAttributes: (data && data.globalAttributes) || (data && data.attributes) || '', + items: (data && data.items && Array.isArray(data.items)) ? data.items : [] + }; + + // Add initial default item if list is empty + if (this.data.items.length === 0) { + this.data.items.push({ + title: 'Card Title', + description: 'Card description text...', + imageUrl: '', + active: true + }); + } + this.wrapper = undefined; + } + + render() { + this.wrapper = document.createElement('div'); + this.wrapper.className = 'p-3 bg-light border rounded mb-3 ce-hovercard-tool-wrapper'; + this.wrapper.style.fontFamily = 'inherit'; + + const titleLabel = document.createElement('div'); + titleLabel.className = 'font-weight-bold text-primary small mb-3'; + titleLabel.innerHTML = ' Dedicated Hover Cards Settings'; + this.wrapper.appendChild(titleLabel); + + // Global Settings Row + const settingsRow = document.createElement('div'); + settingsRow.className = 'form-row mb-3 pb-2 border-bottom'; + + // 1. Cols selector + const colDiv = document.createElement('div'); + colDiv.className = 'col-md-3 mb-2'; + colDiv.innerHTML = ''; + const colSelect = document.createElement('select'); + colSelect.className = 'form-control form-control-sm'; + [1, 2, 3, 4, 6].forEach(num => { + const opt = document.createElement('option'); + opt.value = num; + opt.textContent = `${num} Column${num > 1 ? 's' : ''}`; + if (num === this.data.cols) opt.selected = true; + colSelect.appendChild(opt); + }); + if (this.readOnly) colSelect.disabled = true; + colSelect.addEventListener('change', (e) => { + this.data.cols = parseInt(e.target.value) || 4; + }); + this.colsSelect = colSelect; + colDiv.appendChild(colSelect); + settingsRow.appendChild(colDiv); + + // 2. Style Variant Selector (Hover 1 vs Hover 2) + const variantDiv = document.createElement('div'); + variantDiv.className = 'col-md-3 mb-2'; + variantDiv.innerHTML = ''; + const variantSelect = document.createElement('select'); + variantSelect.className = 'form-control form-control-sm'; + + const optV1 = document.createElement('option'); + optV1.value = 'v1'; + optV1.textContent = 'Hover 1 (Standard Light)'; + if (this.data.styleVariant === 'v1') optV1.selected = true; + variantSelect.appendChild(optV1); + + const optV2 = document.createElement('option'); + optV2.value = 'v2'; + optV2.textContent = 'Hover 2 (Modern Blue & Glow)'; + if (this.data.styleVariant === 'v2') optV2.selected = true; + variantSelect.appendChild(optV2); + + if (this.readOnly) variantSelect.disabled = true; + variantSelect.addEventListener('change', (e) => { + this.data.styleVariant = e.target.value; + }); + this.variantSelect = variantSelect; + variantDiv.appendChild(variantSelect); + settingsRow.appendChild(variantDiv); + + // 2. Global CSS Class + const classDiv = document.createElement('div'); + classDiv.className = 'col-md-3 mb-2'; + classDiv.innerHTML = ''; + const classInput = document.createElement('input'); + classInput.type = 'text'; + classInput.className = 'form-control form-control-sm'; + classInput.placeholder = 'e.g. sis-hover-card-section'; + classInput.value = this.data.globalClass || ''; + if (this.readOnly) classInput.disabled = true; + classInput.addEventListener('input', (e) => this.data.globalClass = e.target.value.trim()); + this.globalClassInput = classInput; + classDiv.appendChild(classInput); + settingsRow.appendChild(classDiv); + + // 3. Global HTML ID + const idDiv = document.createElement('div'); + idDiv.className = 'col-md-3 mb-2'; + idDiv.innerHTML = ''; + const idInput = document.createElement('input'); + idInput.type = 'text'; + idInput.className = 'form-control form-control-sm'; + idInput.placeholder = 'e.g. hover-cards-section'; + idInput.value = this.data.globalId || ''; + if (this.readOnly) idInput.disabled = true; + idInput.addEventListener('input', (e) => this.data.globalId = e.target.value.trim()); + this.globalIdInput = idInput; + idDiv.appendChild(idInput); + settingsRow.appendChild(idDiv); + + // 4. Global Custom Attributes + const attrDiv = document.createElement('div'); + attrDiv.className = 'col-md-3 mb-2'; + attrDiv.innerHTML = ''; + const attrInput = document.createElement('input'); + attrInput.type = 'text'; + attrInput.className = 'form-control form-control-sm'; + attrInput.placeholder = 'e.g. data-aos="fade-up" style="..."'; + attrInput.value = this.data.globalAttributes || ''; + if (this.readOnly) attrInput.disabled = true; + attrInput.addEventListener('input', (e) => this.data.globalAttributes = e.target.value.trim()); + this.globalAttrInput = attrInput; + attrDiv.appendChild(attrInput); + settingsRow.appendChild(attrDiv); + + this.wrapper.appendChild(settingsRow); + + // Cards items container + this.itemsContainer = document.createElement('div'); + this.wrapper.appendChild(this.itemsContainer); + + this._renderAllItems(); + + if (!this.readOnly) { + const addButton = document.createElement('button'); + addButton.type = 'button'; + addButton.className = 'btn btn-sm btn-success mt-2'; + addButton.innerHTML = ' Add Hover Card'; + addButton.addEventListener('click', () => { + this.data.items.push({ + title: 'New Card Title', + description: 'New card description text...', + imageUrl: '', + active: false + }); + this._renderAllItems(); + }); + this.wrapper.appendChild(addButton); + } + + return this.wrapper; + } + + _renderAllItems() { + this.itemsContainer.innerHTML = ''; + this.data.items.forEach((item, index) => { + this._renderItem(item, index); + }); + } + + _renderItem(item, index) { + const itemBox = document.createElement('div'); + itemBox.className = 'mb-3 p-3 bg-white border rounded shadow-sm'; + itemBox.style.position = 'relative'; + + // Top Control Action buttons (Move Up, Move Down, Delete) + if (!this.readOnly) { + const actionGroup = document.createElement('div'); + actionGroup.style.position = 'absolute'; + actionGroup.style.top = '10px'; + actionGroup.style.right = '10px'; + actionGroup.className = 'btn-group btn-group-sm'; + + if (index > 0) { + const upBtn = document.createElement('button'); + upBtn.type = 'button'; + upBtn.className = 'btn btn-outline-secondary'; + upBtn.innerHTML = ''; + upBtn.title = 'Move Up'; + upBtn.addEventListener('click', () => { + const temp = this.data.items[index]; + this.data.items[index] = this.data.items[index - 1]; + this.data.items[index - 1] = temp; + this._renderAllItems(); + }); + actionGroup.appendChild(upBtn); + } + + if (index < this.data.items.length - 1) { + const downBtn = document.createElement('button'); + downBtn.type = 'button'; + downBtn.className = 'btn btn-outline-secondary'; + downBtn.innerHTML = ''; + downBtn.title = 'Move Down'; + downBtn.addEventListener('click', () => { + const temp = this.data.items[index]; + this.data.items[index] = this.data.items[index + 1]; + this.data.items[index + 1] = temp; + this._renderAllItems(); + }); + actionGroup.appendChild(downBtn); + } + + const removeBtn = document.createElement('button'); + removeBtn.type = 'button'; + removeBtn.className = 'btn btn-danger'; + removeBtn.innerHTML = ''; + removeBtn.title = 'Remove Card'; + removeBtn.addEventListener('click', () => { + this.data.items.splice(index, 1); + this._renderAllItems(); + }); + actionGroup.appendChild(removeBtn); + + itemBox.appendChild(actionGroup); + } + + // Header Label + const itemHeader = document.createElement('h6'); + itemHeader.className = 'font-weight-bold text-dark mb-3'; + itemHeader.innerHTML = ` Card #${index + 1}`; + itemBox.appendChild(itemHeader); + + // 1. Card Title + const titleGroup = document.createElement('div'); + titleGroup.className = 'form-group mb-2'; + titleGroup.innerHTML = ''; + const titleInput = document.createElement('input'); + titleInput.type = 'text'; + titleInput.className = 'form-control form-control-sm'; + titleInput.placeholder = 'e.g. Tiên phong'; + titleInput.value = item.title || ''; + if (this.readOnly) titleInput.disabled = true; + titleInput.addEventListener('input', (e) => { item.title = e.target.value; }); + titleGroup.appendChild(titleInput); + itemBox.appendChild(titleGroup); + + // 2. Card Description + const descGroup = document.createElement('div'); + descGroup.className = 'form-group mb-2'; + descGroup.innerHTML = ''; + const descInput = document.createElement('textarea'); + descInput.className = 'form-control form-control-sm'; + descInput.rows = 2; + descInput.placeholder = 'e.g. Phát triển và ứng dụng kỹ thuật tiên tiến...'; + descInput.value = item.description || ''; + if (this.readOnly) descInput.disabled = true; + descInput.addEventListener('input', (e) => { item.description = e.target.value; }); + descGroup.appendChild(descInput); + itemBox.appendChild(descGroup); + + // 3. Image URL + Media Library Selector + const imgGroup = document.createElement('div'); + imgGroup.className = 'form-group mb-2'; + imgGroup.innerHTML = ''; + + const inputGroup = document.createElement('div'); + inputGroup.className = 'input-group input-group-sm mb-2'; + + const imgInput = document.createElement('input'); + imgInput.type = 'text'; + imgInput.className = 'form-control'; + imgInput.placeholder = '/uploads/2026/07/icon.jpg'; + imgInput.value = item.imageUrl || ''; + if (this.readOnly) imgInput.disabled = true; + + const previewContainer = document.createElement('div'); + previewContainer.className = 'mt-1'; + const updatePreview = (url) => { + previewContainer.innerHTML = ''; + if (url) { + const img = document.createElement('img'); + img.src = url; + img.style.maxHeight = '60px'; + img.style.maxWidth = '100px'; + img.style.objectFit = 'contain'; + img.className = 'border rounded p-1 bg-light'; + previewContainer.appendChild(img); + } + }; + updatePreview(item.imageUrl); + + imgInput.addEventListener('input', (e) => { + item.imageUrl = e.target.value.trim(); + updatePreview(item.imageUrl); + }); + + inputGroup.appendChild(imgInput); + + 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) => { + imgInput.value = selectedUrl; + item.imageUrl = selectedUrl; + if (config) { + item.imageStyle = config.style || ''; + item.imageClass = config.cssClass || ''; + item.imageAlt = config.alt || ''; + item.imageAttrs = config.customAttributes || ''; + item.imageAspectRatio = config.aspectRatio || ''; + } + updatePreview(selectedUrl); + }); + } else { + alert('Media modal function openSISMediaModal is not available.'); + } + }); + appendDiv.appendChild(mediaBtn); + inputGroup.appendChild(appendDiv); + } + + imgGroup.appendChild(inputGroup); + imgGroup.appendChild(previewContainer); + itemBox.appendChild(imgGroup); + + // 4. Hover Icon / Image URL (Swapped on hover) + const hoverImgGroup = document.createElement('div'); + hoverImgGroup.className = 'form-group mb-2'; + hoverImgGroup.innerHTML = ''; + + const hoverInputGroup = document.createElement('div'); + hoverInputGroup.className = 'input-group input-group-sm mb-2'; + + const hoverImgInput = document.createElement('input'); + hoverImgInput.type = 'text'; + hoverImgInput.className = 'form-control'; + hoverImgInput.placeholder = '/uploads/2026/07/hover-icon.jpg (optional)'; + hoverImgInput.value = item.hoverImageUrl || ''; + if (this.readOnly) hoverImgInput.disabled = true; + + const hoverPreviewContainer = document.createElement('div'); + hoverPreviewContainer.className = 'mt-1'; + const updateHoverPreview = (url) => { + hoverPreviewContainer.innerHTML = ''; + if (url) { + const img = document.createElement('img'); + img.src = url; + img.style.maxHeight = '60px'; + img.style.maxWidth = '100px'; + img.style.objectFit = 'contain'; + img.className = 'border rounded p-1 bg-info text-white'; + hoverPreviewContainer.appendChild(img); + } + }; + updateHoverPreview(item.hoverImageUrl); + + hoverImgInput.addEventListener('input', (e) => { + item.hoverImageUrl = e.target.value.trim(); + updateHoverPreview(item.hoverImageUrl); + }); + + hoverInputGroup.appendChild(hoverImgInput); + + 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) => { + hoverImgInput.value = selectedUrl; + item.hoverImageUrl = selectedUrl; + if (config) { + item.hoverImageStyle = config.style || ''; + item.hoverImageClass = config.cssClass || ''; + item.hoverImageAlt = config.alt || ''; + item.hoverImageAttrs = config.customAttributes || ''; + item.hoverImageAspectRatio = config.aspectRatio || ''; + } + updateHoverPreview(selectedUrl); + }); + } else { + alert('Media modal function openSISMediaModal is not available.'); + } + }); + appendDiv.appendChild(mediaBtn); + hoverInputGroup.appendChild(appendDiv); + } + + hoverImgGroup.appendChild(hoverInputGroup); + hoverImgGroup.appendChild(hoverPreviewContainer); + // Custom Attributes field for individual card + const cardAttrDiv = document.createElement('div'); + cardAttrDiv.className = 'form-group mb-2'; + cardAttrDiv.innerHTML = ''; + const cardAttrInput = document.createElement('input'); + cardAttrInput.type = 'text'; + cardAttrInput.className = 'form-control form-control-sm'; + cardAttrInput.placeholder = 'e.g. data-aos="fade-up" style="background:#fff"'; + cardAttrInput.value = item.attributes || ''; + if (this.readOnly) cardAttrInput.disabled = true; + cardAttrInput.addEventListener('input', (e) => { + item.attributes = e.target.value.trim(); + }); + cardAttrDiv.appendChild(cardAttrInput); + itemBox.appendChild(cardAttrDiv); + + // Default Active Radio (Only 1 option active at a time) + const activeRadioDiv = document.createElement('div'); + activeRadioDiv.className = 'custom-control custom-radio mt-2'; + const activeRadio = document.createElement('input'); + activeRadio.type = 'radio'; + activeRadio.name = 'hovercard_active_' + this.instanceId; + activeRadio.className = 'custom-control-input'; + const radioId = 'hovercard_active_' + index + '_' + Math.random().toString(36).substring(7); + activeRadio.id = radioId; + activeRadio.checked = !!item.active; + if (this.readOnly) activeRadio.disabled = true; + + activeRadio.addEventListener('change', () => { + this.data.items.forEach((it, i) => { + it.active = (i === index); + }); + }); + + const activeLabel = document.createElement('label'); + activeLabel.className = 'custom-control-label small font-weight-bold text-secondary'; + activeLabel.htmlFor = radioId; + activeLabel.innerHTML = 'Default Active Card (Only 1 active at a time)'; + + activeRadioDiv.appendChild(activeRadio); + activeRadioDiv.appendChild(activeLabel); + itemBox.appendChild(activeRadioDiv); + + this.itemsContainer.appendChild(itemBox); + } + + save() { + return { + cols: this.colsSelect ? parseInt(this.colsSelect.value) : (this.data.cols || 4), + styleVariant: this.variantSelect ? this.variantSelect.value : (this.data.styleVariant || 'v1'), + globalClass: this.globalClassInput ? this.globalClassInput.value.trim() : (this.data.globalClass || 'sis-hover-card-section'), + globalId: this.globalIdInput ? this.globalIdInput.value.trim() : (this.data.globalId || ''), + globalAttributes: this.globalAttrInput ? this.globalAttrInput.value.trim() : (this.data.globalAttributes || ''), + items: this.data.items || [] + }; + } +} + +// Register the plugin globally +window.SISEditorPlugins = window.SISEditorPlugins || {}; +window.SISEditorPlugins['hovercard'] = { class: SISHoverCardTool }; diff --git a/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/html-snippet.js.download b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/html-snippet.js.download new file mode 100644 index 00000000..7fb75843 --- /dev/null +++ b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/html-snippet.js.download @@ -0,0 +1,91 @@ +/** + * Custom Editor.js Block Tool for embedding predefined HTML snippets. + * Uses iframe for WYSIWYG preview - snippets look exactly like on the live page. + */ +class HtmlSnippetTool { + static get toolbox() { + return { + title: 'HTML Snippet', + icon: '' + }; + } + + constructor({ data, api }) { + this.data = { + id: data.id || '', + globalAttributes: data.globalAttributes || data.attributes || '' + }; + this.api = api; + this.wrapper = undefined; + } + + render() { + this.wrapper = document.createElement('div'); + this.wrapper.classList.add('ce-snippet-wrapper'); + this.wrapper.style.border = '1px solid #ddd'; + this.wrapper.style.borderRadius = '5px'; + this.wrapper.style.background = '#fafafa'; + this.wrapper.style.width = '100%'; + this.wrapper.style.boxSizing = 'border-box'; + + this._showInput(); + + return this.wrapper; + } + + _showInput() { + this.wrapper.innerHTML = ''; + + const title = document.createElement('h4'); + title.style.marginTop = '0'; + title.style.marginBottom = '10px'; + title.style.padding = '15px 15px 0 15px'; + title.innerText = 'HTML Snippet ID & Custom Attributes:'; + title.style.fontSize = '14px'; + title.style.color = '#333'; + + const inputContainer = document.createElement('div'); + inputContainer.style.display = 'flex'; + inputContainer.style.gap = '10px'; + inputContainer.style.padding = '0 15px 15px 15px'; + + const input = document.createElement('input'); + input.classList.add('ce-input'); + input.placeholder = 'Enter Snippet ID (e.g., test_banner)'; + input.value = this.data.id; + input.style.flex = '1'; + + input.addEventListener('input', (e) => { + this.data.id = e.target.value.trim(); + }); + + const attrInput = document.createElement('input'); + attrInput.classList.add('ce-input'); + attrInput.placeholder = 'Custom Attributes (e.g. data-aos="fade-up" style="...")'; + attrInput.value = this.data.globalAttributes || ''; + attrInput.style.flex = '1'; + + attrInput.addEventListener('input', (e) => { + this.data.globalAttributes = e.target.value.trim(); + }); + + inputContainer.appendChild(input); + inputContainer.appendChild(attrInput); + + this.wrapper.appendChild(title); + this.wrapper.appendChild(inputContainer); + } + + save(blockContent) { + return { + id: this.data.id, + globalAttributes: this.data.globalAttributes || '' + }; + } +} + +// Register the plugin globally +window.SISEditorPlugins = window.SISEditorPlugins || {}; +window.SISEditorPlugins['snippet'] = { + class: HtmlSnippetTool +}; diff --git a/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/image.umd.js.download b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/image.umd.js.download new file mode 100644 index 00000000..fcf6cb7a --- /dev/null +++ b/sisvietnamvn_Trang chính thức hiện tại/Edit Page - SIS Vietnam - Manage_files/image.umd.js.download @@ -0,0 +1,32 @@ +(function(){"use strict";try{if(typeof document<"u"){var o=document.createElement("style");o.appendChild(document.createTextNode('.image-tool{--bg-color: #cdd1e0;--front-color: #388ae5;--border-color: #e8e8eb}.image-tool__image{border-radius:3px;overflow:hidden;margin-bottom:10px}.image-tool__image-picture{max-width:100%;vertical-align:bottom;display:block}.image-tool__image-preloader{width:50px;height:50px;border-radius:50%;background-size:cover;margin:auto;position:relative;background-color:var(--bg-color);background-position:center center}.image-tool__image-preloader:after{content:"";position:absolute;z-index:3;width:60px;height:60px;border-radius:50%;border:2px solid var(--bg-color);border-top-color:var(--front-color);left:50%;top:50%;margin-top:-30px;margin-left:-30px;animation:image-preloader-spin 2s infinite linear;box-sizing:border-box}.image-tool__caption[contentEditable=true][data-placeholder]:before{position:absolute!important;content:attr(data-placeholder);color:#707684;font-weight:400;display:none}.image-tool__caption[contentEditable=true][data-placeholder]:empty:before{display:block}.image-tool__caption[contentEditable=true][data-placeholder]:empty:focus:before{display:none}.image-tool--empty .image-tool__image,.image-tool--empty .image-tool__caption,.image-tool--loading .image-tool__caption{display:none}.image-tool .cdx-button{display:flex;align-items:center;justify-content:center}.image-tool .cdx-button svg{height:auto;margin:0 6px 0 0}.image-tool--filled .cdx-button,.image-tool--filled .image-tool__image-preloader{display:none}.image-tool--loading .image-tool__image{min-height:200px;display:flex;border:1px solid var(--border-color);background-color:#fff}.image-tool--loading .image-tool__image-picture,.image-tool--loading .cdx-button{display:none}.image-tool--withBorder .image-tool__image{border:1px solid var(--border-color)}.image-tool--withBackground .image-tool__image{padding:15px;background:var(--bg-color)}.image-tool--withBackground .image-tool__image-picture{max-width:60%;margin:0 auto}.image-tool--stretched .image-tool__image-picture{width:100%}@keyframes image-preloader-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}')),document.head.appendChild(o)}}catch(e){console.error("vite-plugin-css-injected-by-js",e)}})(); +(function(_,O){typeof exports=="object"&&typeof module<"u"?module.exports=O():typeof define=="function"&&define.amd?define(O):(_=typeof globalThis<"u"?globalThis:_||self,_.ImageTool=O())})(this,function(){"use strict";const _="",O='',D='',R='',x='';function M(E,a=null,i={}){const c=document.createElement(E);Array.isArray(a)?c.classList.add(...a):a&&c.classList.add(a);for(const r in i)c[r]=i[r];return c}class T{constructor({api:a,config:i,onSelectFile:c,readOnly:r}){this.api=a,this.config=i,this.onSelectFile=c,this.readOnly=r,this.nodes={wrapper:M("div",[this.CSS.baseClass,this.CSS.wrapper]),imageContainer:M("div",[this.CSS.imageContainer]),fileButton:this.createFileButton(),imageEl:void 0,imagePreloader:M("div",this.CSS.imagePreloader),caption:M("div",[this.CSS.input,this.CSS.caption],{contentEditable:!this.readOnly})},this.nodes.caption.dataset.placeholder=this.config.captionPlaceholder,this.nodes.imageContainer.appendChild(this.nodes.imagePreloader),this.nodes.wrapper.appendChild(this.nodes.imageContainer),this.nodes.wrapper.appendChild(this.nodes.caption),this.nodes.wrapper.appendChild(this.nodes.fileButton)}get CSS(){return{baseClass:this.api.styles.block,loading:this.api.styles.loader,input:this.api.styles.input,button:this.api.styles.button,wrapper:"image-tool",imageContainer:"image-tool__image",imagePreloader:"image-tool__image-preloader",imageEl:"image-tool__image-picture",caption:"image-tool__caption"}}static get status(){return{EMPTY:"empty",UPLOADING:"loading",FILLED:"filled"}}render(a){return!a.file||Object.keys(a.file).length===0?this.toggleStatus(T.status.EMPTY):this.toggleStatus(T.status.UPLOADING),this.nodes.wrapper}createFileButton(){const a=M("div",[this.CSS.button]);return a.innerHTML=this.config.buttonContent||`${R} ${this.api.i18n.t("Select an Image")}`,a.addEventListener("click",()=>{this.onSelectFile()}),a}showPreloader(a){this.nodes.imagePreloader.style.backgroundImage=`url(${a})`,this.toggleStatus(T.status.UPLOADING)}hidePreloader(){this.nodes.imagePreloader.style.backgroundImage="",this.toggleStatus(T.status.EMPTY)}fillImage(a){const i=/\.mp4$/.test(a)?"VIDEO":"IMG",c={src:a};let r="load";i==="VIDEO"&&(c.autoplay=!0,c.loop=!0,c.muted=!0,c.playsinline=!0,r="loadeddata"),this.nodes.imageEl=M(i,this.CSS.imageEl,c),this.nodes.imageEl.addEventListener(r,()=>{this.toggleStatus(T.status.FILLED),this.nodes.imagePreloader&&(this.nodes.imagePreloader.style.backgroundImage="")}),this.nodes.imageContainer.appendChild(this.nodes.imageEl)}fillCaption(a){this.nodes.caption&&(this.nodes.caption.innerHTML=a)}toggleStatus(a){for(const i in T.status)Object.prototype.hasOwnProperty.call(T.status,i)&&this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${T.status[i]}`,a===T.status[i])}applyTune(a,i){this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${a}`,i)}}function U(E){return E&&E.__esModule&&Object.prototype.hasOwnProperty.call(E,"default")?E.default:E}var H={exports:{}};(function(E,a){(function(i,c){E.exports=c()})(window,function(){return function(i){var c={};function r(n){if(c[n])return c[n].exports;var o=c[n]={i:n,l:!1,exports:{}};return i[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=i,r.c=c,r.d=function(n,o,d){r.o(n,o)||Object.defineProperty(n,o,{enumerable:!0,get:d})},r.r=function(n){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},r.t=function(n,o){if(1&o&&(n=r(n)),8&o||4&o&&typeof n=="object"&&n&&n.__esModule)return n;var d=Object.create(null);if(r.r(d),Object.defineProperty(d,"default",{enumerable:!0,value:n}),2&o&&typeof n!="string")for(var b in n)r.d(d,b,(function(u){return n[u]}).bind(null,b));return d},r.n=function(n){var o=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(o,"a",o),o},r.o=function(n,o){return Object.prototype.hasOwnProperty.call(n,o)},r.p="",r(r.s=3)}([function(i,c){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch{typeof window=="object"&&(r=window)}i.exports=r},function(i,c,r){(function(n){var o=r(2),d=setTimeout;function b(){}function u(e){if(!(this instanceof u))throw new TypeError("Promises must be constructed via new");if(typeof e!="function")throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],t(e,this)}function p(e,l){for(;e._state===3;)e=e._value;e._state!==0?(e._handled=!0,u._immediateFn(function(){var s=e._state===1?l.onFulfilled:l.onRejected;if(s!==null){var g;try{g=s(e._value)}catch(m){return void y(l.promise,m)}f(l.promise,g)}else(e._state===1?f:y)(l.promise,e._value)})):e._deferreds.push(l)}function f(e,l){try{if(l===e)throw new TypeError("A promise cannot be resolved with itself.");if(l&&(typeof l=="object"||typeof l=="function")){var s=l.then;if(l instanceof u)return e._state=3,e._value=l,void w(e);if(typeof s=="function")return void t((g=s,m=l,function(){g.apply(m,arguments)}),e)}e._state=1,e._value=l,w(e)}catch(h){y(e,h)}var g,m}function y(e,l){e._state=2,e._value=l,w(e)}function w(e){e._state===2&&e._deferreds.length===0&&u._immediateFn(function(){e._handled||u._unhandledRejectionFn(e._value)});for(var l=0,s=e._deferreds.length;l' + name + '
' + + '