1: <?php
2:
3: /**
4: * keyboard shortcuts modules
5: * @package modules
6: * @subpackage keyboard_shortcuts
7: */
8:
9: if (!defined('DEBUG_MODE')) { die(); }
10:
11: /**
12: * @subpackage keyboard_shortcuts/handler
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: * @subpackage keyboard_shortcuts/handler
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: * @subpackage keyboard_shortcuts/handler
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: * @subpackage keyboard_shortcuts/handler
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: * @subpackage keyboard_shortcuts/handler
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: * @subpackage keyboard_shortcuts/output
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: * @subpackage keyboard_shortcuts/output
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: * @subpackage keyboard_shortcuts/output
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: * @subpackage keyboard_shortcuts/output
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: * @subpackage keyboard_shortcuts/output
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: * @subpackage keyboard_shortcuts/output
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: * @subpackage keyboard_shortcuts/functions
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: * @subpackage keyboard_shortcuts/functions
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: * @subpackage keyboard_shortcuts/functions
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: * @subpackage keyboard_shortcuts/functions
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: * @subpackage keyboard_shortcuts/functions
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: