feat(main): Add base theme: This is the falcon theme out of the box.

This is falcon v3.1.2
This commit is contained in:
2025-11-18 14:04:01 +01:00
parent 3a7f2db331
commit 6849b8eefd
605 changed files with 49820 additions and 0 deletions

View File

@ -0,0 +1,54 @@
import prestashop from 'prestashop';
import $ from 'jquery';
import FiltersRangeSliders from '@js/listing/components/filters/FiltersRangeSliders';
class Filters {
constructor() {
this.$body = $('body');
this.setEvents();
this.rangeSliders = FiltersRangeSliders;
this.rangeSliders.init();
}
setEvents() {
prestashop.on('updatedProductList', () => {
prestashop.pageLoader.hideLoader();
this.rangeSliders.init();
});
prestashop.on('updateFacets', () => {
prestashop.pageLoader.showLoader();
});
this.$body.on('click', '.js-search-link', (event) => {
event.preventDefault();
prestashop.emit('updateFacets', $(event.target).closest('a').get(0).href);
});
this.$body.on('change', '[data-action="search-select"]', ({ target }) => {
prestashop.emit('updateFacets', $(target).find('option:selected').data('href'));
});
this.$body.on('click', '.js-search-filters-clear-all', (event) => {
prestashop.emit('updateFacets', this.constructor.parseSearchUrl(event));
});
this.$body.on('change', '#search_filters input[data-search-url]', (event) => {
prestashop.emit('updateFacets', this.constructor.parseSearchUrl(event));
});
}
static parseSearchUrl(event) {
if (event.target.dataset.searchUrl !== undefined) {
return event.target.dataset.searchUrl;
}
if ($(event.target).parent()[0].dataset.searchUrl === undefined) {
throw new Error('Can not parse search URL');
}
return $(event.target).parent()[0].dataset.searchUrl;
}
}
export default Filters;

View File

@ -0,0 +1,15 @@
import $ from 'jquery';
import RangeSlider from '@js/listing/components/filters/RangeSlider';
class FiltersRangeSliders {
static init() {
const $rangeSliders = $('.js-range-slider');
$rangeSliders.each((i, el) => {
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "slider" }] */
const slider = new RangeSlider(el);
});
}
}
export default FiltersRangeSliders;

View File

