UNPKG

csdsolutions-csdjs

Version:

Libreria per i progetti di CSD Solutions

1,753 lines (1,540 loc) 80 kB
function initializeEditor(element) { if (!element || element.initialized) return; const $textarea = $CSD(element); const $container = $CSD('<div class="csd-editor-container"></div>'); $textarea.wrap($container); // Create menu bar const $menuBar = createMenuBar(); // Create toolbar const $toolbar = createToolbar(); // Create content area const $editor = $CSD( '<div class="csd-editor-content" contenteditable="true"></div>' ); $editor.attr("placeholder", $textarea.attr("placeholder") || ""); // Create status bar const $statusBar = createStatusBar(); // Insert components $textarea.before($menuBar); $textarea.before($toolbar); $textarea.before($editor); $textarea.after($statusBar); $textarea.hide(); // Set initial content $editor.html($textarea.val()); // Event handlers initializeEventHandlers($editor, $textarea); // Initialize autoformatting initializeAutoformatting($editor); // Mark as initialized element.initialized = true; } function initializeEventHandlers($editor, $textarea) { const $container = $editor.closest(".csd-editor-container"); // Content changes $editor.on("input", function () { $textarea.val($editor.html()); $textarea.trigger("change"); updateWordCount($container); }); // Toolbar actions $container.on("click", ".csd-editor-tool", function (e) { e.preventDefault(); const tool = $CSD(this).data("tool"); executeCommand(tool, $editor); }); // Menu actions $container.on("click", ".csd-editor-menu-item button", function (e) { e.preventDefault(); const action = $CSD(this).data("action"); executeMenuAction(action, $editor); }); // Initialize word count updateWordCount($container); // Paste handling $editor.on("paste", function (e) { e.preventDefault(); // Prendi il contenuto dagli appunti const clipboardData = e.originalEvent.clipboardData || window.clipboardData; const pastedData = clipboardData.getData("text/html") || clipboardData.getData("text"); // Crea un elemento temporaneo per pulire il contenuto const div = document.createElement("div"); div.innerHTML = pastedData; // Rimuovi stili indesiderati const elements = div.getElementsByTagName("*"); for (let el of elements) { // Mantieni solo gli stili di base const allowedStyles = [ "font-weight", "font-style", "text-decoration", "text-align", ]; const style = el.style; for (let i = style.length - 1; i >= 0; i--) { const prop = style[i]; if (!allowedStyles.includes(prop)) { style.removeProperty(prop); } } // Rimuovi classi e ID el.removeAttribute("class"); el.removeAttribute("id"); } // Inserisci il contenuto pulito document.execCommand("insertHTML", false, div.innerHTML); }); } function executeCommand(tool, $editor) { $editor.focus(); const command = tool.dataset ? tool.dataset.command : tool; const value = tool.dataset ? tool.dataset.value : null; switch (command) { // Basic formatting case "undo": case "redo": case "bold": case "italic": case "underline": case "strikethrough": document.execCommand(command, false); break; // Headings case "heading": showDropdown($editor, [ { text: "Heading 1", command: "formatBlock", value: "h1", }, { text: "Heading 2", command: "formatBlock", value: "h2", }, { text: "Heading 3", command: "formatBlock", value: "h3", }, { text: "Paragraph", command: "formatBlock", value: "p", }, ]); break; case "formatBlock": const selection = window.getSelection(); const range = selection.getRangeAt(0); document.execCommand(command, false, value); break; // Font styling case "font": showDropdown($editor, [ { text: "Arial", command: "fontName", value: "Arial", }, { text: "Times New Roman", command: "fontName", value: "Times New Roman", }, { text: "Courier New", command: "fontName", value: "Courier New", }, { text: "Georgia", command: "fontName", value: "Georgia", }, ]); break; case "fontName": document.execCommand(command, false, value); break; case "fontSize": if (value === null) { showDropdown($editor, [ { text: "Small", command: "fontSize", value: "1", }, { text: "Normal", command: "fontSize", value: "3", }, { text: "Large", command: "fontSize", value: "5", }, { text: "Huge", command: "fontSize", value: "7", }, ]); } else { document.execCommand(command, false, value); } break; // Colors case "textColor": showColorPicker($editor, (color) => { const selection = window.getSelection(); const range = selection.getRangeAt(0); if (range.collapsed) { // Se non c'è selezione, crea uno span colorato const span = document.createElement("span"); span.style.color = color; span.innerHTML = "\u200B"; // Zero-width space range.insertNode(span); range.setStartAfter(span); } else { // Se c'è selezione, applica il colore al testo selezionato const span = document.createElement("span"); span.style.color = color; range.surroundContents(span); } }); break; case "backgroundColor": showColorPicker($editor, (color) => { const selection = window.getSelection(); const range = selection.getRangeAt(0); if (range.collapsed) { // Se non c'è selezione, crea uno span con background colorato const span = document.createElement("span"); span.style.backgroundColor = color; span.innerHTML = "\u200B"; // Zero-width space range.insertNode(span); range.setStartAfter(span); } else { // Se c'è selezione, applica il background al testo selezionato const span = document.createElement("span"); span.style.backgroundColor = color; range.surroundContents(span); } }); break; // Alignment case "alignment": showDropdown($editor, [ { text: "Left", icon: "fa-solid fa-align-left", command: "justifyLeft", }, { text: "Center", icon: "fa-solid fa-align-center", command: "justifyCenter", }, { text: "Right", icon: "fa-solid fa-align-right", command: "justifyRight", }, { text: "Justify", icon: "fa-solid fa-align-justify", command: "justifyFull", }, ]); break; case "justifyLeft": case "justifyCenter": case "justifyRight": case "justifyFull": document.execCommand(command, false); break; // Lists case "numberedList": document.execCommand("insertOrderedList", false); break; case "bulletedList": document.execCommand("insertUnorderedList", false); break; // Indentation case "indent": document.execCommand("indent", false); break; case "outdent": document.execCommand("outdent", false); break; // Insert elements case "link": showLinkPopover($editor); break; case "image": showImagePicker($editor); break; case "table": showTablePicker($editor); break; case "specialCharacters": showSpecialCharacters($editor); break; case "pageBreak": document.execCommand("insertHTML", false, '<hr class="page-break">'); break; case "sourceEditing": toggleSourceMode($editor); break; case "paste": // Gestisci l'evento paste direttamente nell'editor const handlePaste = function (e) { e.preventDefault(); // Ottieni il contenuto dagli appunti const clipboardData = e.clipboardData || window.clipboardData; let content; // Prova prima a ottenere HTML, altrimenti usa testo semplice if (clipboardData.types.includes("text/html")) { content = clipboardData.getData("text/html"); // Crea un elemento temporaneo per pulire l'HTML const tempDiv = document.createElement("div"); tempDiv.innerHTML = content; // Rimuovi tutti gli stili inline e attributi indesiderati const cleanNode = function (node) { if (node.nodeType === 1) { // Element node // Rimuovi tutti gli attributi tranne quelli permessi const allowedAttrs = ["href", "src", "alt"]; const attrs = Array.from(node.attributes); attrs.forEach((attr) => { if (!allowedAttrs.includes(attr.name)) { node.removeAttribute(attr.name); } }); // Gestisci stili inline if (node.style.length > 0) { const allowedStyles = [ "font-weight", "font-style", "text-decoration", "text-align", ]; const styles = Array.from(node.style); styles.forEach((style) => { if (!allowedStyles.includes(style)) { node.style.removeProperty(style); } }); } // Pulisci ricorsivamente i nodi figli Array.from(node.children).forEach(cleanNode); } }; cleanNode(tempDiv); content = tempDiv.innerHTML; } else { // Se non c'è HTML, usa il testo semplice content = clipboardData.getData("text/plain"); content = content.replace(/\n/g, "<br>"); } // Inserisci il contenuto pulito const selection = window.getSelection(); if (selection.rangeCount) { const range = selection.getRangeAt(0); range.deleteContents(); // Crea un frammento con il contenuto pulito const fragment = document.createDocumentFragment(); const temp = document.createElement("div"); temp.innerHTML = content; while (temp.firstChild) { fragment.appendChild(temp.firstChild); } range.insertNode(fragment); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); } }; // Aggiungi il gestore dell'evento paste all'editor $editor.elements[0].addEventListener("paste", handlePaste); break; default: if (command) { document.execCommand(command, false, value || null); } break; } } function executeMenuAction(action, $editor) { switch (action) { case "new": if (confirm("Create new document? All changes will be lost.")) { $editor.html(""); } break; case "open": const input = document.createElement("input"); input.type = "file"; input.accept = ".txt,.html,.md"; input.onchange = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { $editor.html(e.target.result); }; reader.readAsText(file); } }; input.click(); break; case "save": const content = $editor.html(); const blob = new Blob([content], { type: "text/html" }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "document.html"; a.click(); window.URL.revokeObjectURL(url); break; case "export-pdf": alert( "PDF export functionality requires a PDF generation library. Please implement based on your needs." ); break; case "export-word": const wordContent = $editor.html(); const wordBlob = new Blob([wordContent], { type: "application/msword" }); const wordUrl = window.URL.createObjectURL(wordBlob); const wordLink = document.createElement("a"); wordLink.href = wordUrl; wordLink.download = "document.doc"; wordLink.click(); window.URL.revokeObjectURL(wordUrl); break; } } function showDropdown($editor, items) { const $dropdown = $CSD('<div class="csd-editor-dropdown"></div>'); items.forEach((item) => { const $item = $CSD(` <button type="button" class="csd-editor-dropdown-item csd-editor-tool" data-command="${ item.command || "" }" data-value="${item.value || ""}"> ${item.icon ? `<i class="${item.icon}"></i>` : ""} <span>${item.text}</span> </button> `); $dropdown.append($item); }); const $button = $CSD(event.target).closest(".csd-editor-tool"); const buttonRect = $button[0].getBoundingClientRect(); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; $dropdown.css({ position: "absolute", top: buttonRect.bottom + scrollTop + "px", left: buttonRect.left + scrollLeft + "px", }); $CSD("body").append($dropdown); // Gestisci il click sugli item del dropdown $dropdown.on("click", ".csd-editor-dropdown-item", function (e) { e.preventDefault(); e.stopPropagation(); const command = this.dataset.command; const value = this.dataset.value; executeCommand(this, $editor); $dropdown.remove(); }); // Previeni la chiusura quando si clicca dentro il dropdown $dropdown.on("mousedown", function (e) { e.preventDefault(); e.stopPropagation(); }); setTimeout(() => { $CSD(document).on("mousedown", function (e) { if (!$dropdown.elements[0].contains(e.target)) { $dropdown.remove(); $CSD(document).off("mousedown"); } }); }, 0); } function showColorPicker($editor, callback) { const $picker = $CSD(` <div class="csd-editor-color-picker"> <div class="color-grid"> ${[ "#000000", "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFFFFF", "#808080", "#800000", "#008000", "#000080", "#808000", "#800080", "#008080", "#C0C0C0", ] .map( (color) => ` <button type="button" class="color-item" style="background-color: ${color}" data-color="${color}"></button> ` ) .join("")} </div> </div> `); const $button = $CSD(event.target).closest(".csd-editor-tool"); const buttonRect = $button[0].getBoundingClientRect(); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; $picker.css({ position: "absolute", top: buttonRect.bottom + scrollTop + "px", left: buttonRect.left + scrollLeft + "px", }); $CSD("body").append($picker); $picker.on("click", ".color-item", function () { const color = $CSD(this).data("color"); callback(color); $picker.remove(); }); setTimeout(() => { $CSD(document).one("click", () => $picker.remove()); }, 0); } function showSpecialCharacters($editor) { const specialChars = [ "€", "£", "$", "¥", "¢", "₽", "₹", "±", "×", "÷", "≠", "≈", "≤", "≥", "∞", "∑", "∏", "√", "∫", "∂", "∆", "¼", "½", "¾", "⅓", "⅔", "⅕", "⅖", "⅗", "⅘", "←", "→", "↑", "↓", "↔", "↕", "⇐", "⇒", "⇑", "⇓", "©", "®", "™", "§", "¶", "†", "‡", "•", "·", "…", "‰", "′", "″", "À", "Á", "È", "É", "Ì", "Í", "Ò", "Ó", "Ù", "Ú", "à", "á", "è", "é", "ì", "í", "ò", "ó", "ù", "ú", ]; const $picker = $CSD('<div class="csd-editor-special-chars"></div>'); const $grid = $CSD(` <div class="special-chars-grid"> ${specialChars .map( (char) => ` <button type="button" class="special-char-item" title="${char}">${char}</button> ` ) .join("")} </div> `); $picker.append($grid); // Stili inline per il picker $picker.css({ position: "absolute", background: "#fff", border: "1px solid #ddd", borderRadius: "4px", padding: "10px", boxShadow: "0 2px 8px rgba(0,0,0,0.15)", zIndex: 1000, maxHeight: "400px", overflowY: "auto", width: "400px", }); $picker.find(".special-chars-grid").css({ display: "grid", gridTemplateColumns: "repeat(10, 1fr)", gap: "5px", }); // Stili per i bottoni $picker.find(".special-char-item").css({ width: "30px", height: "30px", border: "1px solid #ddd", borderRadius: "4px", background: "#fff", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "16px", transition: "all 0.2s", }); // Posizionamento const $button = $CSD(event.target).closest(".csd-editor-tool"); const buttonRect = $button[0].getBoundingClientRect(); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; $picker.css({ top: buttonRect.bottom + scrollTop + 5 + "px", left: buttonRect.left + scrollLeft + "px", }); $CSD("body").append($picker); // Hover effect per i bottoni $picker.find(".special-char-item").hover( function () { $(this).css({ background: "#f5f5f5", borderColor: "#999" }); }, function () { $(this).css({ background: "#fff", borderColor: "#ddd" }); } ); // Click handler $picker.on("click", ".special-char-item", function (e) { e.stopPropagation(); const char = $CSD(this).text().trim(); document.execCommand("insertText", false, char); $picker.remove(); }); // Chiudi il picker quando si clicca fuori setTimeout(() => { $CSD(document).one("mousedown", function (e) { if (!$picker[0].contains(e.target)) { $picker.remove(); } }); }, 0); } function insertTable($editor, rows, cols) { let tableHtml = "<table><tbody>"; for (let i = 0; i < rows; i++) { tableHtml += "<tr>"; for (let j = 0; j < cols; j++) { tableHtml += "<td><br></td>"; } tableHtml += "</tr>"; } tableHtml += "</tbody></table>"; const editorElement = $editor.elements[0]; const selection = window.getSelection(); let range; // Se c'è una selezione usa quella, altrimenti crea un nuovo range alla fine if (selection.rangeCount > 0) { range = selection.getRangeAt(0); // Verifica che il range sia all'interno dell'editor if (!editorElement.contains(range.commonAncestorContainer)) { range = null; } } // Se non abbiamo un range valido, troviamo il punto di inserimento corretto if (!range) { range = document.createRange(); // Se c'è un nodo di testo attivo, usa quello if (document.activeElement === editorElement && selection.focusNode) { if (selection.focusNode.nodeType === Node.TEXT_NODE) { range.setStart(selection.focusNode, selection.focusOffset); range.setEnd(selection.focusNode, selection.focusOffset); } else { // Se il nodo focus non è un nodo di testo, cerca il punto di inserimento più vicino const walker = document.createTreeWalker( editorElement, NodeFilter.SHOW_TEXT, null, false ); let node; let closestNode = null; let minDistance = Infinity; while ((node = walker.nextNode())) { const range = document.createRange(); range.selectNode(node); const rect = range.getBoundingClientRect(); const distance = Math.abs( rect.top - selection.focusNode.getBoundingClientRect().top ); if (distance < minDistance) { minDistance = distance; closestNode = node; } } if (closestNode) { range.setStart(closestNode, closestNode.length); range.setEnd(closestNode, closestNode.length); } else { // Se non troviamo nessun nodo di testo, crea uno nuovo const textNode = document.createTextNode(""); editorElement.appendChild(textNode); range.setStart(textNode, 0); range.setEnd(textNode, 0); } } } else { // Se l'editor non è attivo, inserisci alla fine let lastNode = editorElement.lastChild; // Se l'ultimo nodo è un BR, crea un nuovo nodo di testo if (!lastNode || lastNode.nodeName === "BR") { const textNode = document.createTextNode(""); editorElement.appendChild(textNode); lastNode = textNode; } range.selectNode(lastNode); range.collapse(false); } // Aggiorna la selezione selection.removeAllRanges(); selection.addRange(range); } const table = document.createElement("table"); table.innerHTML = tableHtml; // Inserisci la tabella range.deleteContents(); range.insertNode(table); // Aggiungi uno spazio dopo la tabella per facilitare l'editing const space = document.createElement("p"); space.innerHTML = "<br>"; table.parentNode.insertBefore(space, table.nextSibling); // Sposta il cursore dopo la tabella const newRange = document.createRange(); newRange.setStartAfter(space); newRange.collapse(true); selection.removeAllRanges(); selection.addRange(newRange); } function toggleSourceMode($editor) { const $container = $editor.closest(".csd-editor-container"); const isSourceMode = $container.hasClass("source-mode"); if (isSourceMode) { const sourceCode = $editor.text(); $editor.html(sourceCode); $container.removeClass("source-mode"); } else { const htmlContent = $editor.html(); $editor.text(htmlContent); $container.addClass("source-mode"); } } function updateWordCount($container) { const text = $container.find(".csd-editor-content").text(); const words = text.trim().split(/\s+/).length; const chars = text.length; $container.find(".csd-editor-wordcount").text(`Words: ${words}`); $container.find(".csd-editor-charcount").text(`Characters: ${chars}`); } // Markdown-style autoformatting function initializeAutoformatting($editor) { // Formatta il contenuto iniziale function formatInitialContent() { const content = $editor.html(); if (!content) return; // Dividi il contenuto in linee const lines = content.split(/\n|<br\/?>/); const formattedLines = lines.map((line) => { line = line.trim(); // Headings if (line.match(/^#\s/)) { return `<h1>${line.replace(/^#\s/, "")}</h1>`; } else if (line.match(/^##\s/)) { return `<h2>${line.replace(/^##\s/, "")}</h2>`; } else if (line.match(/^###\s/)) { return `<h3>${line.replace(/^###\s/, "")}</h3>`; } // Lists else if (line.match(/^[\*\-]\s/)) { return `<ul><li>${line.replace(/^[\*\-]\s/, "")}</li></ul>`; } else if (line.match(/^1\.\s/)) { return `<ol><li>${line.replace(/^1\.\s/, "")}</li></ol>`; } // Blockquote else if (line.match(/^>\s/)) { return `<blockquote>${line.replace(/^>\s/, "")}</blockquote>`; } // Code block else if (line.match(/^```/)) { return "<pre><code>"; } // Inline formatting else { // Bold line = line.replace(/\*\*([^\*]+)\*\*/g, "<strong>$1</strong>"); // Italic line = line.replace(/\*([^\*]+)\*/g, "<em>$1</em>"); // Code line = line.replace(/`([^`]+)`/g, "<code>$1</code>"); // Links line = line.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, '<a href="$2">$1</a>'); return line ? `<p>${line}</p>` : "<p><br></p>"; } }); $editor.html(formattedLines.join("")); } // Formatta il contenuto iniziale formatInitialContent(); // Gestisci gli eventi di formattazione durante l'editing $editor.on("keydown", function (e) { if (e.key === "Enter") { const selection = window.getSelection(); const line = selection.anchorNode.textContent; // Headings if (line.match(/^#\s/)) { e.preventDefault(); document.execCommand("formatBlock", false, "h1"); document.execCommand("delete", false); document.execCommand("delete", false); } else if (line.match(/^##\s/)) { e.preventDefault(); document.execCommand("formatBlock", false, "h2"); document.execCommand("delete", false); document.execCommand("delete", false); document.execCommand("delete", false); } else if (line.match(/^###\s/)) { e.preventDefault(); document.execCommand("formatBlock", false, "h3"); document.execCommand("delete", false); document.execCommand("delete", false); document.execCommand("delete", false); document.execCommand("delete", false); } // Lists else if (line.match(/^[\*\-]\s/)) { e.preventDefault(); document.execCommand("insertUnorderedList", false); document.execCommand("delete", false); document.execCommand("delete", false); } else if (line.match(/^1\.\s/)) { e.preventDefault(); document.execCommand("insertOrderedList", false); document.execCommand("delete", false); document.execCommand("delete", false); document.execCommand("delete", false); } // Blockquote else if (line.match(/^>\s/)) { e.preventDefault(); document.execCommand("formatBlock", false, "blockquote"); document.execCommand("delete", false); document.execCommand("delete", false); } // Code block else if (line.match(/^```/)) { e.preventDefault(); document.execCommand("formatBlock", false, "pre"); const pre = selection.anchorNode.parentElement; if (pre.tagName === "PRE") { const code = document.createElement("code"); pre.appendChild(code); const range = document.createRange(); range.setStart(code, 0); selection.removeAllRanges(); selection.addRange(range); } document.execCommand("delete", false); document.execCommand("delete", false); document.execCommand("delete", false); } } // Keyboard shortcuts else if (e.ctrlKey) { switch (e.key.toLowerCase()) { case "b": e.preventDefault(); document.execCommand("bold", false); break; case "i": e.preventDefault(); document.execCommand("italic", false); break; case "u": e.preventDefault(); document.execCommand("underline", false); break; } } // Tab handling else if (e.key === "Tab") { e.preventDefault(); document.execCommand(e.shiftKey ? "outdent" : "indent", false); } // Inline formatting else if (e.key === " ") { const selection = window.getSelection(); const line = selection.anchorNode.textContent; const position = selection.anchorOffset; // Bold with **text** if (line.match(/\*\*([^\*]+)\*\*$/)) { e.preventDefault(); const text = line.match(/\*\*([^\*]+)\*\*$/)[1]; replaceText( selection, text, position, "**", document.execCommand.bind(document, "bold", false) ); } // Italic with *text* else if (line.match(/\*([^\*]+)\*$/)) { e.preventDefault(); const text = line.match(/\*([^\*]+)\*$/)[1]; replaceText( selection, text, position, "*", document.execCommand.bind(document, "italic", false) ); } // Code with `code` else if (line.match(/`([^`]+)`$/)) { e.preventDefault(); const text = line.match(/`([^`]+)`$/)[1]; replaceText(selection, text, position, "`", () => { document.execCommand("fontName", false, "monospace"); }); } // Links with [text](url) else if (line.match(/\[([^\]]+)\]\(([^\)]+)\)$/)) { e.preventDefault(); const matches = line.match(/\[([^\]]+)\]\(([^\)]+)\)$/); const text = matches[1]; const url = matches[2]; const range = document.createRange(); range.setStart(selection.anchorNode, position - matches[0].length); range.setEnd(selection.anchorNode, position); selection.removeAllRanges(); selection.addRange(range); document.execCommand("createLink", false, url); document.execCommand("insertText", false, " "); } } }); } // Helper function to replace markdown text with formatted text function replaceText(selection, text, position, marker, formatCommand) { const range = document.createRange(); range.setStart( selection.anchorNode, position - (text.length + 2 * marker.length) ); range.setEnd(selection.anchorNode, position); selection.removeAllRanges(); selection.addRange(range); formatCommand(); document.execCommand("insertText", false, text + " "); } // Funzione per ottenere i valori dei checkbox window.getCheckboxValues = function (name) { const values = []; const $checkboxes = $CSD(`.csd-checkbox-wrapper input[name="${name}"]`); $checkboxes.each(function () { const $input = $CSD(this); values.push({ value: $input.val() || "on", checked: $input.elements[0].checked, disabled: $input.elements[0].disabled, label: $input .closest(".csd-checkbox-wrapper") .find(".csd-checkbox-label") .text(), }); }); return values; }; // Funzione per ottenere il valore del radio selezionato window.getRadioValue = function (name) { const values = []; const $radios = $CSD(`.csd-radio-wrapper input[name="${name}"]`); $radios.each(function () { const $input = $CSD(this); values.push({ value: $input.val() || "on", checked: $input.elements[0].checked, disabled: $input.elements[0].disabled, label: $input .closest(".csd-radio-wrapper") .find(".csd-radio-label") .text(), }); }); return values; }; function initializeSearchbar(searchBarElement) { const $searchBar = $CSD(searchBarElement); const hasClearButton = $searchBar.hasClass("clearbutton"); // Crea il wrapper della searchbar const $searchBarWrapper = $CSD('<div class="csd-searchbar"></div>'); // Sposta l'input nel wrapper $searchBar.wrap($searchBarWrapper); // Aggiungi search icon const $searchIcon = $CSD( '<ion-icon class="csd-searchbar-icon" name="search-sharp"></ion-icon>' ); $searchBar.after($searchIcon); // Aggiungi clear button solo se richiesto if (hasClearButton) { const $clearButton = $CSD( '<div class="csd-clearbutton"><ion-icon name="close-sharp"></ion-icon></div>' ); $searchBar.after($clearButton); $clearButton.on("click", function () { $searchBar.val("").trigger("input"); }); } } function initializeCheckbox(checkboxElement) { const $originalCheckbox = $CSD(checkboxElement); const label = $originalCheckbox.attr("data-label") || ""; const id = $originalCheckbox.attr("id") || "checkbox-" + Math.random().toString(36).substr(2, 9); const $wrapper = $CSD('<div class="csd-checkbox-wrapper"></div>'); const $checkbox = $CSD('<div class="csd-checkbox"></div>'); const $input = $CSD( '<input type="checkbox" class="csd-checkbox-input">' ).attr("id", id); const $box = $CSD( '<div class="csd-checkbox-box"><ion-icon name="checkmark-sharp"></ion-icon></div>' ); // Copia le proprietà dall'originale if ($originalCheckbox.elements[0].checked) $input.elements[0].checked = true; if ($originalCheckbox.elements[0].disabled) $input.elements[0].disabled = true; if ($originalCheckbox.attr("name")) $input.attr("name", $originalCheckbox.attr("name")); if ($originalCheckbox.attr("value")) $input.attr("value", $originalCheckbox.attr("value")); // Copia le classi, escludendo csd-checkbox const originalClass = $originalCheckbox.attr("class"); if (originalClass) { const newClass = originalClass .split(" ") .filter((cls) => cls !== "csd-checkbox") .join(" "); if (newClass) $input.addClass(newClass); } // Copia altri attributi const originalElement = $originalCheckbox.elements[0]; for (let i = 0; i < originalElement.attributes.length; i++) { const attr = originalElement.attributes[i]; if (!["type", "class", "id"].includes(attr.name)) { $input.attr(attr.name, attr.value); } } $checkbox.append($input); $checkbox.append($box); $wrapper.append($checkbox); if (label) { const $label = $CSD('<label class="csd-checkbox-label"></label>') .text(label) .attr("for", id); $wrapper.append($label); } $originalCheckbox.replaceWith($wrapper); } function initializeRadio(radioElement) { const $originalRadio = $CSD(radioElement); const label = $originalRadio.attr("data-label") || ""; const id = $originalRadio.attr("id") || "radio-" + Math.random().toString(36).substr(2, 9); const $wrapper = $CSD('<div class="csd-radio-wrapper"></div>'); const $radio = $CSD('<div class="csd-radio"></div>'); const $input = $CSD('<input type="radio" class="csd-radio-input">').attr( "id", id ); const $box = $CSD( '<div class="csd-radio-box"><div class="csd-radio-dot"></div></div>' ); // Copia le proprietà dall'originale if ($originalRadio.elements[0].checked) $input.elements[0].checked = true; if ($originalRadio.elements[0].disabled) $input.elements[0].disabled = true; if ($originalRadio.attr("name")) $input.attr("name", $originalRadio.attr("name")); if ($originalRadio.attr("value")) $input.attr("value", $originalRadio.attr("value")); // Copia le classi, escludendo csd-radio const originalClass = $originalRadio.attr("class"); if (originalClass) { const newClass = originalClass .split(" ") .filter((cls) => cls !== "csd-radio") .join(" "); if (newClass) $input.addClass(newClass); } // Copia altri attributi const originalElement = $originalRadio.elements[0]; for (let i = 0; i < originalElement.attributes.length; i++) { const attr = originalElement.attributes[i]; if (!["type", "class", "id"].includes(attr.name)) { $input.attr(attr.name, attr.value); } } $radio.append($input); $radio.append($box); $wrapper.append($radio); if (label) { const $label = $CSD('<label class="csd-radio-label"></label>') .text(label) .attr("for", id); $wrapper.append($label); } $originalRadio.replaceWith($wrapper); } function initializeRange(rangeElement) { const $originalRange = $CSD(rangeElement); const min = parseFloat($originalRange.attr("min") || 0); const max = parseFloat($originalRange.attr("max") || 100); const step = parseFloat($originalRange.attr("step") || 1); const isDouble = $originalRange.attr("range") === "true"; const showInput = $originalRange.attr("show-input") === "true"; let values = isDouble ? $originalRange .val() .split(",") .map((v) => parseFloat(v)) : [parseFloat($originalRange.val() || 50)]; // Create wrapper const wrapper = document.createElement("div"); wrapper.className = "csd-range-wrapper"; // Add icons if specified const iconDown = rangeElement.getAttribute("icon-down"); const iconUp = rangeElement.getAttribute("icon-up"); if (iconDown && iconUp && !showInput) { const downIcon = document.createElement("ion-icon"); downIcon.className = "range-icon down"; downIcon.setAttribute("name", iconDown); wrapper.appendChild(downIcon); } const rangeContainer = document.createElement("div"); rangeContainer.className = "csd-range-container"; const track = document.createElement("div"); track.className = "csd-range-track"; const fill = document.createElement("div"); fill.className = "csd-range-fill"; const handles = isDouble ? [document.createElement("div"), document.createElement("div")] : [document.createElement("div")]; handles.forEach((handle, i) => { handle.className = "csd-range-handle"; handle.setAttribute("data-handle", i); }); // Build structure rangeContainer.appendChild(track); rangeContainer.appendChild(fill); handles.forEach((handle) => rangeContainer.appendChild(handle)); wrapper.appendChild(rangeContainer); // Add input if needed let inputs = []; if (showInput) { if (isDouble) { const inputGroup = document.createElement("div"); inputGroup.className = "csd-range-input-group"; const input1 = document.createElement("input"); input1.type = "number"; input1.className = "csd-input csd-range-input"; input1.setAttribute("data-handle", "0"); input1.min = min; input1.max = max; input1.step = step; const input2 = document.createElement("input"); input2.type = "number"; input2.className = "csd-input csd-range-input"; input2.setAttribute("data-handle", "1"); input2.min = min; input2.max = max; input2.step = step; const separator = document.createElement("span"); separator.className = "csd-range-input-separator"; separator.textContent = "-"; inputGroup.appendChild(input1); inputGroup.appendChild(separator); inputGroup.appendChild(input2); wrapper.appendChild(inputGroup); inputs = [input1, input2]; } else { const input = document.createElement("input"); input.type = "number"; input.className = "csd-input csd-range-input"; input.setAttribute("data-handle", "0"); input.min = min; input.max = max; input.step = step; wrapper.appendChild(input); inputs = [input]; } } // Move original range into wrapper and hide it const originalElement = $originalRange.elements[0]; originalElement.style.display = "none"; originalElement.parentNode.replaceChild(wrapper, originalElement); wrapper.appendChild(originalElement); if (iconDown && iconUp && !showInput) { const upIcon = document.createElement("i"); upIcon.className = "range-icon up fa-solid fa-angle-up"; wrapper.appendChild(upIcon); } // Get CSD wrappers for DOM elements const $wrapper = $CSD(wrapper); const $rangeContainer = $CSD(rangeContainer); const $track = $CSD(track); const $fill = $CSD(fill); const $handles = handles.map((h) => $CSD(h)); const $inputs = inputs.map((i) => $CSD(i)); // Update visual state function updateUI(newValues = values, changeVal = true) { values = newValues; const range = max - min; if (isDouble) { const leftPercent = ((values[0] - min) / range) * 100; const rightPercent = ((values[1] - min) / range) * 100; $fill.css("left", leftPercent + "%"); $fill.css("width", rightPercent - leftPercent + "%"); $handles[0].css("left", leftPercent + "%"); $handles[1].css("left", rightPercent + "%"); if (showInput) { $inputs[0].val(values[0]); $inputs[1].val(values[1]); } } else { const percent = ((values[0] - min) / range) * 100; $fill.css("width", percent + "%"); $handles[0].css("left", percent + "%"); if (showInput) { $inputs[0].val(values[0]); } } // Trigger change event if (changeVal) $originalRange .val(isDouble ? values.join(",") : values[0]) .trigger("change"); } // Handle drag functionality let isDragging = false; let activeHandle = null; const $document = $CSD(document); function getValueFromPosition(clientX) { const rect = rangeContainer.getBoundingClientRect(); const position = (clientX - rect.left) / rect.width; let value = min + (max - min) * position; value = Math.round(value / step) * step; return Math.max(min, Math.min(max, value)); } function moveHandler(e) { if (!isDragging) return; const newValue = getValueFromPosition(e.clientX); let newValues = [...values]; if (isDouble) { if (activeHandle === 0) { if (newValue <= values[1]) newValues[0] = newValue; } else { if (newValue >= values[0]) newValues[1] = newValue; } } else { newValues[0] = newValue; } updateUI(newValues); e.preventDefault(); } function upHandler() { if (!isDragging) return; isDragging = false; activeHandle = null; $document.off("mousemove", moveHandler); $document.off("mouseup", upHandler); } // Track click handler track.addEventListener("mousedown", function (e) { const newValue = getValueFromPosition(e.clientX); if (isDouble) { const handle0Distance = Math.abs(newValue - values[0]); const handle1Distance = Math.abs(newValue - values[1]); activeHandle = handle0Distance < handle1Distance ? 0 : 1; } else { activeHandle = 0; } isDragging = true; updateUI( isDouble ? activeHandle === 0 ? [newValue, values[1]] : [values[0], newValue] : [newValue] ); $document.on("mousemove", moveHandler); $document.on("mouseup", upHandler); e.preventDefault(); }); // Handle drag events handles.forEach((handle, index) => { handle.addEventListener("mousedown", function (e) { isDragging = true; activeHandle = index; $document.on("mousemove", moveHandler); $document.on("mouseup", upHandler); e.stopPropagation(); e.preventDefault(); }); }); // Handle input changes if (showInput) { inputs.forEach((input, index) => { input.addEventListener("input", function () { const newValue = parseFloat(this.value); if (isNaN(newValue)) return; let newValues = [...values]; if (isDouble) { if (index === 0 && newValue <= values[1]) { newValues[0] = newValue; } else if (index === 1 && newValue >= values[0]) { newValues[1] = newValue; } } else { newValues[0] = Math.max(min, Math.min(max, newValue)); } updateUI(newValues); }); }); } // Override val method for range input const rangeData = { element: originalElement, isDouble, min, max, step, values, updateUI: (values, changeVal) => updateUI(values, changeVal), }; originalElement._rangeData = rangeData; // Extend CSD prototype for range inputs if (!_CSD_UTILS.prototype._originalVal) { _CSD_UTILS.prototype._originalVal = _CSD_UTILS.prototype.val; _CSD_UTILS.prototype.val = function (value) { const el = this.elements[0]; if (el && el._rangeData) { if (arguments.length === 0) { return this._originalVal(); } const result = this._originalVal(value); const { isDouble, min, max, step, values: currentValues, updateUI, } = el._rangeData; function roundToStep(value) { return Math.round(value / step) * step; } // Update UI when value is set if (isDouble) { const parts = value.toString().split(","); let newValues = [...currentValues]; if (parts.length === 1) { // Se viene fornito un solo valore, aggiorna solo il primo handle const val = parseFloat(parts[0]); if (!isNaN(val)) { const roundedVal = roundToStep(Math.max(min, Math.min(max, val))); if (roundedVal <= newValues[1]) { newValues[0] = roundedVal; } } } else { // Se vengono forniti entrambi i valori, aggiornali const [val1, val2] = parts.map((v) => { const parsed = parseFloat(v); return isNaN(parsed) ? min : roundToStep(Math.max(min, Math.min(max, parsed))); }); if (val1 <= val2) { newValues = [val1, val2]; } } updateUI(newValues, false); } else { const val = parseFloat(value); const newVal = isNaN(val) ? min : roundToStep(Math.max(min, Math.min(max, val))); updateUI([newVal], false); } return result; } return this._originalVal.apply(this, arguments); }; } // Initial UI update updateUI(); } function initializePassword(inputElement) { const $input = $CSD(inputElement); const useInputGroup = $input.attr("input-group") === "true"; if (useInputGroup) { // Wrap input in input group if not already wrapped let $group = $input.closest(".csd-input-group"); if (!$group.elements.length) { $input.wrap('<div class="csd-input-group"></div>'); $group = $input.closest(".csd-input-group"); } // Add the toggle password icon as input group icon const $icon = $CSD(` <span class="csd-input-group-icon"> <ion-icon name="eye-outline"></ion-icon> </span> `); $group.append($icon); // Toggle password visibility $icon.on("click", function (e) { e.preventDefault(); const type = $input.attr("type") === "password" ? "text" : "password"; $input.attr("type", type); // Toggle icon const iconName = type === "password" ? "eye-outline" : "eye-off-outline"; $icon.find("ion-icon").attr("name", iconName); }); } else { // Use the simple password style with absolute positioning $input.wrap('<div class="csd-password"></div>'); // Add the toggle password icon const $icon = $CSD(` <ion-icon class="csd-password-toggle" name="eye-outline"></ion-icon> `); $input.after($icon); // Toggle password visibility $icon.on("click", function (e) { e.preventDefault(); const type = $input.attr("type") === "password" ? "text" : "password"; $input.attr("type", type); // Toggle icon const iconName = type === "password" ? "eye-outline" : "eye-off-outline"; $icon.attr("name", iconName); }); } } function initializeMask(inputElement) { const $input = $CSD(inputElement); const mask = $input.data("mask"); // Se è un input OTP, inizializza il campo OTP if ($input.hasClass("csd-otp")) { initializeOTP($input, mask); return; } $input.on("input", function (e) { const value = e.target.value; // Valore immesso let maskedValue = ""; // Valore con maschera applicata let valueIndex = 0; // Indice del carattere immesso let optional = false; // Flag per gestire il facoltativo // Itera attraverso la maschera for (let i = 0; i < mask.length; i++) { const maskChar = mask[i]; // Carattere corrente della maschera const inputChar = value[valueIndex]; // Carattere immesso if (maskChar === "?") { optional = true; // Tutti i caratteri dopo questo sono facoltativi continue; } if (!inputChar && optional) { break; // Se l'input è terminato e siamo in una parte facoltativa, esci } if (!inputChar) { break; // Interrompi se non ci sono più caratteri da verificare } if (/\d/.test(maskChar)) { // Se il carattere della maschera è un numero (es. 123456) if (/\d/.test(inputChar)) { maskedValue += inputChar; // Accetta solo numeri valueIndex++; } else { // Ignora i caratteri non validi valueIndex++; i--; // Rimani sulla stessa posizione nella maschera } } else if (/[A-Za-z]/.test(maskChar)) { // Se il carattere della maschera è una lettera (es. abc) if (/[A-Za-z]/.test(inputChar)) { maskedValue += inputChar; // Accetta solo lettere valueIndex++; } else { // Ignora i caratteri non validi valueIndex++; i--; // Rimani sulla stessa posizione nella maschera } } else if (maskChar === "*") { // Accetta lettere o numeri if (/[A-Za-z0-9]/.test(inputChar)) { maskedValue += inputChar; valueIndex++; } else { valueIndex++; i--; // Rimani sulla stessa posizione nella maschera } } else if (maskChar === "#") { // Accetta qualsiasi carattere maskedValue += inputChar;