Fixed issue #656 - Shortcuts in different keyboard layout.

This commit is contained in:
Martin Edenhofer 2017-04-10 08:08:18 +02:00
parent b8ed5151fb
commit 5fd5f4318a
8 changed files with 279 additions and 105 deletions

View file

@ -13,6 +13,8 @@ class App.KeyboardShortcutModal extends App.ControllerModal
content: ->
App.view('keyboard_shortcuts')(
areas: App.Config.get('keyboard_shortcuts')
magicKey: App.Browser.magicKey()
hotkeys: App.Browser.hotkeys().split('+').reverse()
)
exists: =>
@ -36,7 +38,8 @@ class App.KeyboardShortcutWidget extends Spine.Module
)
observerKeys: =>
navigationHotkeys = 'alt+ctrl'
navigationHotkeys = App.Browser.hotkeys()
areas = App.Config.get('keyboard_shortcuts')
for area in areas
for item in area.content
@ -401,25 +404,25 @@ App.Config.set(
shortcuts: [
{
key: 'u'
hotkeys: true
magicKey: true
description: 'Format as _underlined_'
globalEvent: 'richtext-underline'
}
{
key: 'b'
hotkeys: true
magicKey: true
description: 'Format as |bold|'
globalEvent: 'richtext-bold'
}
{
key: 'i'
hotkeys: true
magicKey: true
description: 'Format as ||italic||'
globalEvent: 'richtext-italic'
}
{
key: 'v'
hotkeys: true
key: 's'
magicKey: true
description: 'Format as //strikethrough//'
globalEvent: 'richtext-strikethrough'
}

View file

@ -12,9 +12,17 @@ class Widget extends App.Controller
return if !@permissionCheck('admin.translation')
# bind on key down
# if ctrl+alt+t is pressed, enable translation_inline and fire ui:rerender
# if hotkeys+t is pressed, enable translation_inline and fire ui:rerender
browserHotkeys = App.Browser.hotkeys()
$(document).on('keydown.translation', (e) =>
if e.altKey && e.ctrlKey && e.keyCode is 84
hotkeys = false
if browserHotkeys is 'ctrl+shift'
if !e.altKey && e.ctrlKey && !e.metaKey && e.shiftKey
hotkeys = true
else
if e.altKey && e.ctrlKey && !e.metaKey
hotkeys = true
if hotkeys && e.keyCode is 84
@toogle()
)

View file

@ -82,6 +82,20 @@ class App.Browser
localStorage.setItem('fingerprint', fingerprint)
fingerprint
@magicKey: ->
browser = @detection()
magicKey = 'ctrl'
if browser && browser.os && browser.os.name.toString().match(/mac/i)
magicKey = 'cmd'
magicKey
@hotkeys: ->
browser = @detection()
hotkeys = 'ctrl+shift'
if browser && browser.os && browser.os.name.toString().match(/mac/i)
hotkeys = 'alt+ctrl'
hotkeys
class Modal extends App.ControllerModal
buttonClose: false
buttonCancel: false

View file

@ -39,6 +39,7 @@
66: true, // b
73: true, // i
85: true, // u
83: true, // s
},
//maxlength: 20,
};
@ -59,11 +60,9 @@
this.preventInput = false
// detect firefox / handle contenteditable issues
this.browser = undefined
if ( navigator && navigator.userAgent && navigator.userAgent.search('Firefox') != -1) {
this.browser = 'ff'
}
// handle contenteditable issues
this.browserMagicKey = App.Browser.magicKey()
this.browserHotkeys = App.Browser.hotkeys()
this.init();
}
@ -112,28 +111,76 @@
}
}
// on zammad altKey + ctrlKey + i/b/u
// altKey + ctrlKey + u -> Toggles the current selection between underlined and not underlined
// altKey + ctrlKey + b -> Toggles the current selection between bold and non-bold
// altKey + ctrlKey + i -> Toggles the current selection between italic and non-italic
// altKey + ctrlKey + v -> Toggles the current selection between strike and non-strike
// altKey + ctrlKey + f -> Removes the formatting tags from the current selection
// altKey + ctrlKey + y -> Removes the formatting from while textarea
// altKey + ctrlKey + z -> Inserts a Horizontal Rule
// altKey + ctrlKey + l -> Toggles the text selection between an unordered list and a normal block
// altKey + ctrlKey + k -> Toggles the text selection between an ordered list and a normal block
// altKey + ctrlKey + o -> Draws a line through the middle of the current selection
// altKey + ctrlKey + w -> Removes any hyperlink from the current selection
if ( e.altKey && e.ctrlKey && !e.metaKey && (_this.options.richTextFormatKey[ e.keyCode ]
// on zammad magicKey + i/b/u/s
// hotkeys + u -> Toggles the current selection between underlined and not underlined
// hotkeys + b -> Toggles the current selection between bold and non-bold
// hotkeys + i -> Toggles the current selection between italic and non-italic
// hotkeys + v -> Toggles the current selection between strike and non-strike
// hotkeys + f -> Removes the formatting tags from the current selection
// hotkeys + y -> Removes the formatting from while textarea
// hotkeys + z -> Inserts a Horizontal Rule
// hotkeys + l -> Toggles the text selection between an unordered list and a normal block
// hotkeys + k -> Toggles the text selection between an ordered list and a normal block
// hotkeys + o -> Draws a line through the middle of the current selection
// hotkeys + w -> Removes any hyperlink from the current selection
var richtTextControl = false
if (_this.browserMagicKey == 'cmd') {
if (!e.altKey && !e.ctrlKey && e.metaKey) {
richtTextControl = true
}
}
else {
if (!e.altKey && e.ctrlKey && !e.metaKey) {
richtTextControl = true
}
}
if (richtTextControl && _this.options.richTextFormatKey[ e.keyCode ]) {
e.preventDefault()
if (e.keyCode == 66) {
document.execCommand('bold')
return true
}
if (e.keyCode == 73) {
document.execCommand('italic')
return true
}
if (e.keyCode == 85) {
document.execCommand('underline')
return true
}
if (e.keyCode == 83) {
document.execCommand('strikeThrough')
return true
}
}
var hotkeys = false
if (_this.browserHotkeys == 'ctrl+shift') {
if (!e.altKey && e.ctrlKey && !e.metaKey && e.shiftKey) {
hotkeys = true
}
}
else {
if (e.altKey && e.ctrlKey && !e.metaKey) {
hotkeys = true
}
}
if (hotkeys && (_this.options.richTextFormatKey[ e.keyCode ]
|| e.keyCode == 49
|| e.keyCode == 50
|| e.keyCode == 51
|| e.keyCode == 66
|| e.keyCode == 70
|| e.keyCode == 90
|| e.keyCode == 76
|| e.keyCode == 70
|| e.keyCode == 73
|| e.keyCode == 75
|| e.keyCode == 76
|| e.keyCode == 85
|| e.keyCode == 86
|| e.keyCode == 87
|| e.keyCode == 90
|| e.keyCode == 89)) {
e.preventDefault()
@ -142,37 +189,6 @@
return
}
if (e.keyCode == 66) {
document.execCommand('bold')
}
if (e.keyCode == 73) {
document.execCommand('italic')
}
if (e.keyCode == 85) {
document.execCommand('underline')
}
if (e.keyCode == 70) {
document.execCommand('removeFormat')
}
if (e.keyCode == 89) {
var cleanHtml = App.Utils.htmlRemoveRichtext(_this.$element.html())
_this.$element.html(cleanHtml)
}
if (e.keyCode == 90) {
document.execCommand('insertHorizontalRule')
}
if (e.keyCode == 76) {
document.execCommand('insertUnorderedList')
}
if (e.keyCode == 75) {
document.execCommand('insertOrderedList')
}
if (e.keyCode == 86) {
document.execCommand('strikeThrough')
}
if (e.keyCode == 87) {
document.execCommand('unlink')
}
if (e.keyCode == 49) {
_this.toggleBlock('h1')
}
@ -182,6 +198,37 @@
if (e.keyCode == 51) {
_this.toggleBlock('h3')
}
if (e.keyCode == 66) {
document.execCommand('bold')
}
if (e.keyCode == 70) {
document.execCommand('removeFormat')
}
if (e.keyCode == 73) {
document.execCommand('italic')
}
if (e.keyCode == 75) {
document.execCommand('insertOrderedList')
}
if (e.keyCode == 76) {
document.execCommand('insertUnorderedList')
}
if (e.keyCode == 85) {
document.execCommand('underline')
}
if (e.keyCode == 86) {
document.execCommand('strikeThrough')
}
if (e.keyCode == 87) {
document.execCommand('unlink')
}
if (e.keyCode == 89) {
var cleanHtml = App.Utils.htmlRemoveRichtext(_this.$element.html())
_this.$element.html(cleanHtml)
}
if (e.keyCode == 90) {
document.execCommand('insertHorizontalRule')
}
_this.log('content editable richtext key', e.keyCode)
return true
}
@ -391,22 +438,6 @@
return false
}
// check if rich text key is pressed
Plugin.prototype.richTextKey = function(e) {
// e.altKey
// e.ctrlKey
// e.metaKey
// on mac block e.metaKey + i/b/u
if ( !e.altKey && e.metaKey && this.options.richTextFormatKey[ e.keyCode ] ) {
return true
}
// on win block e.ctrlKey + i/b/u
if ( !e.altKey && e.ctrlKey && this.options.richTextFormatKey[ e.keyCode ] ) {
return true
}
return false
}
// max length check
Plugin.prototype.maxLengthOk = function(typeAhead) {
if ( !this.options.maxlength ) {

View file

@ -7,7 +7,7 @@
<% if item.where: %><p><i><%- @T(item.where) %></i></p><% end %>
<% for shortcut in item.shortcuts: %>
<% if shortcut.hotkeys: %>
<kbd>ctrl</kbd> <kbd>alt</kbd>
<kbd><%- @hotkeys.join('</kbd> <kbd>') %></kbd>
<% end %>
<% if shortcut.keyPrefix: %>
<%= shortcut.keyPrefix %>
@ -33,7 +33,9 @@
<% if item.where: %><p><i><%- @T(item.where) %></i></p><% end %>
<% for shortcut in item.shortcuts: %>
<% if shortcut.hotkeys: %>
<kbd>ctrl</kbd> <kbd>alt</kbd>
<kbd><%- @hotkeys.join('</kbd> <kbd>') %></kbd>
<% else if shortcut.magicKey: %>
<kbd><%= @magicKey %></kbd>
<% end %>
<% if shortcut.keyPrefix: %>
<%= shortcut.keyPrefix %>

View file

@ -2,7 +2,7 @@
require 'browser_test_helper'
class AdminRoleTest < TestCase
def test_role
def test_role_device
name = "some role #{rand(99_999_999)}"
@browser = browser_instance
@ -125,4 +125,106 @@ class AdminRoleTest < TestCase
)
end
def test_role_admin_user
@browser = browser_instance
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
# check if admin exists
exists_not(css: '[href="#manage"]')
logout()
# add admin.user to agent role
login(
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
role_edit(
data: {
name: 'Agent',
active: true,
permission: {
'admin.user' => true,
'chat.agent' => true,
'cti.agent' => true,
'ticket.agent' => true,
'user_preferences' => true,
},
}
)
logout()
# check if admin exists
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
# create user
random = rand(999_999_999)
user_email = "admin.user.#{rand}@example.com"
user_create(
data: {
#login: "some login #{random}",
firstname: "Admin.User Firstname #{random}",
lastname: "Admin.User Lastname #{random}",
email: user_email,
password: 'some-pass',
},
)
# create ticket for user
ticket_create(
data: {
customer: user_email,
group: 'Users',
title: 'some changes',
body: 'some body 123äöü - admin.user',
},
)
# revoke admin.user
logout()
login(
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all()
role_edit(
data: {
name: 'Agent',
active: true,
permission: {
'admin.user' => false,
'chat.agent' => true,
'cti.agent' => true,
'ticket.agent' => true,
'user_preferences' => true,
},
}
)
logout()
login(
username: 'agent1@example.com',
password: 'test',
url: browser_url,
)
# check if admin exists
exists_not(css: '[href="#manage"]')
end
end

View file

@ -109,9 +109,9 @@ class TranslationTest < TestCase
)
@browser.action.key_down(:control)
.key_down(:alt)
.key_down(:shift)
.send_keys('t')
.key_up(:alt)
.key_up(:shift)
.key_up(:control)
.perform
@ -128,9 +128,9 @@ class TranslationTest < TestCase
sleep 5
@browser.action.key_down(:control)
.key_down(:alt)
.key_down(:shift)
.send_keys('t')
.key_up(:alt)
.key_up(:shift)
.key_up(:control)
.perform
@ -194,9 +194,9 @@ class TranslationTest < TestCase
sleep 5
@browser.action.key_down(:control)
.key_down(:alt)
.key_down(:shift)
.send_keys('t')
.key_up(:alt)
.key_up(:shift)
.key_up(:control)
.perform
@ -213,9 +213,9 @@ class TranslationTest < TestCase
sleep 5
@browser.action.key_down(:control)
.key_down(:alt)
.key_down(:shift)
.send_keys('t')
.key_up(:alt)
.key_up(:shift)
.key_up(:control)
.perform

View file

@ -1364,9 +1364,9 @@ wait untill text in selector disabppears
instance = params[:browser] || @browser
screenshot(browser: instance, comment: 'shortcut_before')
instance.action.key_down(:control)
.key_down(:alt)
.key_down(:shift)
.send_keys(params[:key])
.key_up(:alt)
.key_up(:shift)
.key_up(:control)
.perform
screenshot(browser: instance, comment: 'shortcut_after')
@ -2944,10 +2944,10 @@ wait untill text in selector disabppears
data: {
name: 'some role' + random,
default_at_signup: false,
permission: [
'admin.group',
'preferences.password',
],
permission: {
'admin.group' => true,
'preferences.password' => true,
},
member: [
'some_user_login',
],
@ -2994,11 +2994,18 @@ wait untill text in selector disabppears
end
if data.key?(:permission)
data[:permission].each { |permission_name|
check(
browser: instance,
css: ".modal [data-permission-name=\"#{permission_name}\"]",
)
data[:permission].each { |permission_name, permission_value|
if permission_value == false
uncheck(
browser: instance,
css: ".modal [data-permission-name=\"#{permission_name}\"]",
)
else
check(
browser: instance,
css: ".modal [data-permission-name=\"#{permission_name}\"]",
)
end
}
end
@ -3046,10 +3053,10 @@ wait untill text in selector disabppears
data: {
name: 'some role' + random,
default_at_signup: false,
permission: [
'admin.group',
'preferences.password',
],
permission: {
'admin.group' => true,
'preferences.password' => true,
},
member: [
'some_user_login',
],
@ -3093,11 +3100,18 @@ wait untill text in selector disabppears
end
if data.key?(:permission)
data[:permission].each { |permission_name|
check(
browser: instance,
css: ".modal [data-permission-name=\"#{permission_name}\"]",
)
data[:permission].each { |permission_name, permission_value|
if permission_value == false
uncheck(
browser: instance,
css: ".modal [data-permission-name=\"#{permission_name}\"]",
)
else
check(
browser: instance,
css: ".modal [data-permission-name=\"#{permission_name}\"]",
)
end
}
end