@salla.sa/twilight-components
Version:
Salla Web Component
871 lines (851 loc) • 80.4 kB
JavaScript
/*!
* Crafted with ❤ by Salla
*/
import { r as registerInstance, h, H as Host, a as getElement, f as require_root, i as requireIsObject, j as requireIsSymbol, d as getDefaultExportFromCjs } from './index-Dbv0I4re.js';
import { i as iconCheck } from './check-BsXh13x8.js';
import { a as anime } from './anime.es-CgtvEd63.js';
import { H as Helper } from './Helper-DFMXF2_h.js';
import { S as Star } from './star2-D4oPi1Ov.js';
import { a as arrowLeft } from './arrow-left-BedNk7k1.js';
import { S as ShoppingBag } from './shopping-bag-DiKTtDW5.js';
const sallaCommentFormCss = ":host{display:block}";
const SallaCommentForm = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.placeholder = salla.lang.get('blocks.comments.placeholder');
this.submitText = salla.lang.get('blocks.comments.submit');
salla.lang.onLoaded(() => {
this.placeholder = salla.lang.get('blocks.comments.placeholder');
this.submitText = salla.lang.get('blocks.comments.submit');
});
salla.onReady(() => {
this.canComment = salla.config.get('user.can_comment');
this.itemId = salla.config.get('page.id');
this.type = salla.url.is_page('page-single') ? 'page' : salla.url.is_page('blog.single') ? 'blog' : 'product';
});
}
submit() {
if (!this.commentForm.reportValidity()) {
salla.log('CommentForm:: validation error!');
return;
}
this.submitBtn.load()
.then(() => salla.comment.add({ id: this.itemId, comment: this.commentField.value, type: this.type }))
.finally(() => this.submitBtn.stop);
}
render() {
return (h(Host, { key: '24b73030ff1828a6f21c1c36a00be2c8651f6edf' }, !!this.canComment ? h("form", { ref: frm => this.commentForm = frm }, h("div", { class: "s-comment-form-wrapper" }, this.showAvatar ?
h("img", { class: "s-comment-form-avatar", src: salla.config.get('user.avatar'), alt: "user avatar" }) : '', h("div", { class: "s-comment-form-content" }, h("textarea", { cols: 30, rows: 5, minlength: "4", maxlength: "500", ref: field => this.commentField = field, placeholder: this.placeholder, class: "s-comment-form-input", required: true }), h("br", null), h("div", { class: "s-comment-form-action" }, h("salla-button", { ref: btn => this.submitBtn = btn, "loader-position": 'center', onClick: () => this.submit() }, this.submitText))))) : ''));
}
};
SallaCommentForm.style = sallaCommentFormCss;
var Reply = `<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>reply</title>
<path d="M20 14.667h-12.609l9.401-6.927c0.592-0.436 0.72-1.271 0.283-1.863-0.439-0.595-1.273-0.72-1.864-0.283l-12.667 9.333c-0.343 0.251-0.544 0.649-0.544 1.072s0.201 0.821 0.543 1.073l12.667 9.333c0.237 0.176 0.515 0.26 0.789 0.26 0.409 0 0.813-0.188 1.075-0.543 0.437-0.592 0.309-1.427-0.283-1.863l-9.4-6.928h12.609c4.412 0 8 3.588 8 8 0 0.737 0.597 1.333 1.333 1.333s1.333-0.596 1.333-1.333c0-5.881-4.785-10.667-10.667-10.667z"></path>
</svg>
`;
var ThumbsUp = `<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>thumbs-up</title>
<path d="M26.667 10.667h-8.261l1.279-3.38c0.723-1.911 0.087-4.051-1.549-5.203-0.909-0.639-2.004-0.881-3.085-0.684-1.101 0.203-2.061 0.837-2.703 1.787l-5.452 8.067c-0.148 0.22-0.228 0.48-0.228 0.747v12c0 3.676 2.991 6.667 6.667 6.667h8.688c2.535 0 4.817-1.407 5.955-3.671l2.548-5.063c0.093-0.187 0.143-0.392 0.143-0.6v-6.667c0-2.205-1.795-4-4-4zM28 21.017l-2.405 4.78c-0.683 1.359-2.052 2.203-3.573 2.203h-8.688c-2.205 0-4-1.795-4-4v-11.592l5.223-7.728c0.237-0.352 0.584-0.585 0.975-0.657 0.367-0.067 0.749 0.017 1.068 0.241 0.631 0.444 0.879 1.317 0.591 2.079l-1.963 5.185c-0.156 0.409-0.099 0.869 0.149 1.229s0.66 0.576 1.099 0.576h10.192c0.735 0 1.333 0.599 1.333 1.333zM2.667 10.667c-0.736 0-1.333 0.597-1.333 1.333v16c0 0.736 0.597 1.333 1.333 1.333s1.333-0.597 1.333-1.333v-16c0-0.736-0.597-1.333-1.333-1.333z"></path>
</svg>
`;
const sallaCommentsCss$1 = ":host{display:block}";
const SallaCommentItem = class {
constructor(hostRef) {
registerInstance(this, hostRef);
// Translations
this.has_bought_trans = salla.lang.get('blocks.comments.has_bought');
this.rated_trans = salla.lang.get('pages.rating.rated');
this.waiting_approval_trans = salla.lang.get('blocks.comments.waiting_approval');
this.has_order_trans = salla.lang.get('blocks.comments.has_order');
this.allowLikes = salla.config.get('store.settings.rating.allow_likes');
this.allowAttachImages = salla.config.get('store.settings.rating.allow_attach_images');
this.helpfulLabel = salla.lang.getWithDefault('blocks.comments.helpful', 'مفيد');
this.likesCount = 0;
this.likedComments = [];
salla.lang.onLoaded(() => {
const setNestedAsync = (lang, key, value) => {
return new Promise((resolve) => {
salla.helpers.setNested(salla.lang.messages[lang], key, value);
resolve(true);
});
};
const setTranslations = async () => {
await setNestedAsync('ar.trans', 'blocks.comments.helpful', 'مفيد');
await setNestedAsync('en.trans', 'blocks.comments.helpful', 'Helpful');
this.helpfulLabel = salla.lang.getWithDefault('blocks.comments.helpful', 'مفيد');
};
this.has_bought_trans = salla.lang.get('blocks.comments.has_bought');
this.rated_trans = salla.lang.get('pages.rating.rated');
this.waiting_approval_trans = salla.lang.get('blocks.comments.waiting_approval');
this.has_order_trans = salla.lang.get('blocks.comments.has_order');
setTranslations();
});
}
componentDidLoad() {
this.likesCount = this.comment.likes_count;
try {
this.likedComments = JSON.parse(localStorage.getItem('liked_comments') || '[]');
if (this.likedComments.includes(this.comment.id)) {
this.likeBtn.classList.add('liked');
this.likeBtn.fill = "solid";
}
}
catch {
salla.log('Bad json for liked_comments');
}
}
getReplies() {
return Array.isArray(this.comment.replies) ? this.comment.replies : [this.comment.replies];
}
getDate(dateString) {
const [datePart] = dateString.split(' ');
const [year, month, day] = datePart.split('-');
const formattedDate = `${parseInt(day, 10)}/${parseInt(month, 10)}/${parseInt(year, 10)}`;
return formattedDate;
}
getTime(dateString) {
const [, timePart] = dateString.split(' ');
const [hours, minutes] = timePart.split(':');
const formattedTime = `${parseInt(hours, 10)}:${parseInt(minutes, 10)}`;
return formattedTime;
}
async toggleLike() {
if (salla.config.isGuest()) {
return salla.notify.error(salla.lang.get('common.messages.must_login'));
}
this.likedComments = JSON.parse(localStorage.getItem('liked_comments') || '[]');
const isLiked = this.likedComments.includes(this.comment.id);
try {
const endpoint = isLiked ? `rating/${this.comment.id}/unlike` : `rating/${this.comment.id}/like`;
const res = await salla.api.request(endpoint, '', 'put');
salla.log(res.message);
if (isLiked) {
this.likeBtn.classList.remove('liked');
this.likeBtn.fill = 'outline';
this.updateLikedComments(this.comment.id, false);
this.likesCount--;
}
else {
this.likeBtn.classList.add('liked');
this.likeBtn.fill = 'solid';
this.updateLikedComments(this.comment.id, true);
this.likesCount++;
}
}
catch (e) {
if (e.response.status == 409) {
if (this.likeBtn.classList.contains('liked')) {
this.likeBtn.fill = 'outline';
this.likeBtn.classList.remove('liked');
this.updateLikedComments(this.comment.id, false);
this.likesCount--;
}
else {
this.likeBtn.fill = 'solid';
this.likeBtn.classList.add('liked');
salla.logger.warn('Like already exists');
this.updateLikedComments(this.comment.id, true);
}
}
}
}
updateLikedComments(commentId, add) {
this.likedComments = JSON.parse(localStorage.getItem('liked_comments') || '[]');
if (add) {
if (!this.likedComments.includes(commentId)) {
this.likedComments.push(commentId);
}
}
else {
this.likedComments = this.likedComments.filter(id => id !== commentId);
}
localStorage.setItem('liked_comments', JSON.stringify(this.likedComments));
}
PinnedIcon() {
return (h("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", width: "16", height: "16", color: "#555555", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M12 16V21" }), h("path", { d: "M8 5.2918C8 5.02079 8 4.88529 8.01312 4.77132C8.1194 3.84789 8.84789 3.1194 9.77133 3.01312C9.88529 3 10.0208 3 10.2918 3H13.7082C13.9792 3 14.1147 3 14.2287 3.01312C15.1521 3.1194 15.8806 3.84789 15.9869 4.77132C16 4.88529 16 5.02079 16 5.2918C16 5.37885 16 5.42237 15.9967 5.46264C15.9708 5.78281 15.7927 6.07104 15.5179 6.2374C15.4834 6.25832 15.4444 6.27779 15.3666 6.31672L15.1055 6.44726C14.7021 6.64897 14.5003 6.74983 14.3681 6.90564C14.26 7.03286 14.1856 7.18509 14.1515 7.34846C14.1097 7.54854 14.1539 7.76968 14.2424 8.21197L15 12H15.3333C15.9533 12 16.2633 12 16.5176 12.0681C17.2078 12.2531 17.7469 12.7922 17.9319 13.4824C18 13.7367 18 14.0467 18 14.6667C18 14.9767 18 15.1317 17.9659 15.2588C17.8735 15.6039 17.6039 15.8735 17.2588 15.9659C17.1317 16 16.9767 16 16.6667 16H7.33333C7.02334 16 6.86835 16 6.74118 15.9659C6.39609 15.8735 6.12654 15.6039 6.03407 15.2588C6 15.1317 6 14.9767 6 14.6667C6 14.0467 6 13.7367 6.06815 13.4824C6.25308 12.7922 6.79218 12.2531 7.48236 12.0681C7.73669 12 8.04669 12 8.66667 12H9L9.75761 8.21197C9.84606 7.76968 9.89029 7.54854 9.84852 7.34846C9.81441 7.18509 9.73995 7.03286 9.63194 6.90564C9.49965 6.74983 9.29794 6.64897 8.89452 6.44726L8.63344 6.31672C8.55558 6.27779 8.51665 6.25832 8.48208 6.2374C8.20731 6.07104 8.02917 5.78281 8.00326 5.46264C8 5.42237 8 5.37885 8 5.2918Z" })));
}
render() {
let isAdmin = this.comment.type == 'admin';
return (h(Host, { key: '3f9d4b98cff2dd49259d3386b4996b905f92d7db', class: isAdmin ? 's-comments-item-admin' : 's-comments-item' }, h("div", { key: '1348f11b2e553be483f60c5c558d3e800f050700', class: { "s-comments-item-wrapper": !isAdmin, "s-comments-item-admin-wrapper": isAdmin }, id: `s-comments-item-${this.comment.id}` }, h("div", { key: '0587afd7ae1fc27b12fc4fd2c9ef4bf9e919d04a', class: "s-comments-item-inner s-comments-flex-1" }, isAdmin && h("span", { key: '40009b2b3b9af60481d0e40d5fb919076ab43b10', class: "s-comments-item-reply-icon", innerHTML: Reply }), h("div", { key: '8f0563811445046ecf5ce3abe6ee6d7d1ec4a235', class: "s-comments-item-avatar" }, h("img", { key: '5f82bb9d3a5c4c340f37b8e48af3edce42c133f8', "data-src": this.comment?.avatar, alt: this.comment?.name, src: this.comment?.avatar, class: "s-comments-item-avatar-img lazy" })), h("div", { key: '34581ee9fddaa2f79ee933efb65c2e354010f37a', class: "s-comments-flex-1" }, h("div", { key: 'e03fc074d5ac8ddf183e3b081001e73dd62a7116', class: "s-comments-item-user-wrapper" }, h("div", { key: '6ea5e07eb9fa35d7673019628e6fde4605b74e0c', class: "s-comments-item-user-info" }, h("h3", { key: '18d7cf8c8b8125138a544bd10027ae7503f2f3a6', class: this.comment.is_pinned ? "s-comments-item-user-info-name" : "s-comments-item-user-info-name-with-margin" }, this.comment?.name), this.comment.is_pinned ?
h("div", { class: "s-comments-item-pinned-icon-with-margin" }, this.PinnedIcon())
: null, (this.comment.has_order || !!this.comment.rating) && !this.comment.is_pending && !this.hideBought ?
h("div", { class: "s-comments-flex" }, this.comment.has_order ?
[h("span", { class: "s-comments-item-has-order-check-icon", innerHTML: iconCheck }), h("span", { class: "s-comments-item-has-order-check-text" }, this.has_bought_trans, " ", this.comment.rating ? ', ' : '')] : null, !!this.comment.rating ?
h("span", { class: "s-comments-item-rated-widget" }, this.rated_trans) : null)
: null), h("p", { key: '694c2079d3379966994af7040fe4542481a53e66', class: "s-comments-item-timestamp s-ltr" }, this.getDate(this.comment.created_at?.date), h("span", { key: '734ca2eb6bf846c661007a1b9c41f22c47923b27', class: "s-comments-item-time" }, " - ", this.getTime(this.comment.created_at?.date))), !!this.comment.rating || !!this.comment.stars ?
h("salla-rating-stars", { size: "mini", class: "s-comments-item-stars", value: this.comment.rating || this.comment.stars })
: null), h("div", { key: '45e32a0759536f8c9e8bc8b823d50c931bdd1e23', class: "s-comments-item-content" }, h("p", { key: '9edf2cdfd489f77214d7a1ecae8a99943dfd4ece', innerHTML: this.comment.content }), this.allowAttachImages && h("div", { key: 'bbba6c62c4d13289137c634f5413880dfa572ab4', class: "s-comments-item-images" }, this.comment.images.map((image, index) => (h("img", { key: index, src: image, alt: "", onClick: () => this.modal.open() }))), h("salla-modal", { key: 'cd5c48b8cf3e1c9457a2fb60a42859cbd91aa996', ref: modal => this.modal = modal, width: "sm" }, h("salla-slider", { key: '304bc9417e6d7fd0e310127dce4e56a1c6ce00ea', id: `s-comments-item-${this.comment.id}-images`, class: "s-comments-item-images-slider", type: "thumbs", "auto-height": true, showControls: this.comment.images.length > 1 ? true : false, "show-thumbs-controls": "false" }, h("div", { key: '50401766737293c42fa7ba3b351b6db736fd92eb', slot: 'items' }, this.comment.images.map((image, index) => (h("img", { key: index, src: image, alt: "" })))), h("div", { key: '6b9a50da0b67497dfae21b52044850329d8712b5', slot: "thumbs" }, this.comment.images.map((image, index) => (h("div", { class: "s-comments-item-images-slider-thumb" }, h("img", { key: index, src: image, alt: "" })))))))), this.allowLikes && !isAdmin && salla.url.is_page('product.single') ? h("salla-button", { ref: el => this.likeBtn = el, class: `s-comments-item-like-btn ${this.likedComments.includes(this.comment.id) ? 'liked' : ''}`, loaderPosition: 'center', fill: 'outline', size: 'small', onClick: () => this.toggleLike() }, h("span", null, this.helpfulLabel, " ", this.likesCount > 0 ? `(${this.likesCount})` : ''), h("span", { innerHTML: ThumbsUp })) : '', this.comment.is_pending ?
h("span", { class: "s-comments-item-pending-text" }, this.waiting_approval_trans) : null))), !!this.getReplies().length && !isAdmin ?
this.getReplies().map((reply) => {
return h("div", null, h("salla-comment-item", { comment: reply }));
}) : null)));
}
get host() { return getElement(this); }
};
SallaCommentItem.style = sallaCommentsCss$1;
var CommentType;
(function (CommentType) {
CommentType["PAGE"] = "page";
CommentType["PRODUCT"] = "product";
CommentType["BLOG"] = "blog";
})(CommentType || (CommentType = {}));
var ChatBubbles = `<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>chat-bubbles</title>
<path d="M15.333 17.333c4.779 0 8.667-3.888 8.667-8.667s-3.888-8.667-8.667-8.667h-6.667c-4.779 0-8.667 3.888-8.667 8.667 0 2.985 1.513 5.712 4 7.297v4.703c0 0.497 0.277 0.953 0.717 1.183 0.195 0.1 0.405 0.151 0.616 0.151 0.269 0 0.536-0.081 0.764-0.241l6.323-4.425zM11.236 14.908l-4.569 3.199v-2.913c0-0.5-0.28-0.959-0.725-1.187-2.020-1.035-3.275-3.080-3.275-5.34 0-3.308 2.692-6 6-6h6.667c3.308 0 6 2.692 6 6s-2.692 6-6 6h-3.333c-0.273 0-0.54 0.084-0.764 0.241zM29.196 12.964c-0.543-0.5-1.388-0.464-1.884 0.077-0.5 0.541-0.465 1.385 0.077 1.884 1.253 1.156 1.944 2.72 1.944 4.408 0 2.26-1.255 4.305-3.275 5.339-0.445 0.228-0.725 0.687-0.725 1.188v2.572l-5.441-2.939c-0.195-0.104-0.412-0.16-0.633-0.16h-2.592c-1.688 0-3.309-0.724-4.451-1.988-0.492-0.545-1.335-0.591-1.883-0.096-0.547 0.493-0.589 1.336-0.096 1.883 1.644 1.823 3.988 2.868 6.429 2.868h2.255l7.111 3.84c0.199 0.107 0.417 0.16 0.635 0.16 0.236 0 0.472-0.063 0.683-0.188 0.404-0.241 0.651-0.676 0.651-1.145v-4.036c2.487-1.585 4-4.311 4-7.297 0-2.407-1.023-4.727-2.804-6.369z"></path>
</svg>
`;
const sallaCommentsCss = ":host{display:block}";
const SallaComments = class {
constructor(hostRef) {
registerInstance(this, hostRef);
/**
* Comment Type
*/
this.type = CommentType.PAGE;
/**
* Show or hide avatar
*/
this.showFormAvatar = false;
/**
* Hide Bought
*/
this.hideBought = false;
/**
* Determines if the comments are testimonials
*/
this.testimonials = false;
// Translations
this.noComments = salla.lang.get('blocks.comments.no_comments');
this.comment_title = salla.lang.get('blocks.comments.title');
this.comment_name = salla.lang.get('blocks.comments.comment');
this.showRatingSummary = salla.config.get('store.settings.rating.show_rating_summary');
this.allowLikes = salla.config.get('store.settings.rating.allow_likes');
salla.onReady(() => {
this.allowLikes = salla.config.get('store.settings.rating.allow_likes');
this.showRatingSummary = salla.config.get('store.settings.rating.show_rating_summary');
});
salla.lang.onLoaded(() => {
this.comment_title = salla.lang.get('blocks.comments.title');
this.comment_name = salla.lang.get('blocks.comments.comment');
this.noComments = salla.lang.get('pages.rating.no_ratings');
const setNestedAsync = (lang, key, value) => {
return new Promise((resolve) => {
salla.helpers.setNested(salla.lang.messages[lang], key, value);
resolve(true);
});
};
const setTranslations = async () => {
await setNestedAsync('ar.trans', 'blocks.comments.most_helpful', 'الأكثر إفادة');
await setNestedAsync('en.trans', 'blocks.comments.most_helpful', 'Most helpful');
this.mostHelpfulLabel = salla.lang.get('blocks.comments.most_helpful');
this.comment_title = salla.lang.get('blocks.comments.title');
this.comment_name = salla.lang.get('blocks.comments.comment');
this.noComments = salla.lang.get('pages.rating.no_ratings');
};
setTranslations();
});
}
// TOOD: it's a good idea to move this into lang.js
// Pluralize a string based on the count
pluralize(phrases, count) {
const options = phrases.split('|');
const conditions = [
{ condition: count === 0, index: 0 },
{ condition: count === 1, index: 1 },
{ condition: count === 2, index: 2 },
{ condition: count > 2 && count <= 10, index: 3 },
{ condition: count >= 11, index: 4 }
];
const { index } = conditions.find(({ condition }) => condition) || { index: options.length - 1 };
const selectedOption = options[index];
return selectedOption.replace(':count', salla.helpers.number(count.toString()))
.replace(/\{[0-9]+\}/g, '')
.replace(/\[\d+,\d+\]|\[11,\*\]/g, '');
}
wrapConsoleError() {
if (Salla.infiniteScroll.errorWrapped) {
return;
}
(() => {
const orig = console.error.bind(console);
console.error = (...args) => {
const msg = args[0];
// only rewrite the noisy one
if (typeof msg === 'string' && msg.toLowerCase().replace(/\s/g, '').includes('infinitescroll')) {
return console.log(...args); // downgrade to log
}
return orig(...args); // keep real errors
};
})();
Salla.infiniteScroll.errorWrapped = true;
}
// Initiate infinite scroll
initiateInfiniteScroll() {
if (!this.wrapper) {
console.error('Wrapper is undefined. Cannot initiate infinite scroll.');
return;
}
this.wrapConsoleError();
this.infiniteScroll = salla.infiniteScroll.initiate(this.wrapper, this.wrapper, {
path: () => this.nextPage,
history: false,
nextPage: this.nextPage,
scrollThreshold: false,
}, true);
this.infiniteScroll?.on('request', _response => {
this.loading();
});
this.infiniteScroll?.on('load', response => {
this.pagination = response.pagination;
this.nextPage = typeof response.pagination.links === 'object' && !!response.pagination.links.next ? response.pagination.links.next : null;
for (const card of this.handleResponse(response)) {
this.wrapper.append(card);
}
const items = this.host.querySelectorAll('salla-comment-item:not(.animated):not(.s-comments-item-admin)');
this.animateItems(items);
this.loading(false);
});
this.infiniteScroll?.on('error', (e) => {
salla.console.error('Error loading more comments:', e);
});
}
// Show/hide loading
loading(isLoading = true) {
const btnText = this.status?.querySelector('.s-button-text');
if (btnText) {
Helper.toggleElementClassIf(btnText, 's-button-hide', 's-button-show', () => isLoading);
this.btnLoader.style.display = isLoading ? 'inherit' : 'none';
}
}
// Animate newly added items
animateItems(items) {
anime({
targets: items,
opacity: [0, 1],
duration: 1200,
translateY: [20, 0],
delay: (_el, i) => i * 100,
easing: 'easeOutExpo',
complete: (_anim) => {
for (const item of items) {
item.classList.add('animated');
}
}
});
}
/**
* Reloads the comments data from the server
*/
async reload() {
this.showPlaceholder = false;
if (this.wrapper) {
this.wrapper.innerHTML = "";
const loading = document.createElement('salla-loading');
this.wrapper.append(loading);
}
this.nextPage = null;
this.loadInitialData();
}
// Get comment item HTML
getCommentHTML(comment) {
const commentItem = document.createElement('salla-comment-item');
commentItem.comment = comment;
commentItem.hideBought = this.hideBought;
return commentItem;
}
// Parse response and return an array of comment items to be appended to the wrapper
handleResponse(response) {
return response.data?.map(comment => this.getCommentHTML(comment)) || [];
}
componentWillLoad() {
return salla.onReady()
.then(() => {
this.showRatingSummary = salla.config.get('store.settings.rating.show_rating_summary');
})
.then(() => this.loading())
.then(() => {
this.hideTitle = this.hideTitle || this.testimonials;
this.hideForm = this.hideForm || this.testimonials;
return this.loadInitialData();
});
}
// Load initial data
async loadInitialData() {
try {
let resp = { data: [], pagination: {} };
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has('sort')) {
this.sort = searchParams.get('sort');
}
if (this.testimonials) {
const params = {
sort: this.sort,
type: "store"
};
resp = await salla.api.request('reviews', { params }, 'get');
}
else {
// Ensure sort is passed for regular comments as well
resp = await salla.api.comment.getComments(this.type, this.itemId, 1, 5, this.sort);
}
if (!resp.data || !resp.data.length) {
this.showPlaceholder = false;
this.loading(false);
return;
}
if (this.wrapper) {
this.wrapper.innerHTML = "";
}
this.comments = resp.data;
this.pagination = resp.pagination;
this.total = resp.pagination.total;
this.nextPage = typeof resp.pagination.links === 'object' && !!resp.pagination.links.next ? resp.pagination.links.next : null;
// Preserve sort param in next page URL for infinite scroll
if (this.nextPage && this.sort) {
try {
const url = new URL(this.nextPage, window.location.origin);
if (!url.searchParams.get('sort')) {
url.searchParams.set('sort', this.sort);
this.nextPage = url.toString();
}
}
catch (_e) {
// fallback for relative next links
const hasQuery = this.nextPage.includes('?');
const hasSort = /[?&]sort=/.test(this.nextPage);
if (!hasSort) {
this.nextPage = this.nextPage + (hasQuery ? '&' : '?') + `sort=${this.sort}`;
}
}
}
setTimeout(() => {
for (const card of this.handleResponse(resp)) {
this.wrapper.append(card);
}
this.initiateInfiniteScroll(); // Initiate infinite scroll after the initial data is loaded
const items = this.wrapper.querySelectorAll('salla-comment-item:not(.animated)');
this.animateItems(items);
}, 100);
}
catch (error) {
console.error('Error loading initial data:', error);
this.showPlaceholder = true;
this.loading(false);
}
}
// Get next page
async loadMore() {
this.infiniteScroll?.loadNextPage();
}
render() {
// We should show a different placeholder for pages and products (WIP)
if (this.showPlaceholder) {
return (h("div", null, !!this.total && !this.hideTitle ? h("h2", { class: "s-comments-title" }, this.blockTitle ? this.blockTitle : this.comment_title) : '', !this.hideForm && !this.testimonials ? h("salla-comment-form", { showAvatar: this.showFormAvatar, type: this.type, "item-id": this.itemId }) : '', h("div", { class: "s-comments-placeholder" }, h("span", { innerHTML: ChatBubbles }), h("p", null, this.noComments))));
}
return (h("div", { class: `s-comments s-comments-${this.testimonials ? 'testimonials' : this.type}` }, h("div", { class: `${this.type === CommentType.PAGE ? "s-comments-page-container" : "s-comments-container"}` }, !!this.total && !this.hideTitle ? h("h2", { class: "s-comments-title" }, this.blockTitle ? this.blockTitle : this.comment_title) : '', !this.hideForm && h("salla-comment-form", { showAvatar: this.showFormAvatar, type: this.type, "item-id": this.itemId }), salla.url.is_page('product.single') ? h("salla-reviews-summary", { itemId: this.itemId }) : '', h("div", { class: `s-comments-header ${this.total ? "has-total" : ""}` }, !!this.total && h("span", { class: "s-comments-count-label", innerHTML: this.pluralize(this.comment_name, this.total) }), !!this.total && !this.testimonials && this.type !== CommentType.BLOG ?
h("div", { class: "s-comments-filter-wrapper" }, h("label", { class: "s-comments-filter-label", htmlFor: "comments-filter" }, salla.lang.get('pages.categories.sorting')), h("select", { id: "comments-filter", "aria-label": salla.lang.get('pages.categories.sorting'), class: "s-form-control s-comments-sort-input", onChange: (e) => {
this.sort = e.target.value;
this.reload();
} }, h("option", { value: "latest", selected: true }, salla.lang.get("pages.testimonials.sort_by_date_desc")), h("option", { value: "oldest" }, salla.lang.get("pages.testimonials.sort_by_date_asc")), this.allowLikes && h("option", { value: "most_helpful" }, this.mostHelpfulLabel)))
: ''), h("div", { ref: wrapper => {
this.wrapper = wrapper;
} }), this.nextPage && (h("div", { class: "s-infinite-scroll-wrapper", ref: status => {
this.status = status;
} }, h("button", { onClick: () => this.loadMore(), class: "s-infinite-scroll-btn s-button-btn s-button-primary", type: "button" }, h("span", { class: "s-button-text s-infinite-scroll-btn-text" }, this.loadMoreText ? this.loadMoreText : salla.lang.get('common.elements.load_more')), h("span", { class: "s-button-loader s-button-loader-center s-infinite-scroll-btn-loader", ref: btnLoader => {
this.btnLoader = btnLoader;
}, style: { "display": "none" } })))))));
}
get host() { return getElement(this); }
};
SallaComments.style = sallaCommentsCss;
const sallaRatingStarsCss = "";
const SallaRatingStars = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.translationsLoaded = false;
this.labels = [];
this.reviewLabel = '';
this.selectedStar = 0;
/**
* Sets input name.
*/
this.name = 'rating';
/**
* Sets the height and width of the component. Defaults to medium.
*/
this.size = 'medium';
/**
* Number of reviews to display.
*/
this.reviews = 0;
/**
* Allows the rating to be editable.
*/
this.editable = false;
}
async componentWillLoad() {
await new Promise(resolve => {
salla.lang.onLoaded(() => {
this.labels = [
salla.lang.get('pages.rating.poor'),
salla.lang.get('pages.rating.average'),
salla.lang.get('pages.rating.good'),
salla.lang.get('pages.rating.very_good'),
salla.lang.get('pages.rating.excellent')
];
if (this.value && this.withLabel) {
this.reviewLabel = this.labels[this.value - 1];
}
if (this.reviewsElement) {
this.reviewsElement.innerText = `(${salla.helpers.number(salla.lang.choice('pages.rating.reviews', this.reviews))})`;
}
this.translationsLoaded = true;
resolve();
});
});
}
initiateRating() {
this.host.addEventListener('click', this.handleRating.bind(this));
}
handleRating() {
if (!this.starsElem)
return;
let activeStars = this.starsElem.querySelectorAll('.s-rating-stars-hovered');
let selected = activeStars[activeStars.length - 1];
if (!selected)
return;
let selectedIndex = parseInt(selected.getAttribute('data-star'));
this.starsElem.querySelector('.rating_hidden_input').value = selectedIndex.toString();
this.starsElem.querySelectorAll('.s-rating-stars-btn-star')
.forEach((star, index) => Helper.toggleElementClassIf(star, 's-rating-stars-selected', 's-rating-stars-unselected', () => index < selectedIndex));
this.starsElem.querySelectorAll('[aria-pressed]').forEach(star => star.removeAttribute('aria-pressed'));
selected.setAttribute('aria-pressed', 'true');
this.selectedStar = selectedIndex;
this.withLabel && (this.reviewLabel = this.labels[selectedIndex - 1]);
}
triggerRatingProgrammatically(index) {
if (!this.starsElem)
return;
const stars = this.starsElem.querySelectorAll('.s-rating-stars-btn-star');
if (stars && index >= 0 && index <= stars.length) {
// Simulate the hovering effect
stars.forEach((s, i) => {
s.classList.toggle('s-rating-stars-hovered', i <= index);
});
// Trigger the same logic as clicking
this.handleRating();
}
}
highlightSelectedStars() {
let hoveredClass = 's-rating-stars-hovered', stars = this.starsElem?.querySelectorAll('.s-rating-stars-btn-star');
stars?.forEach((star, index) => {
star.addEventListener('mouseover', () => {
for (let i = 0; i <= index; i++) {
stars[i].classList.add(hoveredClass);
}
this.withLabel && (this.reviewLabel = this.labels[index]);
});
star.addEventListener('mouseout', () => {
star.classList.remove(hoveredClass);
this.withLabel && (this.reviewLabel = this.selectedStar ? this.labels[this.selectedStar - 1] : '');
});
});
this.starsElem?.addEventListener('mouseout', () => stars.forEach(star => star.classList.remove(hoveredClass)));
}
createStars(n) {
let stars = [];
for (let i = 0; i < 5; i++) {
stars.push(h("span", { class: {
's-rating-stars-btn-star': true,
['s-rating-stars-' + this.size]: true,
's-rating-stars-selected': i < n
}, innerHTML: Star }));
}
if (this.reviews > 0) {
stars.push(h("span", { class: "s-rating-stars-reviews", ref: el => this.reviewsElement = el }, "(", salla.helpers.number(salla.lang.choice('pages.rating.reviews', this.reviews)), ")"));
}
return stars;
}
render() {
return this.translationsLoaded ? (this.host.closest('.swiper-slide')?.classList.contains('swiper-slide-duplicate')
? ''
: (h(Host, null, (this.value || this.value == 0) && !this.editable ?
h("div", { class: "s-rating-stars-wrapper" }, this.createStars(this.value), this.withLabel && this.reviewLabel ? h("span", { class: "s-rating-stars-label" }, this.reviewLabel) : '')
:
h("div", { class: "s-rating-stars-wrapper" }, h("div", { class: "s-rating-stars-element", ref: (el) => this.starsElem = el }, h("input", { type: "hidden", class: "rating_hidden_input", name: this.name, value: "" }), [1, 2, 3, 4, 5].map(star => h("button", { class: `s-rating-stars-btn-star s-rating-stars-` + this.size, "data-star": star }, h("span", { innerHTML: Star })))), this.withLabel && this.reviewLabel ? h("span", { class: "s-rating-stars-label" }, this.reviewLabel) : '')))) : (h(Host, null));
}
componentDidLoad() {
this.initiateRating();
this.highlightSelectedStars();
if (this.value && this.editable) {
const stars = this.starsElem?.querySelectorAll('.s-rating-stars-btn-star');
if (stars && this.value >= 0 && this.value <= stars.length) {
this.triggerRatingProgrammatically(this.value - 1);
}
}
}
get host() { return getElement(this); }
};
SallaRatingStars.style = sallaRatingStarsCss;
var IconStar2 = ` <svg width="18" height="17" view-box="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00045 13.6951L3.71036 16.6563L4.89186 10.71L0.440918 6.59397L6.4612 5.88017L9.00045 0.375122L11.5396 5.88017L17.5599 6.59397L13.109 10.71L14.2905 16.6563L9.00045 13.6951Z" fill="currentcolor" />
</svg>`;
var IconStar2Muted = ` <svg width="18" height="17" view-box="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00045 13.6951L3.71036 16.6563L4.89186 10.71L0.440918 6.59397L6.4612 5.88017L9.00045 0.375122L11.5396 5.88017L17.5599 6.59397L13.109 10.71L14.2905 16.6563L9.00045 13.6951Z" fill="#DDDDDD" />
</svg>`;
var IconFire2 = ` <svg width="12" height="17" view-box="0 0 12 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 16.2501C9.10658 16.2501 11.625 13.7317 11.625 10.6251C11.625 9.976 11.4523 9.35252 11.25 8.77232C9.99998 10.0075 9.05002 10.6251 8.4 10.6251C11.3966 5.37512 9.75 3.12512 5.25 0.125122C5.625 3.87475 3.15302 5.58043 2.14634 6.52757C1.0559 7.5535 0.375 9.00977 0.375 10.6251C0.375 13.7317 2.89339 16.2501 6 16.2501ZM6.53205 2.92636C8.96333 4.98908 8.97495 6.59185 7.09725 9.88157C6.5265 10.8815 7.2486 12.1251 8.4 12.1251C8.9163 12.1251 9.43807 11.9745 9.98917 11.6789C9.52342 13.4466 7.91393 14.7501 6 14.7501C3.72182 14.7501 1.875 12.9033 1.875 10.6251C1.875 9.47072 2.34959 8.39582 3.17419 7.62002C3.2687 7.53115 3.74812 7.1062 3.76858 7.08782C4.08646 6.8017 4.34835 6.54985 4.60718 6.2727C5.52998 5.28461 6.19283 4.18735 6.53205 2.92636Z" fill="currentcolor" />
</svg>`;
const sallaReviewCardCss = ":host{display:block}";
const SallaReviewCard = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.currentSlide = 0;
this.showPurchaseCount = false;
this.startPoint = { x: 0, y: 0 };
this.isSwiping = false;
this.isRTL = "rtl";
this.handlePointerDown = (e) => {
// Only handle primary pointer (first touch/mouse)
if (!e.isPrimary)
return;
this.sliderElement?.setPointerCapture(e.pointerId);
this.startSwipe(e.clientX, e.clientY);
e.preventDefault();
};
this.handlePointerMove = (e) => {
if (!this.isSwiping || !e.isPrimary)
return;
e.preventDefault();
};
this.handlePointerUp = (e) => {
if (!this.isSwiping || !e.isPrimary)
return;
this.sliderElement?.releasePointerCapture(e.pointerId);
this.endSwipe(e.clientX, e.clientY);
};
this.handlePointerCancel = (e) => {
if (!this.isSwiping || !e.isPrimary)
return;
this.sliderElement?.releasePointerCapture(e.pointerId);
this.isSwiping = false;
};
this.goToSlide = (index) => {
this.currentSlide = Math.max(0, Math.min(index, this.images.length - 1));
};
}
async componentDidLoad() {
await salla.onReady();
this.showPurchaseCount = !!salla.config.get('store.settings.product.total_sold_enabled', false);
this.isRTL = salla.config.get('theme.is_rtl', true);
this.purchasedCount = salla.lang.getWithDefault('blocks.home.reviews.purchased_count', this.isRTL ? ` تم شراءه ${this.review.product?.sold_quantity} مرة`
: `Purchased ${this.review.product.sold_quantity} times`, { count: this.review.product?.sold_quantity });
this.initializeSlider();
}
disconnectedCallback() {
this.removeEventListeners();
}
get images() {
const { review } = this;
return review?.images?.length > 1
? review.images.map((image, idx) => ({ url: image, alt: '', id: idx }))
: review?.product?.images || [];
}
get hasMultipleImages() {
return this.images.length > 1;
}
get slideTransform() {
const direction = this.isRTL ? 1 : -1;
return `translateX(${this.currentSlide * 100 * direction}%)`;
}
initializeSlider() {
if (!this.hasMultipleImages)
return;
this.sliderElement = this.el.querySelector(".s-review-card-slider-container");
if (!this.sliderElement)
return;
// Enable pointer events and set touch-action
this.sliderElement.style.touchAction = 'pan-y pinch-zoom';
this.addEventListeners();
}
addEventListeners() {
if (!this.sliderElement)
return;
this.sliderElement.addEventListener("pointerdown", this.handlePointerDown, { passive: false });
this.sliderElement.addEventListener("pointermove", this.handlePointerMove, { passive: false });
this.sliderElement.addEventListener("pointerup", this.handlePointerUp, { passive: true });
this.sliderElement.addEventListener("pointercancel", this.handlePointerCancel, { passive: true });
}
removeEventListeners() {
if (!this.sliderElement)
return;
this.sliderElement.removeEventListener("pointerdown", this.handlePointerDown);
this.sliderElement.removeEventListener("pointermove", this.handlePointerMove);
this.sliderElement.removeEventListener("pointerup", this.handlePointerUp);
this.sliderElement.removeEventListener("pointercancel", this.handlePointerCancel);
}
startSwipe(x, y) {
this.startPoint = { x, y };
this.isSwiping = true;
}
componentDidRender() {
this.removeEventListeners();
this.addEventListeners();
}
endSwipe(x, y) {
this.isSwiping = false;
const dx = x - this.startPoint.x;
const dy = y - this.startPoint.y;
this.processSwipe(dx, dy);
}
processSwipe(dx, dy) {
const MIN_SWIPE_DISTANCE = 10;
if (Math.abs(dx) < MIN_SWIPE_DISTANCE || Math.abs(dy) > Math.abs(dx))
return;
const isLeftSwipe = dx < 0;
const shouldGoNext = this.isRTL ? !isLeftSwipe : isLeftSwipe;
if (shouldGoNext) {
this.goToNextSlide();
}
else {
this.goToPrevSlide();
}
}
goToNextSlide() {
if (this.currentSlide < this.images.length - 1) {
this.currentSlide++;
}
}
goToPrevSlide() {
if (this.currentSlide > 0) {
this.currentSlide--;
}
}
renderStars() {
return Array(5)
.fill(null)
.map((_, index) => h("span", { key: index, innerHTML: this.review.stars >= index + 1 ? IconStar2 : IconStar2Muted }));
}
renderDots() {
return this.images.map(({ url }, index) => (h("button", { key: url || index, type: "button", class: `s-review-card-slider-dot ${this.currentSlide === index ? "active" : ""}`, onClick: () => this.goToSlide(index), "aria-label": `Go to slide ${index + 1}`, onPointerDown: () => this.goToSlide(index) })));
}
renderSlider() {
if (!this.hasMultipleImages)
return null;
return (h("div", { class: "s-review-card-slider-container" }, h("div", { class: "s-review-card-slides", style: { transform: this.slideTransform } }, this.images.map((image) => (h("div", { key: image?.id, class: "s-review-card-slider-slide" }, h("img", { src: image.url, alt: image.alt || "Product image", width: 275, height: 275, loading: "lazy", draggable: false }))))), h("div", { class: "s-review-card-slider-dots" }, this.renderDots())));
}
renderSingleImage() {
const image = this.review?.product?.image;
if (!image || this.hasMultipleImages)
return null;
return (h("img", { src: image.url, alt: image.alt || "Product image", class: "s-review-card-image", width: 275, height: 275, loading: "lazy", decoding: "async", draggable: false }));
}
renderHeader() {
return (h("div", { class: "s-review-card-header" }, h("div", { class: "s-review-card-reviewer-name" }, h("p", null, this.review?.name), this.review?.has_order && h("span", { class: "s-review-card-verified-icon", innerHTML: iconCheck })), h("div", { class: "s-review-card-stars" }, this.renderStars())));
}
renderProductInfo() {
const product = this.review?.product;
if (!product)
return null;
return (h("a", { href: this.review?.product?.url, class: "s-review-card-product-container" }, h("img", { alt: product.image?.alt || "Product", src: product.image?.url, class: "s-review-card-product-image", width: 60, height: 60, loading: "lazy", decoding: "async", draggable: false }), h("div", { class: "s-review-card-product-details" }, h("p", { class: "s-review-card-product-details-name" }, product.name), this.showPurchaseCount ? h("p", { class: "s-review-card-product-details-purchase-count" }, h("span", { innerHTML: IconFire2 }), this.purchasedCount) : null)));
}
render() {
return h("div", { key: '2c239df97aea1d28e160ede3e06bd283874bbaea', class: "s-review-card-container" }, this.renderSlider(), this.renderSingleImage(), renderDivider(), h("div", { key: '227c1fad17d6d2bf26316cabf69b854bb23cca52', class: "s-review-card-content" }, this.renderHeader(), h("p", { key: '74b06a8c24767d8e8cd7101915055d3861c5d0fd', class: "s-review-card-review-content", innerHTML: this.review?.content }), renderDivider(), this.renderProductInfo()));
}
get el() { return getElement(this); }
};
const renderDivider = (className) => (h("div", { class: `s-review-card-divider ${""}` }));
SallaReviewCard.style = sallaReviewCardCss;
var IconQuoteOpen = `<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>quote-open</title>
<path d="M8 12v-5.333c0-0.737-0.596-1.333-1.333-1.333-3.676 0-6.667 2.991-6.667 6.667v8c0 3.676 2.991 6.667 6.667 6.667h1.333c3.676 0 6.667-2.991 6.667-6.667v-1.333c0-3.676-2.991-6.667-6.667-6.667zM12 20c0 2.205-1.795 4-4 4h-1.333c-2.205 0-4-1.795-4-4v-8c0-1.739 1.115-3.221 2.667-3.772v5.105c0 0.737 0.596 1.333 1.333 1.333h1.333c2.205 0 4 1.795 4 4zM25.333 12v-5.333c0-0.737-0.596-1.333-1.333-1.333-3.676 0-6.667 2.991-6.667 6.667v8c0 3.676 2.991 6.667 6.667 6.667h1.333c3.676 0 6.667-2.991 6.667-6.667v-1.333c0-3.676-2.991-6.667-6.667-6.667zM29.333 20c0 2.205-1.795 4-4 4h-1.333c-2.205 0-4-1.795-4-4v-8c0-1.739 1.115-3.221 2.667-3.772v5.105c0 0.737 0.596 1.333 1.333 1.333h1.333c2.205 0 4 1.795 4 4z"></path>
</svg>
`;
const sallaReviewsCss = "";
const SallaReviews = class {
constructor(hostRef) {
registerInstance(this, hostRef);
/**
* Defines the maximum number of reviews to retrieve from the API.
*
* @type {number}
* @default 5
*/
this.limit = 5;
/**
* Specifies the type of reviews to fetch.
* Available options:
* - "all": Fetches reviews from all sources.
* - "store": Fetches reviews specific to the store.
* - "products": Fetches reviews specific to products.
*
* @type {ReviewType}
* @default store
*/
this.type = "store";
/**
* Specifies the sorting criteria for the fetched reviews.
* Available options:
* - "top_rating": Sorts reviews based on top ratings.
* - "random": Sorts reviews randomly.
* - "latest": Sorts reviews based on the latest ones (default).
*
* @type {SortingOption}
* @default latest
*/
this.sort = "latest";
/**
* Specifies whether to hide customer information in the component.
* When set to true, customer information will be hidden.
* Defaults to false, meaning customer information will be displayed.
*/
this.hideCustomerInfo = false;
this.reviews = [];
this.showReviews = false;
this.testimonialText = salla.lang.get('blocks.home.testimonials');
this.displayAllLinkText = salla.lang.get('blocks.home.display_all');
this.displayAllURL = null;
this.source = this.source;
salla.onReady(() => {
this.displayAllURL = salla.url.get('testimonials');
this.isRTL = salla.config.get('theme.is_rtl', true);
});
salla.lang.onLoaded(() => {
this.testimonialText = salla.lang.get('blocks.home.testimonials');
this.displayAllLinkText = salla.lang.get('blocks.home.display_all');
});
}
fetchReviews() {
if (this.source === 'json') {
return Promise.resolve(JSON.parse(this.sourceValue));
}
const isJsonEncoded = ['products', 'categories'].includes(this.source);
const params = {
limit: this.limit,
source: this.source,
items: isJsonEncoded ? JSON.parse(this.sourceValue) : this.sourceValue,
sort: this.sort,
type: this.type,
hide_customer_info: this.hideCustomerInfo ? 1 : 0
};
return salla.api.request('reviews', { params }, 'get');
}
componentWillLoad() {
return (new Promise(resolve => salla.onReady(resolve)))
.then(() => this.fetchReviews())
.then((resp) => resp.data || [])
.then(reviews => {
if (reviews.length) {
this.reviews = reviews;
this.showReviews = true;
Helper.generateReviewSchema(this.reviews);
}
});
}
render() {
return (h("div", { key: '70962f093fe86f8d24c4c7127aea2393753ae0dd', class: "s-reviews-container" }, h("div", { key: 'e462464473514c515b4406205f28759101755874', class: "s-reviews-header-wrapper" }, h("h1", { key: '4f1d93865eaede37f8c54f82f094b28abf59512c', class: "s-reviews-header" }, this.testimonialText), !!this.displayAllLink ? (h("a", { hre