Feature #34549 » 0001-Add-keyboard-shortcuts-for-bold-italic-and-underline_v2.patch
| lib/redmine/platform.rb | ||
|---|---|---|
| 24 | 24 |
(/(:?mswin|mingw)/.match?(RUBY_PLATFORM)) || |
| 25 | 25 |
(RUBY_PLATFORM == 'java' && /windows/i.match?(ENV['OS'] || ENV['os'])) |
| 26 | 26 |
end |
| 27 | ||
| 28 |
def osx? |
|
| 29 |
(/(:?darwin)/.match?(RUBY_PLATFORM)) |
|
| 30 |
end |
|
| 27 | 31 |
end |
| 28 | 32 |
end |
| 29 | 33 |
end |
| public/javascripts/jstoolbar/jstoolbar.js | ||
|---|---|---|
| 22 | 22 | |
| 23 | 23 |
/* Modified by JP LANG for textile formatting */ |
| 24 | 24 |
let lastJstPreviewed = null; |
| 25 |
const isMac = Boolean(navigator.platform.toLowerCase().match(/mac/)); |
|
| 25 | 26 | |
| 26 | 27 |
function jsToolBar(textarea) {
|
| 27 | 28 |
if (!document.createElement) { return; }
|
| ... | ... | |
| 208 | 209 |
mode: 'wiki', |
| 209 | 210 |
elements: {},
|
| 210 | 211 |
help_link: '', |
| 212 |
shortcuts: {},
|
|
| 211 | 213 | |
| 212 | 214 |
getMode: function() {
|
| 213 | 215 |
return this.mode; |
| ... | ... | |
| 233 | 235 |
button: function(toolName) {
|
| 234 | 236 |
var tool = this.elements[toolName]; |
| 235 | 237 |
if (typeof tool.fn[this.mode] != 'function') return null; |
| 236 |
var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName); |
|
| 238 | ||
| 239 |
const className = 'jstb_' + toolName; |
|
| 240 |
let title = tool.title |
|
| 241 | ||
| 242 |
if (tool.hasOwnProperty('shortcut')) {
|
|
| 243 |
this.shortcuts[tool.shortcut] = className; |
|
| 244 |
title = this.buttonTitleWithShortcut(tool.title, tool.shortcut) |
|
| 245 |
} |
|
| 246 | ||
| 247 |
var b = new jsButton(title, tool.fn[this.mode], this, className); |
|
| 237 | 248 |
if (tool.icon != undefined) b.icon = tool.icon; |
| 249 | ||
| 238 | 250 |
return b; |
| 239 | 251 |
}, |
| 252 |
buttonTitleWithShortcut: function(title, shortcutKey) {
|
|
| 253 |
if (isMac) {
|
|
| 254 |
return title + " (⌘" + shortcutKey.toUpperCase() + ")"; |
|
| 255 |
} else {
|
|
| 256 |
return title + " (Ctrl+" + shortcutKey.toUpperCase() + ")"; |
|
| 257 |
} |
|
| 258 |
}, |
|
| 240 | 259 |
space: function(toolName) {
|
| 241 | 260 |
var tool = new jsSpace(toolName) |
| 242 | 261 |
if (this.elements[toolName].width !== undefined) |
| ... | ... | |
| 409 | 428 |
this.toolbar.classList.add('hidden');
|
| 410 | 429 |
this.textarea.classList.add('hidden');
|
| 411 | 430 |
this.preview.classList.remove('hidden');
|
| 412 |
this.tabsBlock.getElementsByClassName('tab-edit')[0].classList.remove('selected');
|
|
| 431 |
this.tabsBlock.querySelector('.tab-edit').classList.remove('selected');
|
|
| 413 | 432 |
event.target.classList.add('selected');
|
| 414 | 433 |
}, |
| 415 | 434 |
hidePreview: function(event) {
|
| ... | ... | |
| 418 | 437 |
this.textarea.classList.remove('hidden');
|
| 419 | 438 |
this.textarea.focus(); |
| 420 | 439 |
this.preview.classList.add('hidden');
|
| 421 |
this.tabsBlock.getElementsByClassName('tab-preview')[0].classList.remove('selected');
|
|
| 440 |
this.tabsBlock.querySelector('.tab-preview').classList.remove('selected');
|
|
| 422 | 441 |
event.target.classList.add('selected');
|
| 423 | 442 |
}, |
| 424 | 443 |
keyboardShortcuts: function(e) {
|
| 444 |
let stop = false; |
|
| 425 | 445 |
if (isToogleEditPreviewShortcut(e)) {
|
| 426 |
// Switch to preview only if tab edit is selected when the event triggered.
|
|
| 446 |
// Switch to preview only if Edit tab is selected when the event triggers.
|
|
| 427 | 447 |
if (this.tabsBlock.querySelector('.tab-edit.selected')) {
|
| 428 |
e.stopPropagation(); |
|
| 429 |
e.preventDefault(); |
|
| 430 |
this.tabsBlock.getElementsByClassName('tab-preview')[0].click();
|
|
| 448 |
stop = true |
|
| 449 |
this.tabsBlock.querySelector('.tab-preview').click();
|
|
| 431 | 450 |
} |
| 432 | 451 |
} |
| 452 |
if (isModifierKey(e) && this.shortcuts.hasOwnProperty(e.key.toLowerCase())) {
|
|
| 453 |
stop = true |
|
| 454 |
this.toolbar.querySelector("." + this.shortcuts[e.key.toLowerCase()]).click();
|
|
| 455 |
} |
|
| 456 |
if (stop) {
|
|
| 457 |
e.stopPropagation(); |
|
| 458 |
e.preventDefault(); |
|
| 459 |
} |
|
| 433 | 460 |
}, |
| 434 | 461 |
stripBaseURL: function(url) {
|
| 435 | 462 |
if (this.base_url != '') {
|
| ... | ... | |
| 539 | 566 |
} else {
|
| 540 | 567 |
return false; |
| 541 | 568 |
} |
| 569 |
} |
|
| 570 |
function isModifierKey(e) {
|
|
| 571 |
if (isMac && e.metaKey) {
|
|
| 572 |
return true; |
|
| 573 |
} else if (!isMac && e.ctrlKey) {
|
|
| 574 |
return true; |
|
| 575 |
} else {
|
|
| 576 |
return false; |
|
| 577 |
} |
|
| 542 | 578 |
} |
| public/javascripts/jstoolbar/markdown.js | ||
|---|---|---|
| 26 | 26 |
jsToolBar.prototype.elements.strong = {
|
| 27 | 27 |
type: 'button', |
| 28 | 28 |
title: 'Strong', |
| 29 |
shortcut: 'b', |
|
| 29 | 30 |
fn: {
|
| 30 | 31 |
wiki: function() { this.singleTag('**') }
|
| 31 | 32 |
} |
| ... | ... | |
| 35 | 36 |
jsToolBar.prototype.elements.em = {
|
| 36 | 37 |
type: 'button', |
| 37 | 38 |
title: 'Italic', |
| 39 |
shortcut: 'i', |
|
| 38 | 40 |
fn: {
|
| 39 | 41 |
wiki: function() { this.singleTag("*") }
|
| 40 | 42 |
} |
| ... | ... | |
| 44 | 46 |
jsToolBar.prototype.elements.ins = {
|
| 45 | 47 |
type: 'button', |
| 46 | 48 |
title: 'Underline', |
| 49 |
shortcut: 'u', |
|
| 47 | 50 |
fn: {
|
| 48 | 51 |
wiki: function() { this.singleTag('_') }
|
| 49 | 52 |
} |
| public/javascripts/jstoolbar/textile.js | ||
|---|---|---|
| 26 | 26 |
jsToolBar.prototype.elements.strong = {
|
| 27 | 27 |
type: 'button', |
| 28 | 28 |
title: 'Strong', |
| 29 |
shortcut: 'b', |
|
| 29 | 30 |
fn: {
|
| 30 | 31 |
wiki: function() { this.singleTag('*') }
|
| 31 | 32 |
} |
| ... | ... | |
| 35 | 36 |
jsToolBar.prototype.elements.em = {
|
| 36 | 37 |
type: 'button', |
| 37 | 38 |
title: 'Italic', |
| 39 |
shortcut: 'i', |
|
| 38 | 40 |
fn: {
|
| 39 | 41 |
wiki: function() { this.singleTag("_") }
|
| 40 | 42 |
} |
| ... | ... | |
| 44 | 46 |
jsToolBar.prototype.elements.ins = {
|
| 45 | 47 |
type: 'button', |
| 46 | 48 |
title: 'Underline', |
| 49 |
shortcut: 'u', |
|
| 47 | 50 |
fn: {
|
| 48 | 51 |
wiki: function() { this.singleTag('+') }
|
| 49 | 52 |
} |
| test/system/keyboard_shortcuts_test.rb | ||
|---|---|---|
| 68 | 68 |
find 'textarea#issue_notes', :visible => true |
| 69 | 69 |
find 'div#preview_issue_notes', :visible => false |
| 70 | 70 |
end |
| 71 | ||
| 72 |
def test_keyboard_shortcuts_for_wiki_toolbar_buttons_using_textile |
|
| 73 |
with_settings :text_formatting => 'textile' do |
|
| 74 |
log_user('jsmith', 'jsmith')
|
|
| 75 |
visit 'issues/new' |
|
| 76 | ||
| 77 |
find('#issue_description').click.send_keys([modifier_key, 'b'])
|
|
| 78 |
assert_equal '**', find('#issue_description').value
|
|
| 79 | ||
| 80 |
# Clear textarea value |
|
| 81 |
fill_in 'Description', :with => '' |
|
| 82 |
find('#issue_description').send_keys([modifier_key, 'u'])
|
|
| 83 |
assert_equal '++', find('#issue_description').value
|
|
| 84 | ||
| 85 |
# Clear textarea value |
|
| 86 |
fill_in 'Description', :with => '' |
|
| 87 |
find('#issue_description').send_keys([modifier_key, 'i'])
|
|
| 88 |
assert_equal '__', find('#issue_description').value
|
|
| 89 |
end |
|
| 90 |
end |
|
| 91 | ||
| 92 |
def test_keyboard_shortcuts_for_wiki_toolbar_buttons_using_markdown |
|
| 93 |
with_settings :text_formatting => 'markdown' do |
|
| 94 |
log_user('jsmith', 'jsmith')
|
|
| 95 |
visit 'issues/new' |
|
| 96 | ||
| 97 |
find('#issue_description').click.send_keys([modifier_key, 'b'])
|
|
| 98 |
assert_equal '****', find('#issue_description').value
|
|
| 99 | ||
| 100 |
# Clear textarea value |
|
| 101 |
fill_in 'Description', :with => '' |
|
| 102 |
find('#issue_description').send_keys([modifier_key, 'u'])
|
|
| 103 |
assert_equal '__', find('#issue_description').value
|
|
| 104 | ||
| 105 |
# Clear textarea value |
|
| 106 |
fill_in 'Description', :with => '' |
|
| 107 |
find('#issue_description').send_keys([modifier_key, 'i'])
|
|
| 108 |
assert_equal '**', find('#issue_description').value
|
|
| 109 |
end |
|
| 110 |
end |
|
| 111 | ||
| 112 |
def test_keyboard_shortcuts_keys_should_be_shown_in_button_title |
|
| 113 |
log_user('jsmith', 'jsmith')
|
|
| 114 |
visit 'issues/new' |
|
| 115 | ||
| 116 |
within('.jstBlock .jstElements') do
|
|
| 117 |
assert_equal "Strong (#{modifier_key_title}B)", find('button.jstb_strong')['title']
|
|
| 118 |
assert_equal "Italic (#{modifier_key_title}I)", find('button.jstb_em')['title']
|
|
| 119 |
assert_equal "Underline (#{modifier_key_title}U)", find('button.jstb_ins')['title']
|
|
| 120 |
end |
|
| 121 |
end |
|
| 122 | ||
| 123 |
private |
|
| 124 | ||
| 125 |
def modifier_key |
|
| 126 |
modifier = osx? ? "command" : "control" |
|
| 127 |
modifier.to_sym |
|
| 128 |
end |
|
| 129 | ||
| 130 |
def modifier_key_title |
|
| 131 |
osx? ? "⌘" : "Ctrl+" |
|
| 132 |
end |
|
| 71 | 133 |
end |