import {COperationLi} from "./c-operation-li.mjs"; import Sortable from "sortablejs"; /** * c(ustom element)-operation-list */ export class COperationList extends HTMLElement { /** * @param {App} app - The main view object for CyberChef * @param {[string, number[]]} operations - A list of operation names and indexes of characters to highlight * @param {Boolean} includeStarIcon - Include the left side 'star' icon to each of the c-category-li > * c-operation-list > c-operation-li list items in this c-category-list * @param {Boolean} isSortable - List items may be sorted ( reordered ). False by default * @param {Boolean} isCloneable - List items are cloneable to a target list. True by default * @param {Object} icon ( { class: string, innerText: string } ). 'check-icon' by default */ constructor( app, operations, includeStarIcon, isSortable = false, isCloneable = true, icon ) { super(); this.app = app; this.operations = operations; this.includeStarIcon = includeStarIcon; this.isSortable = isSortable; this.isCloneable = isCloneable; this.icon = icon; } /** * Build c-operation-list */ build() { const ul = document.createElement("ul"); ul.classList.add("op-list"); this.operations.forEach((([opName, charIndicesToHighlight]) => { const cOpLi = new COperationLi( this.app, opName, { class: this.icon ? this.icon.class : "check-icon", innerText: this.icon ? this.icon.innerText : "check" }, this.includeStarIcon, charIndicesToHighlight ); ul.appendChild(cOpLi); })); if (this.isSortable) { this.createSortableList(ul); } else if (!this.app.isMobileView() && this.isCloneable) { this.createCloneableList(ul, "recipe", "rec-list"); } this.append(ul); } /** * Create a sortable ( but not cloneable ) list * * @param { HTMLElement } ul * */ createSortableList(ul) { const sortableList = Sortable.create(ul, { group: "sorting", sort: true, draggable: "c-operation-li", filter: "i.material-icons", onFilter: function (e) { const el = sortableList.closest(e.item); if (el && el.parentNode) { $(el).popover("dispose"); el.parentNode.removeChild(el); } }, onEnd: function(e) { if (this.app.manager.recipe.removeIntent) { $(e.item).popover("dispose"); e.item.remove(); } }.bind(this), }); } /** * Create a cloneable ( not sortable ) list * * @param { HTMLElement } ul * @param { string } targetListName * @param { string } targetListId * */ createCloneableList(ul, targetListName, targetListId) { let dragOverRecList = false; const recList = document.querySelector(`#${targetListId}`); Sortable.utils.on(recList, "dragover", function () { dragOverRecList = true; }); Sortable.utils.on(recList, "dragleave", function () { dragOverRecList = false; }); Sortable.create(ul, { group: { name: targetListName, pull: "clone", put: false, }, draggable: "c-operation-li", sort: false, setData: function(dataTransfer, dragEl) { dataTransfer.setData("Text", dragEl.querySelector("li").getAttribute("data-name")); }, onStart: function(e) { // Removes popover element and event bindings from the dragged operation but not the // event bindings from the one left in the operations list. Without manually removing // these bindings, we cannot re-initialise the popover on the stub operation. $(e.item) .find("[data-toggle=popover]") .popover("dispose"); $(e.clone) .find("[data-toggle=popover]") .off(".popover") .removeData("bs.popover"); }, onEnd: ({item, to}) => { if (item.parentNode.id === targetListId && dragOverRecList) { this.app.manager.recipe.addOperation(item.name); item.remove(); } else if (!dragOverRecList && !to.classList.contains("op-list")) { item.remove(); } } }); } } customElements.define("c-operation-list", COperationList);