| 1: | <?php |
| 2: | |
| 3: | |
| 4: | |
| 5: | |
| 6: | |
| 7: | |
| 8: | |
| 9: | if (!defined('DEBUG_MODE')) { die(); } |
| 10: | |
| 11: | |
| 12: | |
| 13: | |
| 14: | class Hm_Handler_load_keyboard_shortcuts extends Hm_Handler_Module { |
| 15: | public function process() { |
| 16: | $this->out('keyboard_shortcut_data', shortcut_defaults($this->user_config)); |
| 17: | $this->out('shortcuts_enabled', $this->user_config->get('enable_keyboard_shortcuts_setting', DEFAULT_ENABLE_KEYBOARD_SHORTCUTS)); |
| 18: | } |
| 19: | } |
| 20: | |
| 21: | |
| 22: | |
| 23: | |
| 24: | class Hm_Handler_get_shortcut_setting extends Hm_Handler_Module { |
| 25: | public function process() { |
| 26: | $this->out('shortcuts_enabled', $this->user_config->get('enable_keyboard_shortcuts_setting', DEFAULT_ENABLE_KEYBOARD_SHORTCUTS)); |
| 27: | } |
| 28: | } |
| 29: | |
| 30: | |
| 31: | |
| 32: | |
| 33: | class Hm_Handler_process_edit_shortcut extends Hm_Handler_Module { |
| 34: | public function process() { |
| 35: | list($success, $form) = $this->process_form(array('shortcut_meta', 'shortcut_key', 'shortcut_id')); |
| 36: | if ($success) { |
| 37: | $shortcuts = $this->get('keyboard_shortcut_data'); |
| 38: | $codes = keycodes(); |
| 39: | if (!array_key_exists($form['shortcut_id'], $shortcuts)) { |
| 40: | Hm_Msgs::add('Unknown shortcut', 'warning'); |
| 41: | return; |
| 42: | } |
| 43: | if (!array_search($form['shortcut_key'], $codes)) { |
| 44: | Hm_Msgs::add('Unknown shortcut key', 'warning'); |
| 45: | return; |
| 46: | } |
| 47: | $meta_list = array(); |
| 48: | foreach ($form['shortcut_meta'] as $meta) { |
| 49: | if (!in_array($meta, array('meta', 'alt', 'shift', 'control', 'none'))) { |
| 50: | Hm_Msgs::add('Uknown modifier key', 'warning'); |
| 51: | return; |
| 52: | } |
| 53: | if ($meta != 'none') { |
| 54: | $meta_list[] = $meta; |
| 55: | } |
| 56: | } |
| 57: | $custom_shortcuts = $this->user_config->get('keyboard_shortcuts', array()); |
| 58: | $custom_shortcuts[$form['shortcut_id']] = array('meta' => $meta_list, 'key' => $form['shortcut_key']); |
| 59: | $this->user_config->set('keyboard_shortcuts', $custom_shortcuts); |
| 60: | $user_data = $this->user_config->dump(); |
| 61: | $this->session->set('user_data', $user_data); |
| 62: | $this->session->record_unsaved('Shortcut updated'); |
| 63: | Hm_Msgs::add('Shortcut updated'); |
| 64: | $this->save_hm_msgs(); |
| 65: | Hm_Dispatch::page_redirect('?page=shortcuts'); |
| 66: | } |
| 67: | } |
| 68: | } |
| 69: | |
| 70: | |
| 71: | |
| 72: | |
| 73: | class Hm_Handler_load_edit_id extends Hm_Handler_Module { |
| 74: | public function process() { |
| 75: | if (array_key_exists('edit_id', $this->request->get)) { |
| 76: | $shortcuts = $this->get('keyboard_shortcut_data'); |
| 77: | if (array_key_exists($this->request->get['edit_id'], $shortcuts)) { |
| 78: | $details = $shortcuts[$this->request->get['edit_id']]; |
| 79: | $details['id'] = $this->request->get['edit_id']; |
| 80: | $this->out('shortcut_details', $details); |
| 81: | } |
| 82: | } |
| 83: | } |
| 84: | } |
| 85: | |
| 86: | |
| 87: | |
| 88: | |
| 89: | class Hm_Handler_process_enable_shortcut_setting extends Hm_Handler_Module { |
| 90: | public function process() { |
| 91: | function shortcut_enabled_callback($val) { return $val; } |
| 92: | process_site_setting('enable_keyboard_shortcuts', $this, 'shortcut_enabled_callback', DEFAULT_ENABLE_KEYBOARD_SHORTCUTS, true); |
| 93: | } |
| 94: | } |
| 95: | |
| 96: | |
| 97: | |
| 98: | |
| 99: | class Hm_Output_enable_shortcut_setting extends Hm_Output_Module { |
| 100: | protected function output() { |
| 101: | $settings = $this->get('user_settings'); |
| 102: | if (array_key_exists('enable_keyboard_shortcuts', $settings) && $settings['enable_keyboard_shortcuts']) { |
| 103: | $checked = ' checked="checked"'; |
| 104: | if($settings['enable_keyboard_shortcuts'] !== DEFAULT_ENABLE_KEYBOARD_SHORTCUTS) { |
| 105: | $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise fs-6 cursor-pointer refresh_list reset_default_value_checkbox"></i></span>'; |
| 106: | } |
| 107: | } |
| 108: | else { |
| 109: | $checked = ''; |
| 110: | $reset=''; |
| 111: | } |
| 112: | return '<tr class="general_setting"><td><label class="form-check-label" for="enable_keyboard_shortcuts">'. |
| 113: | $this->trans('Enable keyboard shortcuts').'</label></td>'. |
| 114: | '<td><input class="form-check-input" type="checkbox" '.$checked. |
| 115: | ' value="1" id="enable_keyboard_shortcuts" name="enable_keyboard_shortcuts" data-default-value="'.(DEFAULT_ENABLE_KEYBOARD_SHORTCUTS ? 'true' : 'false') . '"/>'.$reset.'</td></tr>'; |
| 116: | } |
| 117: | } |
| 118: | |
| 119: | |
| 120: | |
| 121: | |
| 122: | class Hm_Output_start_shortcuts_page extends Hm_Output_Module { |
| 123: | protected function output() { |
| 124: | return '<div class="shortcut_content px-0"><div class="content_title px-3">'.$this->trans('Shortcuts').'</div>'; |
| 125: | } |
| 126: | } |
| 127: | |
| 128: | |
| 129: | |
| 130: | |
| 131: | class Hm_Output_shortcut_edit_form extends Hm_Output_Module { |
| 132: | protected function output() { |
| 133: | $details = $this->get('shortcut_details'); |
| 134: | if (!$details || !is_array($details)) { |
| 135: | return; |
| 136: | } |
| 137: | $codes = keycodes(); |
| 138: | $meta = array('none', 'shift', 'control', 'alt', 'meta'); |
| 139: | $res = '<div class="settings_subtitle p-3">'.$this->trans('Edit Shortcut').'</div>'; |
| 140: | $res .= '<div class="edit_shortcut_form px-5"><form method="POST" action="?page=shortcuts&edit_id='.$this->html_safe($details['id']).'">'; |
| 141: | $res .= '<input type="hidden" name="shortcut_id" value="'.$this->html_safe($details['id']).'" />'; |
| 142: | $res .= '<input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />'; |
| 143: | $res .= '<table>'; |
| 144: | $res .= '<tr><th colspan="2">'.$this->trans(ucfirst($details['group'])).' : '. |
| 145: | $this->trans($details['label']).'</th></tr>'; |
| 146: | $res .= '<tr><td>'.$this->trans('Modifier Key(s)').'</td>'; |
| 147: | $res .= '<td><select class="form-select form-select-sm" required multiple size="5" name="shortcut_meta[]">'; |
| 148: | foreach ($meta as $v) { |
| 149: | $res .= '<option '; |
| 150: | if (in_array($v, $details['control_chars'], true)) { |
| 151: | $res .= 'selected="selected" '; |
| 152: | } |
| 153: | elseif (count($details['control_chars']) == 0 && $v == 'none') { |
| 154: | $res .= 'selected="selected" '; |
| 155: | } |
| 156: | $res .= 'value="'.$v.'">'.ucfirst($v).'</option>'; |
| 157: | } |
| 158: | $res .= '</select></td></tr>'; |
| 159: | $res .= '<tr><td>'.$this->trans('Character').'</td>'; |
| 160: | $res .= '<td><select class="form-select form-select-sm" required " name="shortcut_key">'; |
| 161: | foreach ($codes as $name => $val) { |
| 162: | $res .= '<option '; |
| 163: | if ($val == $details['char']) { |
| 164: | $res .= 'selected="selected" '; |
| 165: | } |
| 166: | $res .= 'value="'.$this->html_safe($val).'">'.$this->html_safe($name).'</option>'; |
| 167: | } |
| 168: | $res .= '</select></td></tr>'; |
| 169: | $res .= '<tr><td colspan="2"> |
| 170: | <input type="submit" class="btn btn-primary" value="'.$this->trans('Update').'" /> |
| 171: | <input type="button" class="btn btn-light border reset_shortcut" value="'.$this->trans('Cancel').'" /> |
| 172: | </td></tr>'; |
| 173: | |
| 174: | $res .= '</table></form></div>'; |
| 175: | return $res; |
| 176: | } |
| 177: | } |
| 178: | |
| 179: | |
| 180: | |
| 181: | |
| 182: | class Hm_Output_shortcuts_content extends Hm_Output_Module { |
| 183: | protected function output() { |
| 184: | $shortcuts = $this->get('keyboard_shortcut_data'); |
| 185: | $res = '<table class="shortcut_table">'; |
| 186: | $res .= '<tr><td colspan="3" class="settings_subtitle px-3">'.$this->trans('General').'</th></tr>'; |
| 187: | $res .= format_shortcut_section($shortcuts, 'general', $this); |
| 188: | $res .= '<tr><td colspan="3" class="settings_subtitle px-3">'.$this->trans('Message List').'</td></tr>'; |
| 189: | $res .= format_shortcut_section($shortcuts, 'message_list', $this); |
| 190: | $res .= '<tr><td colspan="3" class="settings_subtitle px-3">'.$this->trans('Message View').'</td></tr>'; |
| 191: | $res .= format_shortcut_section($shortcuts, 'message', $this); |
| 192: | $res .= '</table>'; |
| 193: | $res .= '</div>'; |
| 194: | return $res; |
| 195: | } |
| 196: | } |
| 197: | |
| 198: | |
| 199: | |
| 200: | |
| 201: | class Hm_Output_keyboard_shortcut_data extends Hm_Output_Module { |
| 202: | protected function output() { |
| 203: | if ($this->get('shortcuts_enabled')) { |
| 204: | return '<script type="text/javascript">'.format_shortcuts($this->get('keyboard_shortcut_data')).'</script>'; |
| 205: | } |
| 206: | } |
| 207: | } |
| 208: | |
| 209: | |
| 210: | |
| 211: | |
| 212: | class Hm_Output_shortcuts_page_link extends Hm_Output_Module { |
| 213: | protected function output() { |
| 214: | if ($this->get('shortcuts_enabled')) { |
| 215: | $res = '<li class="menu_shortcuts"><a class="unread_link" href="?page=shortcuts">'; |
| 216: | if (!$this->get('hide_folder_icons')) { |
| 217: | $res .= '<i class="bi bi-code-slash menu-icon"></i>'; |
| 218: | } |
| 219: | $res .= $this->trans('Shortcuts').'</a></li>'; |
| 220: | if ($this->format == 'HTML5') { |
| 221: | return $res; |
| 222: | } |
| 223: | $this->concat('formatted_folder_list', $res); |
| 224: | } |
| 225: | } |
| 226: | } |
| 227: | |
| 228: | |
| 229: | |
| 230: | |
| 231: | if (!hm_exists('format_shortcut_section')) { |
| 232: | function format_shortcut_section($data, $type, $output_mod) { |
| 233: | $res = ''; |
| 234: | $codes = keycodes(); |
| 235: | foreach ($data as $index => $vals) { |
| 236: | if ($vals['group'] == $type) { |
| 237: | $c_keys = ucfirst(implode(' + ', $vals['control_chars'])); |
| 238: | if ($c_keys) { |
| 239: | $c_keys .= ' +'; |
| 240: | } |
| 241: | $char = array_search($vals['char'], $codes); |
| 242: | $res .= sprintf('<tr><th class="keys">%s %s</th><th>%s</th>'. |
| 243: | '<td><a href="?page=shortcuts&edit_id=%s"><i class="kbd_config cursor-pointer bi bi-gear-wide-connected fs-5"></i><a></td></tr>', |
| 244: | $output_mod->html_safe($c_keys), $output_mod->html_safe($char), |
| 245: | $output_mod->trans($vals['label']), $index); |
| 246: | } |
| 247: | } |
| 248: | return $res; |
| 249: | }} |
| 250: | |
| 251: | |
| 252: | |
| 253: | |
| 254: | if (!hm_exists('shortcut_defaults')) { |
| 255: | function shortcut_defaults($user_config=false) { |
| 256: | $res = array( |
| 257: | array('label' => 'Unfocus all input elements', 'group' => 'general', 'page' => '*', 'control_chars' => array(), 'char' => 27, 'action' => 'Keyboard_Shortcuts.unfocus', 'target' => 'false'), |
| 258: | array('label' => 'Jump to the "Everything" page', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 69, 'action' => 'ks_redirect', 'target' => '?page=message_list&list_path=combined_inbox'), |
| 259: | array('label' => 'Jump to the "Unread" page', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 75, 'action' => 'ks_redirect', 'target' => '?page=message_list&list_path=unread'), |
| 260: | array('label' => 'Jump to the "Flagged" page', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 70, 'action' => 'ks_redirect', 'target' => '?page=message_list&list_path=flagged'), |
| 261: | array('label' => 'Jump to History', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 72, 'action' => 'ks_redirect', 'target' => '?page=history'), |
| 262: | array('label' => 'Jump to Contacts', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 67, 'action' => 'ks_redirect', 'target' => '?page=contacts'), |
| 263: | array('label' => 'Jump to the Compose page', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 83, 'action' => 'ks_redirect', 'target' => '?page=compose'), |
| 264: | array('label' => 'Toggle the folder list', 'group' => 'general', 'page' => '*', 'control_chars' => array('control', 'shift'), 'char' => 89, 'action' => 'Hm_Folders.toggle_folder_list', 'target' => false), |
| 265: | |
| 266: | array('label' => 'Focus the next message in the list', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array(), 'char' => 78, 'action' => 'ks_next_msg_list', 'target' => false), |
| 267: | array('label' => 'Focus the previous message in the list', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array(), 'char' => 80, 'action' => 'ks_prev_msg_list', 'target' => false), |
| 268: | array('label' => 'Open the currently focused message', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array(), 'char' => 13, 'action' => 'ks_load_msg', 'target' => false), |
| 269: | array('label' => 'Select/unselect the currently focused message', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array(), 'char' => 83, 'action' => 'ks_select_msg', 'target' => false), |
| 270: | array('label' => 'Toggle all message selections', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array(), 'char' => 65, 'action' => 'ks_select_all', 'target' => false), |
| 271: | array('label' => 'Mark selected messages as read', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array('shift'), 'char' => 82, 'action' => 'ks_click_button', 'target' => '.msg_read'), |
| 272: | array('label' => 'Mark selected messages as unread', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array('shift'), 'char' => 85, 'action' => 'ks_click_button', 'target' => '.msg_unread'), |
| 273: | array('label' => 'Mark selected messages as flagged', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array('shift'), 'char' => 70, 'action' => 'ks_click_button', 'target' => '.msg_flag'), |
| 274: | array('label' => 'Mark selected messages as unflagged', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array('shift'), 'char' => 69, 'action' => 'ks_click_button', 'target' => '.msg_unflag'), |
| 275: | array('label' => 'Delete selected messages', 'group' => 'message_list', 'page' => 'message_list', 'control_chars' => array('shift'), 'char' => 68, 'action' => 'ks_click_button', 'target' => '.msg_delete'), |
| 276: | |
| 277: | array('label' => 'View the next message in the list', 'group' => 'message', 'page' => 'message', 'control_chars' => array(), 'char' => 78, 'action' => 'ks_follow_link', 'target' => '.nlink'), |
| 278: | array('label' => 'View the previous message in the list', 'group' => 'message', 'page' => 'message', 'control_chars' => array(), 'char' => 80, 'action' => 'ks_follow_link', 'target' => '.plink'), |
| 279: | array('label' => 'Reply', 'group' => 'message', 'page' => 'message', 'control_chars' => array(), 'char' => 82, 'action' => 'ks_follow_link', 'target' => '.reply_link'), |
| 280: | array('label' => 'Reply-all', 'group' => 'message', 'page' => 'message', 'control_chars' => array('shift'), 'char' => 82, 'action' => 'ks_follow_link', 'target' => '.reply_all_link'), |
| 281: | array('label' => 'Forward', 'group' => 'message', 'page' => 'message', 'control_chars' => array('shift'), 'char' => 70, 'action' => 'ks_follow_link', 'target' => '.forward_link'), |
| 282: | array('label' => 'Flag the message', 'group' => 'message', 'page' => 'message', 'control_chars' => array(), 'char' => 70, 'action' => 'ks_click_button', 'target' => '.flagged_link'), |
| 283: | array('label' => 'Unflag the message', 'group' => 'message', 'page' => 'message', 'control_chars' => array(), 'char' => 85, 'action' => 'ks_click_button', 'target' => '.unflagged_link'), |
| 284: | array('label' => 'Delete the message', 'group' => 'message', 'page' => 'message', 'control_chars' => array(), 'char' => 68, 'action' => 'ks_click_button', 'target' => '.delete_link'), |
| 285: | ); |
| 286: | if (!$user_config) { |
| 287: | return $res; |
| 288: | } |
| 289: | $shortcuts = $user_config->get('keyboard_shortcuts', array()); |
| 290: | foreach ($shortcuts as $index => $vals) { |
| 291: | $res[$index]['char'] = $vals['key']; |
| 292: | $res[$index]['control_chars'] = $vals['meta']; |
| 293: | } |
| 294: | return $res; |
| 295: | }} |
| 296: | |
| 297: | |
| 298: | |
| 299: | |
| 300: | if (!hm_exists('fromat_keyboard_action')) { |
| 301: | function format_keyboard_action($action) { |
| 302: | $actions = Array( |
| 303: | 'Keyboard_Shortcuts.unfocus' => 'unfocus', |
| 304: | 'Hm_Folders.toggle_folder_list' => 'toggle', |
| 305: | 'ks_redirect' => 'redirect', |
| 306: | 'ks_next_msg_list' => 'next', |
| 307: | 'ks_prev_msg_list' => 'prev', |
| 308: | 'ks_load_msg' => 'load', |
| 309: | 'ks_select_msg' => 'select', |
| 310: | 'ks_select_all' => 'select_all', |
| 311: | 'ks_click_button' => 'click', |
| 312: | 'ks_follow_link' => 'link' |
| 313: | ); |
| 314: | return $actions[$action]; |
| 315: | }} |
| 316: | |
| 317: | |
| 318: | |
| 319: | |
| 320: | if (!hm_exists('format_shortcuts')) { |
| 321: | function format_shortcuts($data) { |
| 322: | $res = "var shortcuts = [\n"; |
| 323: | foreach ($data as $vals) { |
| 324: | $c_chars = implode(',', array_map(function($v) { return "'".$v."'"; }, $vals['control_chars'])); |
| 325: | $res .= sprintf("{'page': '%s', 'control_chars': [%s], 'char': %s, 'action': '%s', 'target': '%s'},\n", |
| 326: | $vals['page'], $c_chars, $vals['char'], format_keyboard_action($vals['action']), $vals['target']); |
| 327: | } |
| 328: | $res .= "];\n"; |
| 329: | return $res; |
| 330: | }} |
| 331: | |
| 332: | |
| 333: | |
| 334: | |
| 335: | if (!hm_exists('keycodes')) { |
| 336: | function keycodes() { |
| 337: | return array( |
| 338: | 'backspace' => 8, 'enter' => 13, 'pause/break' => 19, 'escape' => 27, 'page up' => 33, 'page down' => 34, |
| 339: | 'end' => 35, 'home' => 36, 'left arrow' => 37, 'up arrow' => 38, 'right arrow' => 39, 'down arrow' => 40, 'insert' => 45, 'delete' => 46, |
| 340: | '0' => 48, '1' => 49, '2' => 50, '3' => 51, '4' => 52, '5' => 53, '6' => 54, '7' => 55, '8' => 56, '9' => 57, 'a' => 65, 'b' => 66, 'c' => 67, |
| 341: | 'd' => 68, 'e' => 69, 'f' => 70, 'g' => 71, 'h' => 72, 'i' => 73, 'j' => 74, 'k' => 75, 'l' => 76, 'm' => 77, 'n' => 78, 'o' => 79, 'p' => 80, |
| 342: | 'q' => 81, 'r' => 82, 's' => 83, 't' => 84, 'u' => 85, 'v' => 86, 'w' => 87, 'x' => 88, 'y' => 89, 'z' => 90, 'numpad 0' => 96, 'numpad 1' => 97, |
| 343: | 'numpad 2' => 98, 'numpad 3' => 99, 'numpad 4' => 100, 'numpad 5' => 101, 'numpad 6' => 102, 'numpad 7' => 103, 'numpad 8' => 104, 'numpad 9' => 105, |
| 344: | 'multiply' => 106, 'add' => 107, 'subtract' => 109, 'decimal point' => 110, 'divide' => 111, 'f1' => 112, 'f2' => 113, 'f3' => 114, 'f4' => 115, |
| 345: | 'f5' => 116, 'f6' => 117, 'f7' => 118, 'f8' => 119, 'f9' => 120, 'f10' => 121, 'f11' => 122, 'f12' => 123, |
| 346: | 'semi-colon' => 186, 'equal sign' => 187, 'comma' => 188, 'dash' => 189, 'period' => 190, 'forward slash' => 191, 'grave accent' => 192, |
| 347: | 'open bracket' => 219, 'back slash' => 220, 'close bracket' => 221, 'single quote' => 222 |
| 348: | ); |
| 349: | }} |
| 350: | |