xref: /plugin/kanban/script.js (revision f4b18c92ed322afef4847ac5c154503a8bb2d449)
1*f4b18c92SGill B.jQuery(function() {
2*f4b18c92SGill B.    var $board = jQuery('.kanban-board');
3*f4b18c92SGill B.	var $mycard = jQuery('.kanban-card');
4*f4b18c92SGill B.    if (!$board.length) return;
5*f4b18c92SGill B.    var boardName = $board.data('board');
6*f4b18c92SGill B.
7*f4b18c92SGill B.    // Helper to send data to PHP
8*f4b18c92SGill B.    function saveCardData($card) {
9*f4b18c92SGill B.        jQuery.post(DOKU_BASE + 'lib/exe/ajax.php', {
10*f4b18c92SGill B.            call: 'kanban_save', // Match this with your action.php check
11*f4b18c92SGill B.            board: boardName,
12*f4b18c92SGill B.            card_id: $card.data('id'),
13*f4b18c92SGill B.            column: $card.closest('.kanban-col').data('id'),
14*f4b18c92SGill B.            name: $card.find('.card-title').text(),
15*f4b18c92SGill B.            importance: $card.attr('class').split(' ').filter(c => ['high','medium','low'].includes(c))[0] || 'medium',
16*f4b18c92SGill B.            desc: $card.find('.card-desc').text(),
17*f4b18c92SGill B.			note: $card.find('.card-note').text(),
18*f4b18c92SGill B.			checked: $card.find('.checkbox-inline').prop('checked')
19*f4b18c92SGill B.        }).done(function(res){ console.log("Saved:", res); });
20*f4b18c92SGill B.    }
21*f4b18c92SGill B.
22*f4b18c92SGill B.	// Prevent movement of the card column placeholder
23*f4b18c92SGill B.	/*
24*f4b18c92SGill B.	jQuery(function() {
25*f4b18c92SGill B.    jQuery(".cards-container").sortable({
26*f4b18c92SGill B.        // Only allow children WITHOUT the 'fixed-top-item' class to be moved
27*f4b18c92SGill B.        items: "> div:not(.fixed-top)",
28*f4b18c92SGill B.
29*f4b18c92SGill B.        // Listen for when an item is being moved to a new position
30*f4b18c92SGill B.        update: function(event, ui) {
31*f4b18c92SGill B.            // ui.item.index() returns the current position in the DOM
32*f4b18c92SGill B.            if (ui.item.index() === 0) {
33*f4b18c92SGill B.                // Find the fixed element
34*f4b18c92SGill B.                var $fixed = jQuery(this).find(".fixed-top");
35*f4b18c92SGill B.                // Immediately force the dropped item to go AFTER the fixed div
36*f4b18c92SGill B.                ui.item.insertAfter($fixed);
37*f4b18c92SGill B.            }
38*f4b18c92SGill B.        }
39*f4b18c92SGill B.		});
40*f4b18c92SGill B.	});
41*f4b18c92SGill B.	*/
42*f4b18c92SGill B.
43*f4b18c92SGill B.    // Initialize Drag and Drop ONCE
44*f4b18c92SGill B.
45*f4b18c92SGill B.    jQuery(".cards-container").sortable({
46*f4b18c92SGill B.        connectWith: ".cards-container",
47*f4b18c92SGill B.        placeholder: "ui-sortable-placeholder",
48*f4b18c92SGill B.        tolerance: "pointer",
49*f4b18c92SGill B.
50*f4b18c92SGill B.
51*f4b18c92SGill B.        start: function(e, ui) {
52*f4b18c92SGill B.		//start-added
53*f4b18c92SGill B.		const card = document.querySelector('.kanban-card');
54*f4b18c92SGill B.
55*f4b18c92SGill B.		distance: 15, // Dragging won't trigger until moved 15px
56*f4b18c92SGill B.        // Add and start the draggable class only AFTER the 15px threshold is met
57*f4b18c92SGill B.		ui.item.addClass("is-dragging");
58*f4b18c92SGill B.		//distance: -15, // Dragging won't trigger until moved -15px
59*f4b18c92SGill B.        // Add and start the draggable class only AFTER the -15px threshold is met
60*f4b18c92SGill B.		//ui.item.addClass("is-left-dragging");
61*f4b18c92SGill B.		//end-added
62*f4b18c92SGill B.            ui.placeholder.height(ui.item.outerHeight());
63*f4b18c92SGill B.        },
64*f4b18c92SGill B.        stop: function(event, ui) {
65*f4b18c92SGill B.			//Remove the draggable class when you are done
66*f4b18c92SGill B.			ui.item.removeClass("is-dragging");
67*f4b18c92SGill B.			//ui.item.removeClass("is-left-dragging");
68*f4b18c92SGill B.            //Fires once when dropped
69*f4b18c92SGill B.            //alert(ui.item.text()); //debugging function works
70*f4b18c92SGill B.			//alert(ui.item.data('id')); //debugging function
71*f4b18c92SGill B.			//saveCardData(ui.item);
72*f4b18c92SGill B.			//Modified to capture the text value in JQuery format
73*f4b18c92SGill B.			//saveCardData(ui.item);
74*f4b18c92SGill B.			saveCardData(ui.item); //replaced to bring about real name for saving
75*f4b18c92SGill B.			//const divId = ui.item.closest('.kanban-col').data('id');
76*f4b18c92SGill B.			//ui.item.closest('.kanban-col').preventDefault(); // This is required to allow a drop
77*f4b18c92SGill B.			//console.log("Dropped on ID:", divId);
78*f4b18c92SGill B.        }
79*f4b18c92SGill B.    }).disableSelection();
80*f4b18c92SGill B.
81*f4b18c92SGill B.    // Fix: Event Delegation for "Add Card"
82*f4b18c92SGill B.    $board.on('click', '.add-card-btn', function() {
83*f4b18c92SGill B.        var name = prompt("Job Name:");
84*f4b18c92SGill B.        if (!name) return;
85*f4b18c92SGill B.        var imp = (prompt("Importance (high, medium, low):", "medium") || "medium").toLowerCase();
86*f4b18c92SGill B.        var cardId = "c" + Date.now();
87*f4b18c92SGill B.		var note = "There are no notes on this card yet.";
88*f4b18c92SGill B.
89*f4b18c92SGill B.        var $newCard = jQuery(`
90*f4b18c92SGill B.            <div class="kanban-card ${imp}" data-id="${cardId}">
91*f4b18c92SGill B.				<input type="checkbox">
92*f4b18c92SGill B.                <strong class="card-title">${name}</strong>
93*f4b18c92SGill B.                <div class="card-desc">Click to add description...</div>
94*f4b18c92SGill B.				<div style="color:black;">Refresh for Notes</div>
95*f4b18c92SGill B.				<div class="card-note"></div>
96*f4b18c92SGill B.            </div>
97*f4b18c92SGill B.        `);
98*f4b18c92SGill B.
99*f4b18c92SGill B.        jQuery(this).closest('.kanban-col').find('.cards-container').append($newCard);
100*f4b18c92SGill B.        saveCardData($newCard);
101*f4b18c92SGill B.    });
102*f4b18c92SGill B.
103*f4b18c92SGill B.	//make the kanban cards add note button clickable
104*f4b18c92SGill B.	$mycard.on('click', '.btn-notes', function() {
105*f4b18c92SGill B.		//var $note = jQuery(this);
106*f4b18c92SGill B.		//var currentNote = $note.text();
107*f4b18c92SGill B.		//alert('this is my click');
108*f4b18c92SGill B.		var mynote = prompt("Add Note:","");
109*f4b18c92SGill B.		//Get the current username
110*f4b18c92SGill B.		//var userlogin = JSINFO['user']; // Login username
111*f4b18c92SGill B.		// Standard DokuWiki configuration object
112*f4b18c92SGill B.		//attempt to query the user from the interface first
113*f4b18c92SGill B.		var userlogin = jQuery('meta[name="plugin_do_user"]').attr('content');
114*f4b18c92SGill B.		//console.log(JSINFO); //debugging line
115*f4b18c92SGill B.		if (!userlogin && typeof JSINFO !== 'undefined') {
116*f4b18c92SGill B.			//pull the user from php if the interface query fails
117*f4b18c92SGill B.			userlogin = JSINFO['plugin_do_user'];
118*f4b18c92SGill B.		}
119*f4b18c92SGill B.		//return currentNote + " | " + note;
120*f4b18c92SGill B.		if(mynote !== null){
121*f4b18c92SGill B.			//Sanitize the input
122*f4b18c92SGill B.			function sanitizeInlineCommands(inputText) {
123*f4b18c92SGill B.				// Regex to match "javascript:" and other common inline command patterns
124*f4b18c92SGill B.					const commandPattern = /javascript:|on\w+=|style="[^"]*expression[^"]*"|\+/gi;
125*f4b18c92SGill B.
126*f4b18c92SGill B.					// Replace found commands with an empty string
127*f4b18c92SGill B.					return inputText.replace(commandPattern, '');
128*f4b18c92SGill B.			}
129*f4b18c92SGill B.
130*f4b18c92SGill B.			// Example usage
131*f4b18c92SGill B.			//const userInput = 'Hello <a href="javascript:alert(1)">Click</a>! <img src=x onerror=alert(1)>';
132*f4b18c92SGill B.			const cleanedNote = sanitizeInlineCommands(mynote);
133*f4b18c92SGill B.			//end sanitize
134*f4b18c92SGill B.			//Get the current date string
135*f4b18c92SGill B.			const date = new Date().toLocaleDateString('en-US');
136*f4b18c92SGill B.			//Get the current time string
137*f4b18c92SGill B.			const mytime = new Date().toLocaleTimeString();
138*f4b18c92SGill B.			//Get the closest kanban-card to the object clicked
139*f4b18c92SGill B.			var $note = jQuery(this).closest('.kanban-card');
140*f4b18c92SGill B.			// filter() checks if $note IS the class; find() checks if it's INSIDE $note
141*f4b18c92SGill B.			var $target = $note.filter('.card-note').add($note.find('.card-note'));
142*f4b18c92SGill B.			// 2. Attach new data to the inner div with <br> delimiter
143*f4b18c92SGill B.			$target.append("<br>+ " + userlogin + " - " + date + ":" + mytime + " - " + cleanedNote);
144*f4b18c92SGill B.
145*f4b18c92SGill B.			saveCardData($note.closest('.kanban-card'));
146*f4b18c92SGill B.		}
147*f4b18c92SGill B.	});
148*f4b18c92SGill B.
149*f4b18c92SGill B.    // Fix: Event Delegation for Clickable Description
150*f4b18c92SGill B.    $board.on('click', '.card-desc', function() {
151*f4b18c92SGill B.        var $desc = jQuery(this);
152*f4b18c92SGill B.        var currentText = $desc.text() === "Click to add description..." ? "" : $desc.text();
153*f4b18c92SGill B.        var newDesc = prompt("Enter Description:", currentText);
154*f4b18c92SGill B.        if (newDesc !== null) {
155*f4b18c92SGill B.            $desc.text(newDesc || "Click to add description...");
156*f4b18c92SGill B.            saveCardData($desc.closest('.kanban-card'));
157*f4b18c92SGill B.        }
158*f4b18c92SGill B.    });
159*f4b18c92SGill B.    // Fix: Event Delegation for Clickable Checkbox
160*f4b18c92SGill B.    $board.on('click', '.checkbox-inline', function() {
161*f4b18c92SGill B.        var $checkit = jQuery(this);
162*f4b18c92SGill B.        var currentVal = $checkit.prop('checked');
163*f4b18c92SGill B.        if ($checkit){
164*f4b18c92SGill B.            saveCardData($checkit.closest('.kanban-card'));
165*f4b18c92SGill B.        }
166*f4b18c92SGill B.    });
167*f4b18c92SGill B.
168*f4b18c92SGill B.
169*f4b18c92SGill B.});
170