import data from "@emoji-mart/data"
import i18nEn from "@emoji-mart/data/i18n/en.json"
import { Picker } from "emoji-mart"
import * as i18n from "src/decidim/i18n";
import { screens } from "tailwindcss/defaultTheme"
class EmojiI18n {
static isObject(item) {
return (item && typeof item === "object" && !Array.isArray(item));
}
static deepMerge(target, ...sources) {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (this.isObject(target) && this.isObject(source)) {
for (const key in source) {
if (this.isObject(source[key])) {
if (!target[key]) {
Object.assign(target, { [key]: {} });
}
this.deepMerge(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return this.deepMerge(target, ...sources);
}
static locale() {
return document.documentElement.getAttribute("lang");
}
static i18n() {
return this.deepMerge(i18nEn, i18n.getMessages("emojis"));
}
}
class EmojiPopUp {
constructor(pickerOptions, handlerElement) {
this.popUp = this.createContainer();
this.popUp.appendChild(this.createCloseButton());
this.popUp.appendChild(this.addStyles());
let container = document.createElement("div");
this.picker = new Picker({
parent: container,
i18n: EmojiI18n.i18n(),
locale: EmojiI18n.locale(),
data: data,
perLine: 8,
theme: "light",
emojiButtonSize: 41,
emojiSize: 30,
...(window.matchMedia(`(max-width: ${screens.sm})`).matches && { emojiButtonSize: 36 }),
...(window.matchMedia(`(max-width: ${screens.sm})`).matches && { emojiSize: 30 }),
...pickerOptions
});
this.popUp.appendChild(container);
this.setCoordinates(handlerElement);
}
createCloseButton() {
let closeButton = document.createElement("button");
closeButton.type = "button";
closeButton.classList.add("emoji-picker__closeButton");
closeButton.innerHTML = '';
closeButton.addEventListener("click", () => {
this.close();
});
return closeButton;
}
addStyles() {
let style = document.createElement("style");
style.innerHTML = `
em-emoji-picker {
--color-border: rgb(204, 204, 204);
--rgb-background: 249, 250, 251;
--rgb-color: 0,0,0;
--rgb-accent: var(--primary-rgb);
--shadow: 5px 5px 15px -8px rgba(0,0,0,0.75);
--color-border-over: rgba(0, 0, 0, 0.1);
--rgb-input: 235, 235, 235;
--background-rgb: var(--primary-rgb);
--category-icon-size: 24px;
border: 1px solid var(--color-border);
}
`;
return style;
}
createContainer() {
const container = document.createElement("div");
container.classList.add("emoji-picker__popupContainer");
container.classList.add("emoji__decidim");
container.id = "picker"
container.style.position = "absolute";
container.style.zIndex = "1000";
document.body.appendChild(container);
return container;
}
setCoordinates(handlerElement) {
let rect = handlerElement.getBoundingClientRect();
let leftPosition = window.scrollX + rect.x;
let topPosition = window.scrollY + rect.y;
topPosition -= this.popUp.offsetHeight;
leftPosition -= this.popUp.offsetWidth;
let popUpWidth = window.matchMedia(`(max-width: ${screens.sm})`).matches
? 41 * 9
: 36 * 8;
// Emoji picker min-width of 352px set in styles.scss in emoji-mart
leftPosition -= popUpWidth;
if (leftPosition < 0) {
leftPosition = parseInt((window.screen.availWidth - popUpWidth) / 2, 10) + 30;
}
this.popUp.style.top = `${topPosition}px`;
this.popUp.style.left = `${leftPosition}px`;
}
close() {
this.popUp.remove();
}
}
export class EmojiButton {
constructor(elem) {
const wrapper = document.createElement("span");
wrapper.className = "emoji__container"
const btnContainer = document.createElement("span");
btnContainer.className = "emoji__trigger"
const btn = document.createElement("button");
btn.className = "emoji__button"
btn.type = "button"
btn.setAttribute("aria-label", EmojiI18n.i18n().button)
btn.innerHTML = ''
const referenceElement = document.createElement("span");
referenceElement.className = "emoji__reference";
const parent = elem.parentNode;
parent.insertBefore(wrapper, elem);
wrapper.appendChild(elem);
wrapper.appendChild(btnContainer);
wrapper.appendChild(referenceElement);
btnContainer.appendChild(btn);
// The form errors need to be in the same container with the field they
// belong to for Foundation Abide to show them automatically.
parent.querySelectorAll(".form-error").forEach((el) => wrapper.appendChild(el));
let emojiSelectHandler = (emojidata) => {
let emoji = emojidata.native;
if (elem.contentEditable === "true") {
if (elem.editor) {
elem.editor.chain().insertContent(` ${emoji} `).focus().run();
} else {
elem.innerHTML += ` ${emoji} `
}
} else {
elem.value += ` ${emoji} `
}
// Make sure the input event is dispatched on the input/textarea elements
if (elem.tagName === "TEXTAREA" || elem.tagName === "INPUT") {
elem.dispatchEvent(new Event("input"));
}
const event = new Event("emoji.added");
elem.dispatchEvent(event);
}
let handlerPicker = () => {
let popUp = document.getElementById("picker");
if (popUp) {
// We close the picker
popUp.remove();
return;
}
let pickerOptions = {
onEmojiSelect: (emoji) => emojiSelectHandler(emoji),
onClickOutside: (event) => {
if (event.target.parentNode === btn) {
return;
}
handlerPicker();
}
}
// eslint-disable-next-line no-new
new EmojiPopUp(pickerOptions, btn);
}
btn.addEventListener("click", handlerPicker);
elem.addEventListener("emoji.added", handlerPicker);
elem.addEventListener("characterCounter", (event) => {
if (event.detail.remaining >= 4) {
btn.addEventListener("click", handlerPicker);
btn.removeAttribute("style");
} else {
btn.removeEventListener("click", handlerPicker);
btn.setAttribute("style", "color:lightgrey");
}
});
}
}
/**
* Adds the input emojis to the input elements that are defined to have them.
*
* @param {HTMLElement} element target node
* @returns {void}
*/
export default function addInputEmoji(element = document) {
const containers = element.querySelectorAll("[data-input-emoji]");
if (containers.length) {
containers.forEach((elem) => new EmojiButton(elem))
}
};