Add X13 WebP module for image conversion to next-generation formats

- Implemented the X13Webp class with core functionalities for converting images to WebP format.
- Added support for different PHP versions and defined constants for versioning.
- Included translation strings for various user interface elements and messages.
- Created XML file for module versioning.
This commit is contained in:
2025-09-12 00:41:29 +02:00
parent fa0f4b590b
commit 9003eede2d
247 changed files with 55597 additions and 1 deletions

View File

@@ -0,0 +1,56 @@
$(document).ready(() => {
let x13images = [];
$("img").each(function () {
const imgSrc = $(this).attr("data-src") ?? $(this).attr("data-original") ?? $(this).attr("src");
if (!imgSrc) return;
if (imgSrc.includes(".webp")) return;
if ($(this).siblings("source").length && $(this).siblings("source").attr("srcset") !== undefined) {
if ($(this).siblings("source").attr("srcset").includes(".webp")) {
return;
}
}
x13images.push(imgSrc.trim());
});
$("[style]").each(function () {
const bgImage = $(this).css("background-image");
if (bgImage.includes(".webp") || (!bgImage.includes(".jpg") && !bgImage.includes(".png"))) {
return;
}
let image = $(this)
.css("background-image")
.replace(/url\("([^"]+)"\)/, "$1");
x13images.push(image.trim());
});
$("#product #thumbs_list a.fancybox").each(function () {
const href = $(this).attr("href");
if (href.length) {
x13images.push(href.trim());
}
});
$("#product .images-container .thumb").each(function () {
const medium = $(this).data("image-medium-src");
const large = $(this).data("image-large-src");
if (typeof medium !== 'undefined') {
x13images.push(medium.trim());
}
if (typeof large !== 'undefined') {
x13images.push(large.trim());
}
});
$.ajax({
url: x13webp_ajax_convert_url,
method: "POST",
data: {
x13images,
},
});
});

View File

