feat: add support for global and element-specific custom HTML attributes across editor plugins and page templates

This commit is contained in:
2026-08-01 19:10:46 +07:00
parent 1ee67e6a46
commit cc228e5c52
7 changed files with 168 additions and 31 deletions
@@ -349,7 +349,8 @@
<!-- 15. Grid / Columns Block -->
<th:block th:if="${block['type'] == 'grid'}">
<div th:id="${block.data['globalId'] != null and !#strings.isEmpty(block.data['globalId']) ? block.data['globalId'] : null}"
th:class="${block.data['globalClass'] != null and !#strings.isEmpty(block.data['globalClass']) ? block.data['globalClass'] : null}">
th:class="${block.data['globalClass'] != null and !#strings.isEmpty(block.data['globalClass']) ? block.data['globalClass'] : null}"
th:data-custom-attrs="${block.data['globalAttributes'] != null and !#strings.isEmpty(block.data['globalAttributes']) ? block.data['globalAttributes'] : (block.data['attributes'] != null and !#strings.isEmpty(block.data['attributes']) ? block.data['attributes'] : null)}">
<div class="container py-4">
<div class="row">
<th:block th:each="i : ${#numbers.sequence(1, block.data['cols'])}">
@@ -357,6 +358,7 @@
th:id="${block.data['id' + i] != null and !#strings.isEmpty(block.data['id' + i]) ? block.data['id' + i] : null}"
th:class="${(block.data['width' + i] != null and block.data['width' + i] gt 0) ? ('col-lg-' + block.data['width' + i] + ' col-md-6 col-12 mb-4') : (block.data['cols'] == 1 ? 'col-12 mb-4' : (block.data['cols'] == 2 ? 'col-md-6 col-12 mb-4' : (block.data['cols'] == 3 ? 'col-lg-4 col-md-6 col-12 mb-4' : (block.data['cols'] == 4 ? 'col-lg-3 col-md-6 col-12 mb-4' : 'col-lg col-md-6 col-12 mb-4'))))}"
th:classappend="${block.data['class' + i] != null and !#strings.isEmpty(block.data['class' + i]) ? block.data['class' + i] : ''}"
th:data-custom-attrs="${block.data['attr' + i] != null and !#strings.isEmpty(block.data['attr' + i]) ? block.data['attr' + i] : null}"
>
<div th:replace=":: renderCell(type=${block.data['type' + i]}, i=${i}, blockData=${block.data})"></div>
</div>
@@ -369,7 +371,8 @@
<!-- 16. Flex Layout Block -->
<th:block th:if="${block['type'] == 'flex'}">
<div th:id="${block.data['globalId'] != null and !#strings.isEmpty(block.data['globalId']) ? block.data['globalId'] : null}"
th:class="${block.data['globalClass'] != null and !#strings.isEmpty(block.data['globalClass']) ? block.data['globalClass'] : null}">
th:class="${block.data['globalClass'] != null and !#strings.isEmpty(block.data['globalClass']) ? block.data['globalClass'] : null}"
th:data-custom-attrs="${block.data['globalAttributes'] != null and !#strings.isEmpty(block.data['globalAttributes']) ? block.data['globalAttributes'] : (block.data['attributes'] != null and !#strings.isEmpty(block.data['attributes']) ? block.data['attributes'] : null)}">
<div class="container py-4">
<div
style="display: flex; flex-wrap: wrap;"
@@ -380,6 +383,7 @@
th:id="${block.data['id' + i] != null and !#strings.isEmpty(block.data['id' + i]) ? block.data['id' + i] : null}"
th:class="${(block.data['width' + i] != null and block.data['width' + i] gt 0) ? ('col-lg-' + block.data['width' + i] + ' col-md-6 col-12 mb-4') : (block.data['cols'] == 1 ? 'col-12 mb-4' : (block.data['cols'] == 2 ? 'col-md-6 col-12 mb-4' : (block.data['cols'] == 3 ? 'col-lg-4 col-md-6 col-12 mb-4' : (block.data['cols'] == 4 ? 'col-lg-3 col-md-6 col-12 mb-4' : 'col-lg col-md-6 col-12 mb-4'))))}"
th:classappend="${block.data['class' + i] != null and !#strings.isEmpty(block.data['class' + i]) ? block.data['class' + i] : ''}"
th:data-custom-attrs="${block.data['attr' + i] != null and !#strings.isEmpty(block.data['attr' + i]) ? block.data['attr' + i] : null}"
>
<div th:replace=":: renderCell(type=${block.data['type' + i]}, i=${i}, blockData=${block.data})"></div>
</div>
@@ -392,12 +396,14 @@
<!-- 17. Dedicated Hover Cards Block -->
<th:block th:if="${block['type'] == 'hovercard' or block['type'] == 'hover-card' or block['type'] == 'hoverCard'}">
<div th:id="${block.data['globalId'] != null and !#strings.isEmpty(block.data['globalId']) ? block.data['globalId'] : null}"
th:class="${(block.data['styleVariant'] == 'v2' or (block.data['globalClass'] != null and block.data['globalClass'].contains('v2'))) ? ('sis-hover-card-v2-section ' + (block.data['globalClass'] != null ? block.data['globalClass'] : '')) : ('sis-hover-card-section ' + (block.data['globalClass'] != null ? block.data['globalClass'] : ''))}">
th:class="${(block.data['styleVariant'] == 'v2' or (block.data['globalClass'] != null and block.data['globalClass'].contains('v2'))) ? ('sis-hover-card-v2-section ' + (block.data['globalClass'] != null ? block.data['globalClass'] : '')) : ('sis-hover-card-section ' + (block.data['globalClass'] != null ? block.data['globalClass'] : ''))}"
th:data-custom-attrs="${block.data['globalAttributes'] != null and !#strings.isEmpty(block.data['globalAttributes']) ? block.data['globalAttributes'] : (block.data['attributes'] != null and !#strings.isEmpty(block.data['attributes']) ? block.data['attributes'] : null)}">
<div class="container py-4">
<div class="row align-items-center">
<th:block th:each="item, iterStat : ${block.data['items']}">
<div th:class="${block.data['cols'] == 1 ? 'col-12 mb-4' : (block.data['cols'] == 2 ? 'col-md-6 col-12 mb-4' : (block.data['cols'] == 3 ? 'col-lg-4 col-md-6 col-12 mb-4' : (block.data['cols'] == 6 ? 'col-lg-2 col-md-4 col-6 mb-4' : 'col-lg-3 col-md-6 col-12 mb-4')))}">
<div th:class="${(block.data['styleVariant'] == 'v2' or (block.data['globalClass'] != null and block.data['globalClass'].contains('v2'))) ? ('sis-hover-card sis-hover-card-v2 ' + ((item['active'] == true or (item['active'] == null and iterStat.first)) ? 'active' : '')) : ('sis-hover-card ' + ((item['active'] == true or (item['active'] == null and iterStat.first)) ? 'active' : ''))}">
<div th:class="${(block.data['styleVariant'] == 'v2' or (block.data['globalClass'] != null and block.data['globalClass'].contains('v2'))) ? ('sis-hover-card sis-hover-card-v2 ' + ((item['active'] == true or (item['active'] == null and iterStat.first)) ? 'active' : '')) : ('sis-hover-card ' + ((item['active'] == true or (item['active'] == null and iterStat.first)) ? 'active' : ''))}"
th:data-custom-attrs="${item['attributes'] != null and !#strings.isEmpty(item['attributes']) ? item['attributes'] : null}">
<div class="w-full flex flex-col items-center justify-center transition-all duration-500">
<div class="sis-hover-card__icon" th:if="${item['imageUrl'] != null and !#strings.isEmpty(item['imageUrl'])}">
<img th:src="${item['imageUrl']}" class="sis-hover-card__img-main" th:alt="${item['title']}" />
@@ -692,17 +698,42 @@
}
});
// Interactive Hover Cards — Mouseenter transfers active class to hovered card
document.querySelectorAll('.sis-hover-card').forEach(function (card) {
card.addEventListener('mouseenter', function () {
var parentRow = card.closest('.row');
// Interactive Hover Cards — Mouseover event delegation to apply active class dynamically
document.addEventListener('mouseover', function (e) {
var card = e.target.closest('.sis-hover-card, .sis-hover-card-v2');
if (card && !card.classList.contains('active')) {
var parentRow = card.closest('.row') || card.parentElement;
if (parentRow) {
parentRow.querySelectorAll('.sis-hover-card').forEach(function (c) {
parentRow.querySelectorAll('.sis-hover-card, .sis-hover-card-v2').forEach(function (c) {
c.classList.remove('active');
});
}
card.classList.add('active');
});
}
});
// Apply Custom Attributes (e.g. data-aos="fade-up" style="background:#fff") dynamically
document.querySelectorAll('[data-custom-attrs]').forEach(function (el) {
var attrStr = el.getAttribute('data-custom-attrs');
if (attrStr && attrStr.trim() !== '') {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = '<div ' + attrStr + '></div>';
var tempEl = tempDiv.firstElementChild;
if (tempEl) {
Array.from(tempEl.attributes).forEach(function (attr) {
if (attr.name === 'class') {
attr.value.split(/\s+/).filter(Boolean).forEach(function (c) {
el.classList.add(c);
});
} else if (attr.name === 'style') {
el.style.cssText += ';' + attr.value;
} else {
el.setAttribute(attr.name, attr.value);
}
});
}
el.removeAttribute('data-custom-attrs');
}
});
});
</script>