| @@ -1,0 +1,725 @@ | ||
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * @package VikBooking | |
| 4 | + * @subpackage com_vikbooking | |
| 5 | + * @author Alessio Gaggii - e4j - Extensionsforjoomla.com | |
| 6 | + * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved. | |
| 7 | + * @license GNU General Public License version 2 or later; see LICENSE | |
| 8 | + * @link https://vikwp.com | |
| 9 | + */ | |
| 10 | + | |
| 11 | +defined('ABSPATH') or die('No script kiddies please!'); | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Class handler for admin widget "sticky notes". This widget has settings. | |
| 15 | + * | |
| 16 | + * @since 1.14.0 (J) - 1.4.0 (WP) | |
| 17 | + */ | |
| 18 | +class VikBookingAdminWidgetStickyNotes extends VikBookingAdminWidget | |
| 19 | +{ | |
| 20 | + /** | |
| 21 | + * The instance counter of this widget. Since we do not load individual parameters | |
| 22 | + * for each widget's instance, we use a static counter to determine its settings. | |
| 23 | + * | |
| 24 | + * @var int | |
| 25 | + */ | |
| 26 | + protected static $instance_counter = -1; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * We detect the Operating System through the browser useer agent | |
| 30 | + * to display different information about the shortcuts to use. | |
| 31 | + * | |
| 32 | + * @var string | |
| 33 | + */ | |
| 34 | + protected $isMac = false; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * Class constructor will define the widget name and identifier. | |
| 38 | + */ | |
| 39 | + public function __construct() | |
| 40 | + { | |
| 41 | + // call parent constructor | |
| 42 | + parent::__construct(); | |
| 43 | + | |
| 44 | + // declare name, description and identifier | |
| 45 | + $this->widgetName = JText::translate('VBO_W_STICKYN_TITLE'); | |
| 46 | + $this->widgetDescr = JText::translate('VBO_W_STICKYN_DESCR'); | |
| 47 | + $this->widgetId = basename(__FILE__, '.php'); | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * Define widget and icon and style name. | |
| 51 | + * | |
| 52 | + * @since 1.15.0 (J) - 1.5.0 (WP) | |
| 53 | + */ | |
| 54 | + $this->widgetIcon = '<i class="' . VikBookingIcons::i('thumbtack') . '"></i>'; | |
| 55 | + $this->widgetStyleName = 'yellow'; | |
| 56 | + | |
| 57 | + // load widget's settings | |
| 58 | + $this->widgetSettings = $this->loadSettings(); | |
| 59 | + | |
| 60 | + // determine if the operating system is MacOS or iOS | |
| 61 | + $ua = JFactory::getApplication()->input->server->getString('HTTP_USER_AGENT', ''); | |
| 62 | + if (stripos($ua, 'windows') === false && (stripos($ua, 'mac') !== false || stripos($ua, 'iphone') !== false || stripos($ua, 'ipad') !== false)) { | |
| 63 | + $this->isMac = true; | |
| 64 | + } | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * Custom method for this widget only to update one sticky note. | |
| 69 | + * The method is called by the admin controller through an AJAX request. | |
| 70 | + * The visibility should be public, it should not exit the process, and | |
| 71 | + * any content sent to output will be returned to the AJAX response. | |
| 72 | + * | |
| 73 | + * @param int $force_note_index the new index can be forced to insert a new note. | |
| 74 | + * @param int $force_instance the widget's instance to force. | |
| 75 | + * @param string $force_txt the text of the sticky note to force. | |
| 76 | + * | |
| 77 | + * @return void it may output a log with the new widget instance assigned. | |
| 78 | + */ | |
| 79 | + public function updateStickyNote($force_note_index = null, $force_instance = null, $force_txt = null) | |
| 80 | + { | |
| 81 | + $note_instance = VikRequest::getInt('note_instance', -1, 'request'); | |
| 82 | + $note_txt = VikRequest::getString('note_txt', '', 'request', VIKREQUEST_ALLOWRAW); | |
| 83 | + $note_index = VikRequest::getInt('note_index', 0, 'request'); | |
| 84 | + | |
| 85 | + if ($force_note_index !== null && $force_note_index >= 0) { | |
| 86 | + // use the forced index instead | |
| 87 | + $note_index = $force_note_index; | |
| 88 | + } | |
| 89 | + | |
| 90 | + if ($force_instance !== null && $force_instance >= 0) { | |
| 91 | + // use the forced instance instead | |
| 92 | + $note_instance = $force_instance; | |
| 93 | + } | |
| 94 | + | |
| 95 | + if (is_string($force_txt) && strlen($force_txt)) { | |
| 96 | + // use the forced text instead | |
| 97 | + $note_txt = $force_txt; | |
| 98 | + } | |
| 99 | + | |
| 100 | + // make sure the settings of the widget are an array | |
| 101 | + if (!is_array($this->widgetSettings)) { | |
| 102 | + $this->widgetSettings = array(); | |
| 103 | + } | |
| 104 | + | |
| 105 | + // create the new sticky note object | |
| 106 | + $username = $this->getLoggedUserName(); | |
| 107 | + $sticky_note = new stdClass; | |
| 108 | + $sticky_note->html = $note_txt; | |
| 109 | + $sticky_note->ts = time(); | |
| 110 | + if (!empty($username)) { | |
| 111 | + $sticky_note->user = $username; | |
| 112 | + } | |
| 113 | + | |
| 114 | + // eventually append debugging result | |
| 115 | + $result_log = ''; | |
| 116 | + | |
| 117 | + // eventually append the new widget instance assigned for newly added widgets via AJAX | |
| 118 | + $new_instance_response = ''; | |
| 119 | + | |
| 120 | + if (!count($this->widgetSettings)) { | |
| 121 | + // push the first sticky note as the first instance of this widget | |
| 122 | + $this->widgetSettings = array( | |
| 123 | + array($sticky_note) | |
| 124 | + ); | |
| 125 | + $result_log = 'pushing the first sticky note as the first instance of the widget'; | |
| 126 | + $new_instance_response = '[instance=0]'; | |
| 127 | + } elseif (isset($this->widgetSettings[$note_instance])) { | |
| 128 | + // this instance was saved already | |
| 129 | + if (isset($this->widgetSettings[$note_instance][$note_index])) { | |
| 130 | + // we already have this exact sticky note index, so we replace it | |
| 131 | + $this->widgetSettings[$note_instance][$note_index] = $sticky_note; | |
| 132 | + $result_log = 'replacing requested note index in existing widget instance'; | |
| 133 | + } else { | |
| 134 | + // we push a new sticky note | |
| 135 | + array_push($this->widgetSettings[$note_instance], $sticky_note); | |
| 136 | + $result_log = 'adding a new note to the existing widget instance'; | |
| 137 | + } | |
| 138 | + } else { | |
| 139 | + // this is a new instance of the widget, which has settings already - push the new instance and the new sticky note | |
| 140 | + array_push($this->widgetSettings, array($sticky_note)); | |
| 141 | + $result_log = 'pushing a new instance and note to the settings'; | |
| 142 | + $new_instance_response = '[instance=' . (count($this->widgetSettings) - 1) . ']'; | |
| 143 | + } | |
| 144 | + | |
| 145 | + // update widget's settings | |
| 146 | + $this->updateSettings(json_encode($this->widgetSettings)); | |
| 147 | + | |
| 148 | + echo 'e4j.ok' . (!empty($result_log) ? '(' . $result_log . ')' : '') . $new_instance_response; | |
| 149 | + } | |
| 150 | + | |
| 151 | + /** | |
| 152 | + * Custom method for this widget only to delete one sticky note. | |
| 153 | + * The method is called by the admin controller through an AJAX request. | |
| 154 | + * The visibility should be public, it should not exit the process, and | |
| 155 | + * any content sent to output will be returned to the AJAX response. | |
| 156 | + */ | |
| 157 | + public function deleteStickyNote() | |
| 158 | + { | |
| 159 | + $note_instance = VikRequest::getInt('note_instance', -1, 'request'); | |
| 160 | + $note_index = VikRequest::getInt('note_index', 0, 'request'); | |
| 161 | + | |
| 162 | + // make sure the settings of the widget are an array | |
| 163 | + if (!is_array($this->widgetSettings)) { | |
| 164 | + echo 'e4j.error.no settings found'; | |
| 165 | + return; | |
| 166 | + } | |
| 167 | + | |
| 168 | + // make sure the instance of the widget exists | |
| 169 | + if (!isset($this->widgetSettings[$note_instance]) || !is_array($this->widgetSettings[$note_instance])) { | |
| 170 | + echo 'e4j.error.instance not found'; | |
| 171 | + return; | |
| 172 | + } | |
| 173 | + | |
| 174 | + // make sure the index of the note exists | |
| 175 | + if (!isset($this->widgetSettings[$note_instance][$note_index])) { | |
| 176 | + echo 'e4j.error.note index not found, maybe it was never updated before'; | |
| 177 | + return; | |
| 178 | + } | |
| 179 | + | |
| 180 | + // splice the array to remove the requested note | |
| 181 | + array_splice($this->widgetSettings[$note_instance], $note_index, 1); | |
| 182 | + | |
| 183 | + // update widget's settings | |
| 184 | + $this->updateSettings(json_encode($this->widgetSettings)); | |
| 185 | + | |
| 186 | + echo 'e4j.ok'; | |
| 187 | + } | |
| 188 | + | |
| 189 | + /** | |
| 190 | + * Custom method for this widget only to sort one sticky note. | |
| 191 | + * The method is called by the admin controller through an AJAX request. | |
| 192 | + * The visibility should be public, it should not exit the process, and | |
| 193 | + * any content sent to output will be returned to the AJAX response. | |
| 194 | + */ | |
| 195 | + public function sortStickyNote() | |
| 196 | + { | |
| 197 | + $note_instance = VikRequest::getInt('note_instance', -1, 'request'); | |
| 198 | + $note_index_new = VikRequest::getInt('note_index_new', 0, 'request'); | |
| 199 | + $note_index_old = VikRequest::getInt('note_index_old', 0, 'request'); | |
| 200 | + | |
| 201 | + if ($note_index_new == $note_index_old) { | |
| 202 | + // nothing to do, as notes can be sorted only within the same instance | |
| 203 | + echo 'e4j.error.same position given for sticky note'; | |
| 204 | + return; | |
| 205 | + } | |
| 206 | + | |
| 207 | + // make sure the settings of the widget are an array | |
| 208 | + if (!is_array($this->widgetSettings)) { | |
| 209 | + echo 'e4j.error.no settings found'; | |
| 210 | + return; | |
| 211 | + } | |
| 212 | + | |
| 213 | + // make sure the instance of the widget exists | |
| 214 | + if (!isset($this->widgetSettings[$note_instance]) || !is_array($this->widgetSettings[$note_instance])) { | |
| 215 | + echo 'e4j.error.instance not found'; | |
| 216 | + return; | |
| 217 | + } | |
| 218 | + | |
| 219 | + // make sure the old index of the note exists | |
| 220 | + if (!isset($this->widgetSettings[$note_instance][$note_index_old])) { | |
| 221 | + // check if this note was moved before getting saved, so as soon as it was added | |
| 222 | + if (count($this->widgetSettings[$note_instance]) == $note_index_old) { | |
| 223 | + // append the newly created (empty) note before moving it | |
| 224 | + $this->updateStickyNote($note_index_old, $note_instance); | |
| 225 | + // reload the widget's settings after they have been updated with the new note | |
| 226 | + $this->widgetSettings = $this->loadSettings(); | |
| 227 | + // proceed below with moving the sticky note | |
| 228 | + } else { | |
| 229 | + // unable to proceed | |
| 230 | + echo 'e4j.error.original note index not found, maybe it was never updated before'; | |
| 231 | + return; | |
| 232 | + } | |
| 233 | + } | |
| 234 | + | |
| 235 | + // make sure the new index can fit | |
| 236 | + if ($note_index_new > (count($this->widgetSettings[$note_instance]) - 1)) { | |
| 237 | + echo 'e4j.error.new index exceeds the highest position available'; | |
| 238 | + return; | |
| 239 | + } | |
| 240 | + | |
| 241 | + // move the sticky note from the old index to the new index | |
| 242 | + $extracted = array_splice($this->widgetSettings[$note_instance], $note_index_old, 1); | |
| 243 | + array_splice($this->widgetSettings[$note_instance], $note_index_new, 0, $extracted); | |
| 244 | + | |
| 245 | + // update widget's settings | |
| 246 | + $this->updateSettings(json_encode($this->widgetSettings)); | |
| 247 | + | |
| 248 | + echo 'e4j.ok'; | |
| 249 | + } | |
| 250 | + | |
| 251 | + /** | |
| 252 | + * Preload the necessary assets. | |
| 253 | + * | |
| 254 | + * @return void | |
| 255 | + */ | |
| 256 | + public function preload() | |
| 257 | + { | |
| 258 | + // JS lang def | |
| 259 | + JText::script('VBO_STICKYN_TITLE'); | |
| 260 | + JText::script('VBO_STICKYN_TEXT'); | |
| 261 | + JText::script('VBO_STICKYN_TEXT2'); | |
| 262 | + JText::script('VBO_STICKYN_CUSTOMURI'); | |
| 263 | + JText::script('VBO_WIDGETS_CONFRMELEM'); | |
| 264 | + } | |
| 265 | + | |
| 266 | + public function render(?VBOMultitaskData $data = null) | |
| 267 | + { | |
| 268 | + // increase widget's instance counter | |
| 269 | + static::$instance_counter++; | |
| 270 | + | |
| 271 | + // check whether the widget is being rendered via AJAX when adding it through the customizer | |
| 272 | + $is_ajax = $this->isAjaxRendering(); | |
| 273 | + | |
| 274 | + // check whether we are in the multitask panel | |
| 275 | + $is_multitask = $this->isMultitaskRendering(); | |
| 276 | + | |
| 277 | + // check whether the widget requires settings | |
| 278 | + $needs_settings = !$is_ajax; | |
| 279 | + $data_instance = !$is_ajax ? static::$instance_counter : '-1'; | |
| 280 | + if ($is_multitask && $is_ajax) { | |
| 281 | + $guess_inst_counter = $this->guessMultitaskStickyInstance($data); | |
| 282 | + if ($guess_inst_counter !== false) { | |
| 283 | + // force the loading of the guessed widget instance's settings | |
| 284 | + static::$instance_counter = $guess_inst_counter; | |
| 285 | + $data_instance = $guess_inst_counter; | |
| 286 | + $needs_settings = true; | |
| 287 | + } | |
| 288 | + } | |
| 289 | + | |
| 290 | + // generate a unique ID for the sticky notes wrapper instance | |
| 291 | + $wrapper_instance = !$is_ajax ? static::$instance_counter : rand(); | |
| 292 | + $wrapper_id = 'vbo-widget-sticky-' . $wrapper_instance; | |
| 293 | + | |
| 294 | + // load the settings for this specific instance of the widget | |
| 295 | + $instance_settings = array(); | |
| 296 | + if ($needs_settings && is_array($this->widgetSettings) && isset($this->widgetSettings[static::$instance_counter])) { | |
| 297 | + $instance_settings = is_array($this->widgetSettings[static::$instance_counter]) ? $this->widgetSettings[static::$instance_counter] : array(); | |
| 298 | + } | |
| 299 | + | |
| 300 | + ?> | |
| 301 | + <div class="vbo-admin-widget-wrapper"> | |
| 302 | + <div class="vbo-admin-widget-head"> | |
| 303 | + <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4> | |
| 304 | + <div class="btn-toolbar pull-right vbo-btn-toolbar-hastext"> | |
| 305 | + <span class="vbo-sticky-shortcuts-help"<?php echo count($instance_settings) ? ' style="display: none;"' : ''; ?>> | |
| 306 | + <?php | |
| 307 | + echo $this->vbo_app->createPopover(array( | |
| 308 | + 'title' => JText::translate('VBO_W_STICKYN_HELP_TITLE'), | |
| 309 | + 'content' => JText::translate(($this->isMac ? 'VBO_W_STICKYN_HELP_DESCR_MAC' : 'VBO_W_STICKYN_HELP_DESCR')), | |
| 310 | + 'icon_class' => VikBookingIcons::i('keyboard'), | |
| 311 | + 'placement' => 'left' | |
| 312 | + )); | |
| 313 | + ?> | |
| 314 | + </span> | |
| 315 | + </div> | |
| 316 | + </div> | |
| 317 | + <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-sticky-notes-wrap" data-instance="<?php echo $data_instance; ?>"> | |
| 318 | + <ul class="vbo-admin-widget-sticky-notes-list"> | |
| 319 | + <?php | |
| 320 | + foreach ($instance_settings as $k => $sticky_note) { | |
| 321 | + ?> | |
| 322 | + <li class="vbo-sticky-note"> | |
| 323 | + <div class="vbo-sticky-note-cmds"> | |
| 324 | + <span class="vbo-sticky-note-cmd-drag"><?php VikBookingIcons::e('ellipsis-v'); ?></span> | |
| 325 | + <span class="vbo-sticky-note-cmd-trash" onclick="vboWidgetStickyNoteDelete(this);"><?php VikBookingIcons::e('trash'); ?></span> | |
| 326 | + </div> | |
| 327 | + <div contenteditable="true" spellcheck="false" class="vbo-widget-sticky-canvas"> | |
| 328 | + <?php echo $sticky_note->html; ?> | |
| 329 | + </div> | |
| 330 | + <div class="vbo-sticky-note-sign"> | |
| 331 | + <span class="vbo-sticky-note-sign-dt"><?php echo date(str_replace("/", $this->datesep, $this->df).' H:i', $sticky_note->ts); ?></span> | |
| 332 | + <?php | |
| 333 | + if (!empty($sticky_note->user)) { | |
| 334 | + ?> | |
| 335 | + <span class="vbo-sticky-note-sign-user"><?php echo $sticky_note->user; ?></span> | |
| 336 | + <?php | |
| 337 | + } | |
| 338 | + ?> | |
| 339 | + </div> | |
| 340 | + </li> | |
| 341 | + <?php | |
| 342 | + } | |
| 343 | + ?> | |
| 344 | + <li class="vbo-sticky-note-add"> | |
| 345 | + <div class="vbo-sticky-note-add-inner" onclick="vboWidgetStickyNoteAdd(this);"> | |
| 346 | + <span><?php VikBookingIcons::e('plus-circle'); ?></span> | |
| 347 | + </div> | |
| 348 | + </li> | |
| 349 | + </ul> | |
| 350 | + </div> | |
| 351 | + </div> | |
| 352 | + | |
| 353 | + <script type="text/javascript"> | |
| 354 | + // declare global variables for notes sorting | |
| 355 | + var vbo_stickynote_initial_pos = null; | |
| 356 | + | |
| 357 | + jQuery(function() { | |
| 358 | + | |
| 359 | + // register input event listener for each sticky note | |
| 360 | + var stickies<?php echo $wrapper_instance; ?> = document.querySelectorAll('#<?php echo $wrapper_id; ?> .vbo-widget-sticky-canvas'); | |
| 361 | + for (var i = 0; i < stickies<?php echo $wrapper_instance; ?>.length; i++) { | |
| 362 | + stickies<?php echo $wrapper_instance; ?>[i].addEventListener('input', VBOCore.debounceEvent(vboWidgetStickyNoteUpdateTxt, 750)); | |
| 363 | + } | |
| 364 | + | |
| 365 | + if (typeof jQuery.fn.sortable !== 'undefined') { | |
| 366 | + /** | |
| 367 | + * Make all sticky notes sortable. Do not use .disableSelection() or this | |
| 368 | + * will break all [contenteditable] elements and their focus/selection events. | |
| 369 | + */ | |
| 370 | + jQuery('#<?php echo $wrapper_id; ?> .vbo-admin-widget-sticky-notes-list').sortable({ | |
| 371 | + cursor: 'move', | |
| 372 | + handle: '.vbo-sticky-note-cmd-drag', | |
| 373 | + items: 'li.vbo-sticky-note', | |
| 374 | + revert: false, | |
| 375 | + start: function(event, ui) { | |
| 376 | + // update sticky note initial position | |
| 377 | + vbo_stickynote_initial_pos = jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-sticky-note').index(jQuery(ui.item)); | |
| 378 | + }, | |
| 379 | + update: function(event, ui) { | |
| 380 | + // get sticky note new position | |
| 381 | + var now_note = jQuery(ui.item); | |
| 382 | + var new_note_index = jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-sticky-note').index(now_note); | |
| 383 | + var widget_instance = now_note.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance'); | |
| 384 | + | |
| 385 | + if (vbo_stickynote_initial_pos === null) { | |
| 386 | + return; | |
| 387 | + } | |
| 388 | + | |
| 389 | + // the widget method to call | |
| 390 | + var call_method = 'sortStickyNote'; | |
| 391 | + | |
| 392 | + // make a silent request to remove the sticky note | |
| 393 | + VBOCore.doAjax( | |
| 394 | + "<?php echo $this->getExecWidgetAjaxUri(); ?>", | |
| 395 | + { | |
| 396 | + widget_id: "<?php echo $this->getIdentifier(); ?>", | |
| 397 | + call: call_method, | |
| 398 | + note_index_old: vbo_stickynote_initial_pos, | |
| 399 | + note_index_new: new_note_index, | |
| 400 | + note_instance: widget_instance, | |
| 401 | + tmpl: "component" | |
| 402 | + }, | |
| 403 | + function(response) { | |
| 404 | + // unset global note position var | |
| 405 | + vbo_stickynote_initial_pos = null; | |
| 406 | + try { | |
| 407 | + var obj_res = typeof response === 'string' ? JSON.parse(response) : response; | |
| 408 | + if (!obj_res.hasOwnProperty(call_method)) { | |
| 409 | + console.error('Unexpected JSON response', obj_res); | |
| 410 | + } | |
| 411 | + } catch(err) { | |
| 412 | + console.error('could not parse JSON response', err, response); | |
| 413 | + } | |
| 414 | + }, | |
| 415 | + function(error) { | |
| 416 | + // unset global note position var | |
| 417 | + vbo_stickynote_initial_pos = null; | |
| 418 | + console.error(error); | |
| 419 | + } | |
| 420 | + ); | |
| 421 | + } | |
| 422 | + }); | |
| 423 | + } else { | |
| 424 | + // hide sortable handler | |
| 425 | + jQuery('#<?php echo $wrapper_id; ?> .vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note-cmd-drag').hide(); | |
| 426 | + } | |
| 427 | + | |
| 428 | + }); | |
| 429 | + </script> | |
| 430 | + | |
| 431 | + <?php | |
| 432 | + if (static::$instance_counter === 0 || $is_ajax) { | |
| 433 | + ?> | |
| 434 | + <script type="text/javascript"> | |
| 435 | + jQuery(function() { | |
| 436 | + | |
| 437 | + /** | |
| 438 | + * Add event listener to keydown for shortcuts during typing on contenteditable elements. | |
| 439 | + */ | |
| 440 | + document.onkeydown = function(e) { | |
| 441 | + e = e || window.event; | |
| 442 | + var active_el = document.activeElement; | |
| 443 | + var exec_cmd = null; | |
| 444 | + var exec_val = null; | |
| 445 | + if ((!e.metaKey && !e.ctrlKey) || !active_el || !active_el.hasAttribute('contenteditable')) { | |
| 446 | + return; | |
| 447 | + } | |
| 448 | + | |
| 449 | + if (e.keyCode == 66) { | |
| 450 | + // CMD + B detected | |
| 451 | + exec_cmd = 'bold'; | |
| 452 | + } else if (e.keyCode == 85) { | |
| 453 | + // CMD + U detected | |
| 454 | + exec_cmd = 'underline'; | |
| 455 | + } else if (e.keyCode == 73) { | |
| 456 | + // CMD + I detected | |
| 457 | + exec_cmd = 'italic'; | |
| 458 | + } else if (e.keyCode == 83) { | |
| 459 | + // CMD + S | |
| 460 | + exec_cmd = 'strikeThrough'; | |
| 461 | + } else if (e.keyCode == 72 || e.keyCode == 84) { | |
| 462 | + // CMD + H || CMD + T detected | |
| 463 | + exec_cmd = 'formatBlock'; | |
| 464 | + exec_val = 'h2'; | |
| 465 | + } else if (e.keyCode == 80) { | |
| 466 | + // CMD + P | |
| 467 | + exec_cmd = 'formatBlock'; | |
| 468 | + exec_val = 'p'; | |
| 469 | + } else if (e.keyCode == 79 || e.keyCode == 78) { | |
| 470 | + // CMD + O || CMD + N detected | |
| 471 | + exec_cmd = 'insertOrderedList'; | |
| 472 | + } else if (e.keyCode == 76) { | |
| 473 | + // CMD + L | |
| 474 | + if (window.getSelection()) { | |
| 475 | + var range = window.getSelection().getRangeAt(0); | |
| 476 | + exec_val = range.toString(); | |
| 477 | + if (exec_val && exec_val.length) { | |
| 478 | + // some text is selected | |
| 479 | + if (exec_val.match(/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i)) { | |
| 480 | + // URI selected, set command to create a link | |
| 481 | + exec_cmd = 'createLink'; | |
| 482 | + } else { | |
| 483 | + // just some plain text was selected, ask for a custom URI | |
| 484 | + e.preventDefault(); | |
| 485 | + var link_uri = prompt(Joomla.JText._('VBO_STICKYN_CUSTOMURI'), '<?php echo JUri::root(); ?>'); | |
| 486 | + if (link_uri != null && link_uri != '' && link_uri.indexOf('http') >= 0) { | |
| 487 | + exec_val = '<a href="' + link_uri + '">' + exec_val + '</a>'; | |
| 488 | + exec_cmd = 'insertHTML'; | |
| 489 | + } | |
| 490 | + } | |
| 491 | + } else { | |
| 492 | + // no text selected, create an unordered list | |
| 493 | + exec_val = null; | |
| 494 | + exec_cmd = 'insertUnorderedList'; | |
| 495 | + } | |
| 496 | + } else { | |
| 497 | + // cannot access text selection, create an unordered list also in this case | |
| 498 | + exec_val = null; | |
| 499 | + exec_cmd = 'insertUnorderedList'; | |
| 500 | + } | |
| 501 | + } else if (e.keyCode == 77 && window.getSelection()) { | |
| 502 | + // CMD + M | |
| 503 | + var txtsel = window.getSelection().getRangeAt(0).toString(); | |
| 504 | + if (txtsel && txtsel.length && txtsel.indexOf('<') === 0 && txtsel.substr(-1, 1) == '>') { | |
| 505 | + // HTML tag selected, convert it from plain text to HTML code | |
| 506 | + if (txtsel.indexOf('><') >= 0) { | |
| 507 | + // hack for converting icons, where empty tags are not parsed correctly | |
| 508 | + txtsel = txtsel.replace('><', '> <'); | |
| 509 | + } | |
| 510 | + exec_val = txtsel + ' '; | |
| 511 | + exec_cmd = 'insertHTML'; | |
| 512 | + } | |
| 513 | + } | |
| 514 | + | |
| 515 | + if (exec_cmd !== null) { | |
| 516 | + e.preventDefault(); | |
| 517 | + document.execCommand(exec_cmd, false, exec_val); | |
| 518 | + active_el.focus(); | |
| 519 | + | |
| 520 | + return false; | |
| 521 | + } | |
| 522 | + } | |
| 523 | + | |
| 524 | + /** | |
| 525 | + * Listen to mousedown event for clicks on links inside the elements | |
| 526 | + * with contenteditable, otherwise links in sticky notes won't open. | |
| 527 | + */ | |
| 528 | + jQuery(document.body).on('mousedown', '.vbo-widget-sticky-canvas[contenteditable]', function(e) { | |
| 529 | + var elem = jQuery(e.target); | |
| 530 | + if (elem.is('a')) { | |
| 531 | + var goto = elem.attr('href'); | |
| 532 | + if (goto && goto.length && goto.indexOf('http') >= 0) { | |
| 533 | + e.preventDefault(); | |
| 534 | + window.open(goto, '_blank'); | |
| 535 | + return false; | |
| 536 | + } | |
| 537 | + } | |
| 538 | + }); | |
| 539 | + }); | |
| 540 | + </script> | |
| 541 | + | |
| 542 | + <script type="text/javascript"> | |
| 543 | + function vboWidgetStickyNoteAdd(elem) { | |
| 544 | + // display the help for the shortcuts instructions | |
| 545 | + jQuery(elem).closest('.vbo-admin-widget-wrapper').find('.vbo-sticky-shortcuts-help').fadeIn(); | |
| 546 | + // sticky note default HTML placeholder | |
| 547 | + var sticky_placeholder = '<h2>' + Joomla.JText._('VBO_STICKYN_TITLE') + '</h2>' + "\n"; | |
| 548 | + sticky_placeholder += '<p>' + Joomla.JText._('VBO_STICKYN_TEXT') + '</p>' + "\n"; | |
| 549 | + sticky_placeholder += '<p>' + Joomla.JText._('VBO_STICKYN_TEXT2') + '</p>' + "\n"; | |
| 550 | + // build new sticky note HTML | |
| 551 | + var html_sticky_new = '<div class="vbo-sticky-note-cmds">'; | |
| 552 | + html_sticky_new += ' <span class="vbo-sticky-note-cmd-drag"><?php VikBookingIcons::e('ellipsis-v'); ?></span>'; | |
| 553 | + html_sticky_new += ' <span class="vbo-sticky-note-cmd-trash" onclick="vboWidgetStickyNoteDelete(this);"><?php VikBookingIcons::e('trash'); ?></span>'; | |
| 554 | + html_sticky_new += '</div>'; | |
| 555 | + html_sticky_new += '<div contenteditable="true" spellcheck="false" class="vbo-widget-sticky-canvas">'; | |
| 556 | + html_sticky_new += sticky_placeholder; | |
| 557 | + html_sticky_new += '</div>'; | |
| 558 | + | |
| 559 | + // build new element and add HTML to it | |
| 560 | + var sticky_new = document.createElement('li'); | |
| 561 | + sticky_new.setAttribute('class', 'vbo-sticky-note'); | |
| 562 | + sticky_new.innerHTML = html_sticky_new; | |
| 563 | + | |
| 564 | + // attach listener for input event | |
| 565 | + sticky_new.addEventListener('input', VBOCore.debounceEvent(vboWidgetStickyNoteUpdateTxt, 750)); | |
| 566 | + | |
| 567 | + // add new element to the document, before the button to add new sticky notes | |
| 568 | + var append_to = jQuery(elem).closest('.vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note-add'); | |
| 569 | + append_to.before(jQuery(sticky_new)); | |
| 570 | + | |
| 571 | + /** | |
| 572 | + * The input event is immediately triggered so that the newly added note will be saved to avoid | |
| 573 | + * problems when like adding two notes before even typing some text, and then moving/removing them. | |
| 574 | + */ | |
| 575 | + sticky_new.dispatchEvent(new Event('input')); | |
| 576 | + } | |
| 577 | + | |
| 578 | + function vboWidgetStickyNoteDelete(elem) { | |
| 579 | + var note_elem = jQuery(elem); | |
| 580 | + var note_index = note_elem.closest('.vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note').index(note_elem.closest('.vbo-sticky-note')); | |
| 581 | + var widget_instance = note_elem.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance'); | |
| 582 | + | |
| 583 | + var confirm_lbl = Joomla.JText._('VBO_WIDGETS_CONFRMELEM'); | |
| 584 | + confirm_lbl = confirm_lbl.length ? confirm_lbl : 'Continue?'; | |
| 585 | + if (confirm(confirm_lbl)) { | |
| 586 | + // the widget method to call | |
| 587 | + var call_method = 'deleteStickyNote'; | |
| 588 | + | |
| 589 | + // make a silent request to remove the sticky note | |
| 590 | + VBOCore.doAjax( | |
| 591 | + "<?php echo $this->getExecWidgetAjaxUri(); ?>", | |
| 592 | + { | |
| 593 | + widget_id: "<?php echo $this->getIdentifier(); ?>", | |
| 594 | + call: call_method, | |
| 595 | + note_index: note_index, | |
| 596 | + note_instance: widget_instance, | |
| 597 | + tmpl: "component" | |
| 598 | + }, | |
| 599 | + function(response) { | |
| 600 | + try { | |
| 601 | + var obj_res = typeof response === 'string' ? JSON.parse(response) : response; | |
| 602 | + if (!obj_res.hasOwnProperty(call_method)) { | |
| 603 | + console.error('Unexpected JSON response', obj_res); | |
| 604 | + } | |
| 605 | + } catch(err) { | |
| 606 | + console.error('could not parse JSON response', err, response); | |
| 607 | + } | |
| 608 | + }, | |
| 609 | + function(error) { | |
| 610 | + console.error(error); | |
| 611 | + } | |
| 612 | + ); | |
| 613 | + | |
| 614 | + // remove the sticky note from the document | |
| 615 | + note_elem.closest('li.vbo-sticky-note').remove(); | |
| 616 | + } | |
| 617 | + } | |
| 618 | + | |
| 619 | + function vboWidgetStickyNoteUpdateTxt(event) { | |
| 620 | + var note_elem = jQuery(this); | |
| 621 | + // display the help for the shortcuts instructions | |
| 622 | + note_elem.closest('.vbo-admin-widget-wrapper').find('.vbo-sticky-shortcuts-help').fadeIn(); | |
| 623 | + // element "this" may be different depending on how the event was triggered | |
| 624 | + if (note_elem.find('.vbo-widget-sticky-canvas').length) { | |
| 625 | + var note_txt = note_elem.find('.vbo-widget-sticky-canvas').html(); | |
| 626 | + } else { | |
| 627 | + var note_txt = note_elem.html(); | |
| 628 | + } | |
| 629 | + var note_index = note_elem.closest('.vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note').index(note_elem.closest('.vbo-sticky-note')); | |
| 630 | + var widget_instance = note_elem.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance'); | |
| 631 | + | |
| 632 | + // the widget method to call | |
| 633 | + var call_method = 'updateStickyNote'; | |
| 634 | + | |
| 635 | + // make a silent request to update the sticky note details | |
| 636 | + VBOCore.doAjax( | |
| 637 | + "<?php echo $this->getExecWidgetAjaxUri(); ?>", | |
| 638 | + { | |
| 639 | + widget_id: "<?php echo $this->getIdentifier(); ?>", | |
| 640 | + call: call_method, | |
| 641 | + note_txt: note_txt, | |
| 642 | + note_index: note_index, | |
| 643 | + note_instance: widget_instance, | |
| 644 | + tmpl: "component" | |
| 645 | + }, | |
| 646 | + function(response) { | |
| 647 | + try { | |
| 648 | + var obj_res = typeof response === 'string' ? JSON.parse(response) : response; | |
| 649 | + if (!obj_res.hasOwnProperty(call_method)) { | |
| 650 | + console.error('Unexpected JSON response', obj_res); | |
| 651 | + } else { | |
| 652 | + // response for the method updateStickyNote() may contain the new instance given to the widget | |
| 653 | + if (widget_instance < 0 && obj_res[call_method].indexOf('[instance=') >= 0) { | |
| 654 | + // extract the new instance assigned | |
| 655 | + var resp_left = obj_res[call_method].split('[instance='); | |
| 656 | + var widget_instance_new = resp_left[1].split(']')[0]; | |
| 657 | + if (widget_instance_new && widget_instance_new.length) { | |
| 658 | + // update widget's instance | |
| 659 | + note_elem.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance', widget_instance_new); | |
| 660 | + } | |
| 661 | + } | |
| 662 | + } | |
| 663 | + } catch(err) { | |
| 664 | + console.error('could not parse JSON response', err, response); | |
| 665 | + } | |
| 666 | + }, | |
| 667 | + function(error) { | |
| 668 | + console.error(error); | |
| 669 | + } | |
| 670 | + ); | |
| 671 | + } | |
| 672 | + </script> | |
| 673 | + <?php | |
| 674 | + } | |
| 675 | + } | |
| 676 | + | |
| 677 | + /** | |
| 678 | + * Protected method to guess if the widget being rendered via AJAX in the | |
| 679 | + * multitask panel should load specific settings. Should be called when | |
| 680 | + * settings have been loaded, and if AJAX + multitask rendering is detected. | |
| 681 | + * It's assumed that the AJAX rendering of a multitask widget takes place one | |
| 682 | + * second before the AJAX event that updates the multitask map to push the widget. | |
| 683 | + * | |
| 684 | + * @param ?VBOMultitaskData $data the data object injected to the widget. | |
| 685 | + * | |
| 686 | + * @return bool|int false on failure, guessed settings index otherwise. | |
| 687 | + */ | |
| 688 | + protected function guessMultitaskStickyInstance(?VBOMultitaskData $data = null) | |
| 689 | + { | |
| 690 | + if (!is_array($this->widgetSettings) || !count($this->widgetSettings)) { | |
| 691 | + // nothing to guess if this widget has got no saved settings | |
| 692 | + return false; | |
| 693 | + } | |
| 694 | + | |
| 695 | + if (!is_object($data)) { | |
| 696 | + // multitask data object must be set | |
| 697 | + return false; | |
| 698 | + } | |
| 699 | + | |
| 700 | + // the page must be set in the multitask object | |
| 701 | + $vbo_page = $data->getPage(); | |
| 702 | + if (empty($vbo_page)) { | |
| 703 | + // nothing to guess if no current page set | |
| 704 | + return false; | |
| 705 | + } | |
| 706 | + | |
| 707 | + // get the map for the current page | |
| 708 | + $page_map = VikBooking::getAdminWidgetsInstance()->getMultitaskingMap($vbo_page, $whole = false); | |
| 709 | + if (!is_array($page_map) || !count($page_map)) { | |
| 710 | + // the multitask panel of this page has got no widgets saved, return the first index for settings | |
| 711 | + return 0; | |
| 712 | + } | |
| 713 | + | |
| 714 | + // count how many widgets of this type are already on this page | |
| 715 | + $guessed_index = 0; | |
| 716 | + foreach ($page_map as $widget_type) { | |
| 717 | + if ($widget_type == $this->getIdentifier()) { | |
| 718 | + $guessed_index++; | |
| 719 | + } | |
| 720 | + } | |
| 721 | + | |
| 722 | + // return the guessed index, which will load the next hypothetical instance | |
| 723 | + return $guessed_index; | |
| 724 | + } | |
| 725 | +} | |