@@ -0,0 +1,171 @@
$(() => {
x13webp.fixProductThumbs();
if (!x13webpIsPs16) {
x13webp.addStylesheet();
}
x13webp.observe();
});
$(window).on('hashchange', function () {
if (!x13webpIsPs16) return;
setTimeout(function () {
let thumb = $('#thumbs_list_frame li:visible:first');
if (thumb.length === 0) return;
if (thumb.find('picture').length === 0) return;
let img = thumb.find('img').attr('data-original') ?? thumb.find('img').attr('src');
let source = thumb.find('source').attr('srcset');
$('#' + x13webp.themeSelectors.jsQvProductCover).attr('src', img.replace('-cart_default', '-large_default'));
$('#' + x13webp.themeSelectors.jsQvProductCover).siblings('source').attr('srcset', source.replace('-cart_default', '-large_default'));
})
});
const x13webp = {
themeSelectors: {
jsQvProductCover: x13webpIsPs16 ? "bigpic" : "js-qv-product-cover",
jsModalProductCover: "js-modal-product-cover",
jsThumb: "js-thumb",
jsModalThumb: "js-modal-thumb",
},
addStylesheet: () => {
let thumbHover = "." + x13webp.themeSelectors.jsThumb + ":hover";
let thumbSelected = "." + x13webp.themeSelectors.jsThumb + ".selected";
let thumb = document.querySelector(thumbSelected);
if (!thumb) return;
thumb = getComputedStyle(thumb);
let thumbBorder = {
style: thumb.borderBlockStyle,
width: thumb.borderBlockWidth,
color: thumb.borderBlockColor,
};
let css =
"picture" +
thumbSelected +
", picture" +
thumbHover +
", " +
"picture" +
thumbSelected.replace(
x13webp.themeSelectors.jsThumb,
x13webp.themeSelectors.jsModalThumb
) +
", picture" +
thumbHover.replace(
x13webp.themeSelectors.jsThumb,
x13webp.themeSelectors.jsModalThumb
) +
" { border: none !important; outline-style: " +
thumbBorder.style +
" !important; outline-color: " +
thumbBorder.color +
" !important; outline-width: " +
thumbBorder.width +
" !important; outline-offset: -" +
thumbBorder.width +
" !important;}",
head = document.head || document.getElementsByTagName("head")[0],
style = document.createElement("style");
head.appendChild(style);
style.type = "text/css";
if (style.styleSheet) {
// This is required for IE8 and below.
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
},
replacePictureSource: (target, source) => {
target.setAttribute("srcset", source);
},
addAttributesToPicture: (target, classArray, dataArray = {}) => {
target.style.display = "inline-block";
target.classList.add(...classArray);
Object.entries(dataArray).forEach(([key, value]) => {
target.dataset[key] = value;
});
},
fixProductThumbs: () => {
const body = document.querySelector("body").getAttribute("id");
if (body !== "product") return;
const thumbs = [
...document.querySelectorAll("." + x13webp.themeSelectors.jsThumb),
];
const modalThumbs = [
...document.querySelectorAll("." + x13webp.themeSelectors.jsModalThumb),
];
const allThumbs = [...thumbs, ...modalThumbs];
allThumbs.forEach((thumb) => {
if (!thumb.parentElement) return;
if (thumb.parentElement.tagName !== "PICTURE") return;
const target = thumb.parentElement;
const classArray = thumb.classList;
const isModal = classArray.contains(x13webp.themeSelectors.jsModalThumb);
const dataArray = Object.assign({}, thumb.dataset);
x13webp.addAttributesToPicture(target, classArray, dataArray);
thumb.className = isModal
? x13webp.themeSelectors.jsModalThumb + "-fix"
: x13webp.themeSelectors.jsThumb + "-fix";
});
},
observe: () => {
const pictureTagObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (!mutation.target.parentElement) return;
if (mutation.target.parentElement.tagName !== "PICTURE") return;
const mutationClass = mutation.target.classList;
if (
mutationClass.contains(x13webp.themeSelectors.jsQvProductCover) ||
mutationClass.contains(x13webp.themeSelectors.jsModalProductCover) ||
mutation.target.getAttribute("id") ===
x13webp.themeSelectors.jsQvProductCover
) {
const target = mutation.target.parentElement.querySelector("source");
let source =
mutation.target.getAttribute("data-original") ??
mutation.target.getAttribute("src");
source = source.replace(/(\.jpg)|(\.png)/, ".webp");
x13webp.replacePictureSource(target, source);
return;
}
if (mutationClass.contains(x13webp.themeSelectors.jsThumb + "-fix")) {
const target = mutation.target.parentElement;
const classArray = mutation.target.classList;
x13webp.addAttributesToPicture(target, classArray);
return;
}
});
});
const allPictureTags = document.querySelectorAll("img");
allPictureTags.forEach((pictureTag) => {
pictureTagObserver.observe(pictureTag, { attributes: true });
});
},
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,327 @@
let pause_othres_conversion = true;
$(document).ready(() => {
$('.list-files:not(.is-loading)').on('click', 'li.dir:not(.is-loaded) p', function (e) {
if ($(e.target).is('input')) return;
$('.list-files').addClass('is-loading');
$(this).addClass('loading');
ajaxFileManager($(this).parent());
});
$('.list-files:not(.is-loading)').on('click', 'li.dir.is-loaded > p', function (e) {
if ($(e.target).is('input')) return;
$(this).parent().toggleClass(['is-open', 'is-closed']);
toggleState($(this).find('.state'), $(this).parent().hasClass('is-open'));
});
$('.list-files').on('change', 'input[type="checkbox"][name="others_item"]', function () {
if ($(this).prop('checked')) {
$('.btn-item-start-others').attr('disabled', false);
} else {
$('.btn-item-start-others').attr('disabled', $('input[name="others_item"]:checked:not(:disabled)').length ? false : true);
}
const li = $(this).closest('li');
if (li.length && li.hasClass('dir')) {
li.find('input:not(:disabled)').prop('checked', $(this).prop('checked'));
}
});
$('.btn-item-start-others').on('click',function(){
actionStartConversion();
let items = [];
$('.list-files').find('input[name="others_item"]:checked:not(:disabled)').each(function(){
items.push($(this).val())
});
ajaxGenerateOthersItem(items)
});
$('.btn-item-pause-others').on('click', function () {
actionPauseConversion();
});
$('.btn-item-refresh-others').on('click', function(){
$.ajax({
url: x13webp_refresh_others_url,
method: 'POST',
}).done((resp) => {
if(resp.length){
const content = JSON.parse(resp);
if(content.error){
alert(content.error);
actionPauseConversion();
} else if(content.items.length){
resetProgressItem();
actionStartConversion();
$(content.items).each(function(){
const input = getInputByValue(this)
if (input.length) input.attr('disabled', false);
});
ajaxGenerateOthersItem(content.items);
} else {
alert(x13webp_unexpected_error);
}
}
});
});
});
const getInputByValue = (val) => {
return $('.list-files input[value="' + val + '"]');
}
const resetProgressItem = () => {
const progress_bar = $('#tab-x13wepb-others').find('.progress-bar');
progress_bar.attr('aria-valuenow', 0);
const totals = $('#tab-x13wepb-others').find('.images-done-total');
totals.find('.done').text(0);
progress_bar.parent().removeClass('is-done')
progress_bar.find('.progressbar-inner').text(`0%`);
progress_bar.css('width', '0%');
}
const actionStartConversion = () => {
pause_othres_conversion = false;
$('#form-tabs').find('li:not(.active)').tooltip('enable').find('a').addClass('disabled');
$('.btn-item-start-others').addClass('hidden').attr('disabled', true).parent().tooltip('disable');
$('.btn-item-refresh-others').addClass('hidden').attr('disabled', true);
$('.btn-item-pause-others').removeClass('hidden').attr('disabled', false);
}
const actionPauseConversion = () => {
pause_othres_conversion = true;
$('#form-tabs').find('li:not(.active)').tooltip('disable').find('a').removeClass('disabled');
const disable_button = $('input[name="others_item"]:checked:not(:disabled)').length ? false : true;
$('.btn-item-start-others').removeClass('hidden').attr('disabled', disable_button).parent().tooltip(disable_button ? 'enable' : 'disable');
$('.btn-item-refresh-others').removeClass('hidden').attr('disabled', false);
$('.btn-item-pause-others').addClass('hidden').attr('disabled', true);
}
const ajaxGenerateOthersItem = (items) => {
if (!pause_othres_conversion && items.length) {
$.ajax({
url: x13webp_generate_others_item_url,
method: 'POST',
data: {
item: items[0]
}
}).done((resp) => {
if(resp.length){
const output = JSON.parse(resp);
if(output && !output.error){
if (output.dir) {
if(output.complete){
const input = getInputByValue(items[0]);
if (input.length) input.attr('disabled', true).parent().addClass('disabled');
items.splice(0, 1);
} else {
const input = getInputByValue(output.image);
if (input.length) input.attr('disabled', true).parent().addClass('disabled');
const index = items.indexOf(output.image);
if (index !== -1) {
items.splice(index, 1);
}
}
} else {
const input = getInputByValue(items[0]);
if (input.length) input.attr('disabled', true).parent().addClass('disabled');
items.splice(0, 1);
}
const done = $('#tab-x13wepb-others').find('.images-done-total .done').text();
if(!output.complete){
addOthersLog(`<p class="log-li ${output.warning ? 'has-warning' : ''}">${output.object_name} <strong>#${parseInt(done) + 1}</strong>: <span class="log-format">WebP [${output.image}]</span> <span class="log log-${output.success ? 'success' : 'danger'}">${output.warning ? output.warning : x13webp_success_alert}</span></p>`, output.warning);
}
increaseProgressItem()
if (items.length) {
ajaxGenerateOthersItem(items)
} else {
actionPauseConversion()
}
if ($('#x13webp-delete-all-webp').hasClass('hidden')) {
$('#x13webp-delete-all-webp').removeClass('hidden').siblings('.help-block').addClass('hidden');
}
} else {
actionPauseConversion()
addOthersLog(`<p class="log-li"><span class="log log-danger">${output.error}</span></p>`, true);
}
} else {
actionPauseConversion()
addOthersLog(`<p class="log-li"><span class="log log-danger">${x13webp_unexpected_error}</span></p>`, true);
}
});
}
}
const addOthersLog = (log, warning = false) => {
if ($('.x13webp-log-info').parent().hasClass('hidden')) $('.x13webp-log-info').parent().removeClass('hidden')
if (log) $('.x13webp-log-info').prepend(log);
if (warning && $('#x13webp-log-warning').hasClass('hidden')) $('#x13webp-log-warning').removeClass('hidden');
}
const increaseProgressItem = () => {
const progress_bar = $('#tab-x13wepb-others').find('.progress-bar');
progress_bar.attr('aria-valuenow', parseInt(progress_bar.attr('aria-valuenow')) + 1);
const percent = parseFloat(progress_bar.attr('aria-valuenow') * 100 / progress_bar.attr('aria-valuemax')).toFixed(2);
const totals = $('#tab-x13wepb-others').find('.images-done-total');
totals.find('.done').text(progress_bar.attr('aria-valuenow'));
if (totals.find('.total').text() == totals.find('.done').text() && !progress_bar.parent().hasClass('is-done')) {
progress_bar.parent().addClass('is-done')
}
progress_bar.find('.progressbar-inner').text(`${percent}%`);
progress_bar.css('width', percent + '%');
}
const toggleState = (state, open) => $(state).find('i').toggleClass('icon-folder').toggleClass('icon-folder-open');
const ajaxFileManager = (li) => {
li.addClass(['is-open', 'is-loaded']);
$.ajax({
url: x13webp_file_manager_url,
method: 'POST',
data: {
file: li.data('file')
}
}).done((resp) => {
if (resp.length) {
const content = JSON.parse(resp);
if (content.error) {
alert(content.error);
} else if (content.tree.length) {
toggleState(li.find('.state'), false);
let ul = '<ul>';
$(content.tree).each(function(){
ul += `<li data-file="${li.data('file')}/${this.name}" ${this.type == 'dir' ? 'class="dir"' : ''}>`;
ul += `<p ${this.exists && this.type == 'dir' ? 'data-toggle="tooltip" title="'+x13webp_empty_folder+'"' : ''}>`;
if(this.type == 'dir'){
ul += `<span class="state"><i class="icon-folder"></i></span>`;
}
if (li.find('p input').prop('checked') && $('.btn-item-start-others').attr('disabled') == true) {
$('.btn-item-start-others').attr('disabled', false);
}
const props = this.exists ?
this.type == 'dir' ?
'disabled' :
'checked disabled':
li.find('p input').prop('checked') ?
'checked' :
'';
ul += `<input type="checkbox" name="others_item" value="${li.data('file')}/${this.name}" ${props}>`;
ul += this.name;
ul += `</p>`;
ul += `</li>`;
})
ul += '</ul>';
li.append(ul);
li.find('.dir p').tooltip('enable');
$('.list-files').removeClass('is-loading');
} else {
li.append('<ul class="empty"><li>..</li></ul>');
}
}
li.find('.loading').removeClass('loading');
});
}

File diff suppressed because it is too large Load Diff