CyberChef/src/web/components/c-operation-list.mjs

147 lines
4.9 KiB
JavaScript

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);