@ -0,0 +1,115 @@
class FiltersUrlHandler {
constructor() {
this.baseUrl = window.location.origin + window.location.pathname;
this.oldSearchUrl = null;
this.searchUrl = null;
}
setOldSearchUrl() {
this.oldSearchUrl = this.searchUrl;
}
getFiltersUrl() {
this.setOldSearchUrl();
return `${this.baseUrl}?q=${this.searchUrl}`;
}
setSearchUrl() {
const searchParams = new URLSearchParams(window.location.search);
this.searchUrl = searchParams.get('q');
this.oldSearchUrl = searchParams.get('q');
}
setRangeParams(group, { unit, from, to }) {
this.removeGroup(group);
this.appendParam(group, unit);
this.appendParam(group, from);
this.appendParam(group, to);
}
appendParam(group, prop) {
const oldSearchUrl = this.searchUrl || '';
let newSearchUrl = oldSearchUrl.length ? oldSearchUrl.split('/') : [];
let groupExist = false;
const newSearchUrlLength = newSearchUrl.length;
group = FiltersUrlHandler.specialEncode(group);
prop = FiltersUrlHandler.specialEncode(prop);
for (let i = 0; i < newSearchUrlLength; i += 1) {
const filterGroup = newSearchUrl[i];
const filterGroupArray = filterGroup.split('-');
if (filterGroupArray[0] === group) {
newSearchUrl[i] = `${newSearchUrl[i]}-${prop}`;
groupExist = true;
break;
}
}
if (!groupExist) {
newSearchUrl = [...newSearchUrl, `${group}-${prop}`];
}
this.searchUrl = FiltersUrlHandler.specialDecode(FiltersUrlHandler.formatSearchUrl(newSearchUrl));
}
removeGroup(group) {
const oldSearchUrl = this.searchUrl || '';
const newSearchUrl = oldSearchUrl.length ? oldSearchUrl.split('/') : [];
const newSearchUrlLength = newSearchUrl.length;
for (let i = 0; i < newSearchUrlLength; i += 1) {
const filterGroup = newSearchUrl[i];
const filterGroupArray = filterGroup.split('-');
if (filterGroupArray[0] === group) {
newSearchUrl.splice(i, 1);
}
}
this.searchUrl = FiltersUrlHandler.specialDecode(FiltersUrlHandler.formatSearchUrl(newSearchUrl));
}
static toString(value) {
return `${value}`;
}
static specialEncode(str) {
return FiltersUrlHandler.toString(str).replace('/', '[slash]');
}
static specialDecode(str) {
return FiltersUrlHandler.toString(str).replace('[slash]', '/');
}
removeParam(group, prop) {
const oldSearchUrl = this.searchUrl || '';
const newSearchUrl = oldSearchUrl.length ? oldSearchUrl.split('/') : [];
const newSearchUrlLength = newSearchUrl.length;
for (let i = 0; i < newSearchUrlLength; i += 1) {
const filterGroup = newSearchUrl[i];
const filterGroupArray = filterGroup.split('-');
if (filterGroupArray[0] === group) {
const filterResult = filterGroupArray.filter((el) => el !== prop);
if (filterResult.length === 1) {
newSearchUrl.splice(i, 1);
} else {
newSearchUrl[i] = filterResult.join('-');
}
break;
}
}
this.searchUrl = FiltersUrlHandler.specialDecode(FiltersUrlHandler.formatSearchUrl(newSearchUrl));
}
static formatSearchUrl(array) {
return array.join('/');
}
}
export default FiltersUrlHandler;

View File

