/*
# -----------------------------------------------------------------------------
# ~/theme_uno/modules/iconPicker/js/universal-icon-picker.js
# UniversalIconPicker v.1.1.0 implementation for J1 Theme
#
# Product/Info:
# https://jekyll.one
# https://github.com/migliori/universal-icon-picker
#
# Copyright (C) 2023, 2024 Juergen Adams
# Copyright (C) 2023 Gilles Migliori
#
# J1 Template is licensed under the MIT License.
# See: https://github.com/jekyll-one-org/j1-template/blob/main/LICENSE.md
# -----------------------------------------------------------------------------
*/
const iconPickerUrl = document.currentScript.src.replace(/js\/([a-z\.-]+)$/gm, '');
const loadedDependencies = [];
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory('UniversalIconPicker'));
} else if (typeof exports === 'object') {
module.exports = factory('UniversalIconPicker');
} else {
root['UniversalIconPicker'] = factory('UniversalIconPicker');
}
}(this, function () {
'use strict';
const createDomEle = function (string) {
const ele = document.createElement('div');
ele.innerHTML = string;
return ele.firstChild;
}
const debounce = function (func, wait, immediate) {
let timeout;
return function () {
const context = this,
args = arguments;
const later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
const escapeHtml = function (text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, function (m) { return map[m]; });
};
/**
* Merge defaults with user options
* @param {Object} defaults Default settings
* @param {Object} options User options
*/
const extend = function (defaults, options) {
let prop, extended = {};
for (prop in defaults) {
if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
extended[prop] = defaults[prop];
}
}
for (prop in options) {
if (Object.prototype.hasOwnProperty.call(options, prop)) {
extended[prop] = options[prop];
}
}
return extended;
};
const getLibraryName = function (string) {
return string.replace(/([A-Z])/g, ' $1');
}
/**
* Plugin Object
* @param selector The html selector to initialize
* @param {Object} options User options
* @constructor
*/
function UniversalIconPicker (selector, options) {
this.selector = selector;
let defaults = {
allowEmpty: true,
iconLibraries: null,
iconLibrariesCss: null,
mode: 'autoload', // autoload | onrequest
onBeforeOpen: null,
onReset: null,
onSelect: null,
resetSelector: null,
loadCustomCss: false
};
this.options = extend(defaults, options);
this.activeLibraryId = '';
this.filterIcon = '';
this.iconEventsLoaded = false;
this.iconLibraries = {};
this.iconLibrariesLoaded = false;
this.iconMarkup = '';
this.iconWrap = '';
this.idSuffix = '-' + this.selector.replace(/[#\s[\]="]/g, '');
this.sideBarBtn = '';
this.sideBarList = [];
this.universalWrap = '
';
this.universalDomEle = createDomEle(this.universalWrap);
this.sidebarTabs = this.universalDomEle.querySelector('.uip-modal--sidebar-tabs');
this.previewWrap = this.universalDomEle.querySelector('#uip-modal--icon-preview' + this.idSuffix);
this.searchInput = this.universalDomEle.querySelector('.uip-modal--icon-search input');
if (this.options.mode === 'autoload') {
this.init();
} else {
document.querySelector(this.selector).addEventListener('click', this.init.bind(this), { once: true });
}
}
// Plugin prototype
UniversalIconPicker.prototype = {
/* Public functions
------------------------------------------------------------------------ */
init: function () {
this._loadCssFiles();
if (this.options.mode !== 'autoload') {
this._onBeforeOpen().then(() => {
this.open();
});
}
document.querySelector(this.selector).addEventListener('click', () => {
this._onBeforeOpen().then(() => {
this.open();
});
});
//Remove selected icon
if (this.options.resetSelector) {
document.querySelector(this.options.resetSelector).addEventListener('click', this.options.onReset);
}
},
open: function () {
this._loadIconLibraries().then(() => {
this.iconLibrariesLoaded = true;
if (!document.getElementById('uip-modal' + this.idSuffix)) {
// push universal dom to body
document.body.appendChild(this.universalDomEle);
// jadams, 2023-05-21: disable page scrolling if modal is OPEN
document.body.classList.add('stop-scrolling');
// Icon library close by clicking close button
this.universalDomEle.querySelector('.uip-modal--header-close-btn').addEventListener('click', () => {
this.universalDomEle.classList.add('uip-close');
this.universalDomEle.classList.remove('uip-open');
// jadams, 2023-05-21: (re-)enable page scrolling if modal is CLOSED
document.body.classList.remove('stop-scrolling');
});
// Insert button
this.universalDomEle.querySelector('.uip-insert-icon-button').addEventListener('click', () => {
let selected = this.universalDomEle.querySelector('.universal-selected');
if (selected) {
let iconHtml = selected.querySelector('i').outerHTML;
let jsonOutput = {
'libraryId': selected.dataset.libraryId,
'libraryName': selected.dataset.libraryName,
'iconHtml': null,
'iconMarkup': null,
'iconClass': null,
'iconText': null
};
if (!selected.querySelector('i').classList.value.match('uip-icon-none')) {
jsonOutput.iconHtml = iconHtml;
jsonOutput.iconMarkup = escapeHtml(iconHtml);
jsonOutput.iconClass = selected.querySelector('i').classList.value;
jsonOutput.iconText = selected.querySelector('i').innerText;
}
this.options.onSelect(jsonOutput);
}
// jadams, 2023-05-21: disable modal CLOSE on a select
// this.universalDomEle.classList.add('uip-close');
// this.universalDomEle.classList.remove('uip-open');
});
} else {
// Icon library open if dom element exist
this.universalDomEle.classList.remove('uip-close');
this.universalDomEle.classList.add('uip-open');
// jadams, 2023-05-21: disable page scrolling if modal is OPEN
document.body.classList.add('stop-scrolling');
}
if (!this.iconEventsLoaded) {
// selected icon highlighted by adding class
this.universalDomEle.querySelectorAll('.uip-icon-item').forEach((item) => {
item.addEventListener('click', (evt) => {
this.iconWrap.forEach((el) => {
el.classList.remove('universal-selected');
});
evt.currentTarget.classList.toggle('universal-selected');
});
item.addEventListener('dblclick', (evt) => {
this.universalDomEle.querySelector('.uip-insert-icon-button').click();
});
});
this.iconEventsLoaded = true;
}
this.universalDomEle.querySelector('.uip-modal--icon-search input').focus();
});
},
setOptions: function (opts) {
this.options = extend(this.options, opts);
if (opts.iconLibrariesCss) {
this._loadCssFiles();
}
if (opts.iconLibraries) {
// dom icon events need to be reloaded
this.iconEventsLoaded = false;
this.iconLibrariesLoaded = false;
this._resetIconAndSidebarList();
}
},
/* Private functions
------------------------------------------------------------------------ */
_clickHandlerFunc: function (e) {
if (!e.currentTarget.classList.contains('universal-active')) {
this.sideBarBtn.forEach(function (item) {
item.classList.remove('universal-active');
});
e.currentTarget.classList.add('universal-active')
}
this._sidebarFilterFunc(e.currentTarget.dataset['libraryId']);
},
_iconItemMarkup: function (libraryName, libraryItem) {
let markup = '',
library = libraryItem['icon-style'],
prefix = libraryItem['prefix'];
if (this.options.allowEmpty) {
markup += '';
}
if (prefix.match(/^material-icons/)) {
libraryItem['icons'].forEach(function (item) {
markup += '' + item + '' + item.replace("-", " ") + '
';
});
} else {
libraryItem['icons'].forEach(function (item) {
markup += '' + item.replace("-", " ") + '
';
});
}
return markup;
},
_iconItemPush: function (arrayList) {
this.previewWrap.innerHTML = '';
arrayList.forEach((item) => {
this.previewWrap.appendChild(item[1]);
});
},
_loadCssFiles: function () {
let link = document.createElement('link');
if (!loadedDependencies.includes('universal-icon-picker.min.css') & !this.options.loadCustomCss) {
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = iconPickerUrl + 'css/universal-icon-picker.min.css';
link.media = 'screen';
document.head.appendChild(link);
loadedDependencies.push('universal-icon-picker.min.css');
}
if (this.options.iconLibrariesCss) {
this.options.iconLibrariesCss.forEach(cssFile => {
if (!loadedDependencies.includes(cssFile)) {
let cssFileLink = iconPickerUrl + 'css/' + cssFile;
if (cssFile.match(/^http|^\/\//)) {
cssFileLink = cssFile;
}
link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = cssFileLink;
link.media = 'screen';
document.head.appendChild(link);
loadedDependencies.push(cssFile);
}
});
}
},
_loadIconLibraries: async function (i = 0) {
if (!this.options.iconLibraries) {
console.error('Universal icon picker - no icon library loaded');
return false;
}
if (this.iconLibrariesLoaded) {
return true;
}
if (i === 0 && this.options.iconLibraries.length > 1) {
this.sideBarList.push({
"title": "all icons",
"list-icon": "",
"library-id": "all",
"prefix": ""
});
}
let iconLib = this.options.iconLibraries[i];
await fetch(iconPickerUrl + 'icons-libraries/' + iconLib)
.then(response => response.json())
.then(data => {
// Success!
const camelCasedIconLibrary = iconLib.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }).replace(/\.[a-z.]+$/, '');
let newLibrary = {};
newLibrary[camelCasedIconLibrary] = data;
Object.assign(this.iconLibraries, newLibrary); // new icon library merge
if (i + 1 === this.options.iconLibraries.length) {
//set icon and sidebar list
this._setIconAndSidebarList();
this.activeLibraryId = this.sideBarList[0]['library-id'];
//sidebar list markup push
this.sidebarTabs.innerHTML = this._sideBarListMarkup(this.sideBarList);
//icon markup push
this.previewWrap.innerHTML = this.iconMarkup;
// get all icon wrapper dom element
this.iconWrap = this.previewWrap.querySelectorAll('.uip-icon-item');
//set event lisner to search input
this.searchInput.addEventListener('keyup', debounce(this._searchFunc, 100).bind(this), false);
//get all sidebar list item wrapper dom element
this.sideBarBtn = this.sidebarTabs.querySelectorAll('.uip-modal--sidebar-tab-item');
//set click event lisner to sidebar list item
this.sideBarBtn.forEach((item) => {
item.addEventListener('click', this._clickHandlerFunc.bind(this), false);
});
return true;
} else {
return this._loadIconLibraries(i + 1);
}
}).catch((error) => {
console.log(error);
return error;
});
},
_onBeforeOpen: async function () {
if (typeof (this.options.onBeforeOpen) === 'function') {
return this.options.onBeforeOpen();
}
},
_resetIconAndSidebarList: function () {
this.sideBarList = [];
this.iconMarkup = '';
this.iconLibraries = {};
this.iconWrap = '';
this.filterIcon = '';
this.sideBarBtn = '';
this.activeLibraryId = '';
},
_searchFunc: function (e) {
// console.log(this.value.toLowerCase());
const searchText = e.target.value.toLowerCase();
this._searchFilterFunc(searchText, 'filter');
},
_searchFilterFunc: function (filterText, dataName) {
this.filterIcon = Object.entries(this.iconWrap).filter((item) => {
if (-1 == item[1].dataset[dataName].indexOf(filterText) || (this.activeLibraryId !== 'all' && item[1].dataset['libraryId'] !== this.activeLibraryId)) {
return false;
}
return true;
});
this._iconItemPush(this.filterIcon);
},
_setIconAndSidebarList: function () {
for (const [libraryName, libraryContent] of Object.entries(this.iconLibraries)) {
this._setSideBarList(getLibraryName(libraryName), libraryContent);
this._setIconMarkup(libraryName, libraryContent);
}
},
_setIconMarkup: function (libraryName, libraryContent) {
if (libraryContent.icons !== undefined) {
this.iconMarkup += this._iconItemMarkup(libraryName, libraryContent)
} else {
Object.entries(libraryContent).forEach((item) => {
this.iconMarkup += this._iconItemMarkup(libraryName, item[1])
});
}
},
_sidebarFilterFunc: function (filterText) {
this.activeLibraryId = filterText;
this.filterIcon = Object.entries(this.iconWrap).filter(function (item) {
if ('all' === filterText || filterText === item[1].dataset['libraryId']) {
return true;
}
return false;
});
this._iconItemPush(this.filterIcon);
},
_setSideBarList: function (libraryName, libraryContent) {
let listItem;
if (libraryContent.icons !== undefined) {
listItem = {
'title': libraryName,
'prefix': libraryContent['prefix'] !== undefined ? libraryContent['prefix'] : '',
'list-icon': libraryContent['list-icon'] !== undefined ? libraryContent['list-icon'] : '',
'library-id': libraryContent['icon-style'] !== undefined ? libraryContent['icon-style'] : 'all',
};
this.sideBarList.push(listItem);
} else {
Object.entries(libraryContent).forEach(item => {
listItem = {
"title": libraryName + ' - ' + item[0],
"prefix": item[1]['prefix'] !== undefined ? item[1]['prefix'] : '',
"list-icon": item[1]['list-icon'] !== undefined ? item[1]['list-icon'] : "",
"library-id": item[1]['icon-style'] !== undefined ? item[1]['icon-style'] : "all",
};
this.sideBarList.push(listItem)
});
}
},
_sideBarListMarkup: function (sideBarList) {
let markup = '';
sideBarList.forEach((item) => {
let activeClazz = '';
if (item['library-id'] === this.activeLibraryId) {
activeClazz = ' universal-active';
}
if ('all' !== item['library-id']) {
let iconTag = '';
if (item['prefix'].match(/^material-icons/)) {
iconTag = '' + item['list-icon'] + '';
}
markup += '';
} else {
markup += '';
}
});
return markup;
}
};
return UniversalIconPicker;
}));