/* # ----------------------------------------------------------------------------- # ~/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 = '
Icon Picker
Close
'; 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 += '
 
None
'; } 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 += '
' + iconTag + item['title'] + '
'; } else { markup += '
All' + item['title'] + '
'; } }); return markup; } }; return UniversalIconPicker; }));