@ -0,0 +1,182 @@
import $ from 'jquery';
import prestashop from 'prestashop';
import noUiSlider from 'nouislider';
import wNumb from 'wnumb';
import FiltersUrlHandler from '@js/listing/components/filters/FiltersUrlHandler';
class RangeSlider {
constructor(element) {
this.$slider = $(element);
this.setConfig();
this.setFormat();
this.initFilersSlider();
this.setEvents();
}
getSliderType() {
this.sliderType = this.$slider.data('slider-specifications') ? 'price' : 'weight';
}
setConfig() {
this.min = this.$slider.data('slider-min');
this.max = this.$slider.data('slider-max');
this.$parentContainer = this.$slider.closest('.js-input-range-slider-container');
this.$inputs = [this.$parentContainer.find('[data-action="range-from"]'), this.$parentContainer.find('[data-action="range-to"]')];
this.getSliderType();
if (this.sliderType === 'price') {
const {
currencySymbol,
positivePattern,
} = this.$slider.data('slider-specifications');
this.sign = currencySymbol;
this.positivePattern = positivePattern;
this.values = this.$slider.data('slider-values');
this.signPosition = this.positivePattern.indexOf('¤') === 0 ? 'prefix' : 'suffix';
} else if (this.sliderType === 'weight') {
const unit = this.$slider.data('slider-unit');
this.sign = unit;
this.values = this.$slider.data('slider-values');
this.signPosition = 'suffix';
}
if (!Array.isArray(this.values)) {
this.values = [this.min, this.max];
}
}
setFormat() {
this.format = wNumb({
mark: ',',
thousand: ' ',
decimals: 0,
[this.signPosition]:
this.signPosition === 'prefix' ? this.sign : ` ${this.sign}`,
});
}
initFilersSlider() {
this.sliderHandler = noUiSlider.create(this.$slider.get(0), {
start: this.values,
connect: [false, true, false],
range: {
min: this.min,
max: this.max,
},
format: this.format,
});
}
initFilersSliderInputs() {
this.setInputValues(this.values, true);
}
setInputValues(values, formatValue = false) {
this.$inputs.forEach((input, i) => {
const val = formatValue ? this.format.from(values[i]) : values[i];
$(input).val(val);
});
}
setEvents() {
this.sliderHandler.off('set', this.constructor.handlerSliderSet);
this.sliderHandler.on('set', this.constructor.handlerSliderSet);
this.sliderHandler.off('update', this.handlerSliderUpdate);
this.sliderHandler.on('update', this.handlerSliderUpdate);
this.$inputs.forEach(($input) => {
$input.off('focus', this.handleInputFocus);
$input.on('focus', this.handleInputFocus);
$input.off('blur', this.handleInputBlur);
$input.on('blur', this.handleInputBlur);
$input.on('keyup', this.handleInputKeyup);
});
}
static getInputAction($input) {
return $input.data('action');
}
getInputPositionInValue($input) {
const actionPosition = {
'range-from': 0,
'range-to': 1,
};
return actionPosition[this.constructor.getInputAction($input)];
}
handleInputFocus = ({ target }) => {
const $input = $(target);
$input.val(this.format.from($input.val()));
};
handleInputBlur = ({ target }) => {
const $input = $(target);
const value = $input.val();
const position = this.getInputPositionInValue($input);
const oldValues = this.values;
const newValues = [...oldValues];
newValues[position] = value;
if (value !== oldValues[position]) {
this.sliderHandler.set(newValues);
} else {
$input.val(this.format.to(parseFloat($input.val(), 10)));
}
};
handleInputKeyup = ({ target, keyCode }) => {
if (keyCode !== 13) {
return;
}
const $input = $(target);
const value = $input.val();
const position = this.getInputPositionInValue($input);
const oldValues = this.values;
const newValues = [...oldValues];
newValues[position] = value;
if (value !== oldValues[position]) {
this.sliderHandler.set(newValues);
} else {
$input.val(this.format.to(parseFloat($input.val(), 10)));
}
};
handlerSliderUpdate = (
values,
) => {
this.setInputValues(values);
};
static handlerSliderSet(
values,
handle,
unencoded,
tap,
positions,
noUiSliderInstance,
) {
const formatFunction = noUiSliderInstance.options.format;
const $target = $(noUiSliderInstance.target);
const group = $target.data('slider-label');
const unit = $target.data('slider-unit');
const [from, to] = values.map((val) => formatFunction.from(val));
const filtersHandler = new FiltersUrlHandler();
filtersHandler.setSearchUrl();
filtersHandler.setRangeParams(group, { unit, from, to });
const newUrl = filtersHandler.getFiltersUrl();
prestashop.emit('updateFacets', newUrl);
}
}
export default RangeSlider;

View File

@ -0,0 +1,47 @@
import $ from 'jquery';
import prestashop from 'prestashop';
import Filters from '@js/listing/components/filters/Filters';
function updateProductListDOM(data) {
$(prestashop.themeSelectors.listing.searchFilters).replaceWith(
data.rendered_facets,
);
$(prestashop.themeSelectors.listing.activeSearchFilters).replaceWith(
data.rendered_active_filters,
);
$(prestashop.themeSelectors.listing.listTop).replaceWith(
data.rendered_products_top,
);
const renderedProducts = $(data.rendered_products);
const productSelectors = $(prestashop.themeSelectors.listing.product);
if (productSelectors.length > 0) {
productSelectors.removeClass().addClass(productSelectors.first().attr('class'));
} else {
productSelectors.removeClass().addClass(renderedProducts.first().attr('class'));
}
$(prestashop.themeSelectors.listing.list).replaceWith(renderedProducts);
$(prestashop.themeSelectors.listing.listBottom).replaceWith(data.rendered_products_bottom);
if (data.rendered_products_header) {
$(prestashop.themeSelectors.listing.listHeader).replaceWith(data.rendered_products_header);
}
prestashop.emit('updatedProductList', data);
}
$(() => {
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "filters" }] */
const filters = new Filters();
prestashop.on('updateProductList', (data) => {
updateProductListDOM(data);
window.scrollTo(0, 0);
});
prestashop.on('updatedProductList', () => {
prestashop.pageLazyLoad.update();
});
});