csdsolutions-csdjs
Version:
Libreria per i progetti di CSD Solutions
1,753 lines (1,540 loc) • 80 kB
JavaScript
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;