Add keyboard shortcuts for bold, italic and underline buttons (#34549).

Patch by Marius BALTEANU.


git-svn-id: http://svn.redmine.org/redmine/trunk@20729 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Go MAEDA
2021-01-26 06:50:34 +00:00
parent 876d6fd628
commit 082570094a
5 changed files with 115 additions and 7 deletions

View File

@@ -24,6 +24,10 @@ module Redmine
(/(:?mswin|mingw)/.match?(RUBY_PLATFORM)) || (/(:?mswin|mingw)/.match?(RUBY_PLATFORM)) ||
(RUBY_PLATFORM == 'java' && /windows/i.match?(ENV['OS'] || ENV['os'])) (RUBY_PLATFORM == 'java' && /windows/i.match?(ENV['OS'] || ENV['os']))
end end
def osx?
(/(:?darwin)/.match?(RUBY_PLATFORM))
end
end end
end end
end end

View File

@@ -22,6 +22,7 @@
/* Modified by JP LANG for textile formatting */ /* Modified by JP LANG for textile formatting */
let lastJstPreviewed = null; let lastJstPreviewed = null;
const isMac = Boolean(navigator.platform.toLowerCase().match(/mac/));
function jsToolBar(textarea) { function jsToolBar(textarea) {
if (!document.createElement) { return; } if (!document.createElement) { return; }
@@ -208,6 +209,7 @@ jsToolBar.prototype = {
mode: 'wiki', mode: 'wiki',
elements: {}, elements: {},
help_link: '', help_link: '',
shortcuts: {},
getMode: function() { getMode: function() {
return this.mode; return this.mode;
@@ -233,10 +235,27 @@ jsToolBar.prototype = {
button: function(toolName) { button: function(toolName) {
var tool = this.elements[toolName]; var tool = this.elements[toolName];
if (typeof tool.fn[this.mode] != 'function') return null; if (typeof tool.fn[this.mode] != 'function') return null;
var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName);
const className = 'jstb_' + toolName;
let title = tool.title
if (tool.hasOwnProperty('shortcut')) {
this.shortcuts[tool.shortcut] = className;
title = this.buttonTitleWithShortcut(tool.title, tool.shortcut)
}
var b = new jsButton(title, tool.fn[this.mode], this, className);
if (tool.icon != undefined) b.icon = tool.icon; if (tool.icon != undefined) b.icon = tool.icon;
return b; return b;
}, },
buttonTitleWithShortcut: function(title, shortcutKey) {
if (isMac) {
return title + " (⌘" + shortcutKey.toUpperCase() + ")";
} else {
return title + " (Ctrl+" + shortcutKey.toUpperCase() + ")";
}
},
space: function(toolName) { space: function(toolName) {
var tool = new jsSpace(toolName) var tool = new jsSpace(toolName)
if (this.elements[toolName].width !== undefined) if (this.elements[toolName].width !== undefined)
@@ -409,7 +428,7 @@ jsToolBar.prototype = {
this.toolbar.classList.add('hidden'); this.toolbar.classList.add('hidden');
this.textarea.classList.add('hidden'); this.textarea.classList.add('hidden');
this.preview.classList.remove('hidden'); this.preview.classList.remove('hidden');
this.tabsBlock.getElementsByClassName('tab-edit')[0].classList.remove('selected'); this.tabsBlock.querySelector('.tab-edit').classList.remove('selected');
event.target.classList.add('selected'); event.target.classList.add('selected');
}, },
hidePreview: function(event) { hidePreview: function(event) {
@@ -418,18 +437,26 @@ jsToolBar.prototype = {
this.textarea.classList.remove('hidden'); this.textarea.classList.remove('hidden');
this.textarea.focus(); this.textarea.focus();
this.preview.classList.add('hidden'); this.preview.classList.add('hidden');
this.tabsBlock.getElementsByClassName('tab-preview')[0].classList.remove('selected'); this.tabsBlock.querySelector('.tab-preview').classList.remove('selected');
event.target.classList.add('selected'); event.target.classList.add('selected');
}, },
keyboardShortcuts: function(e) { keyboardShortcuts: function(e) {
let stop = false;
if (isToogleEditPreviewShortcut(e)) { if (isToogleEditPreviewShortcut(e)) {
// Switch to preview only if tab edit is selected when the event triggered. // Switch to preview only if Edit tab is selected when the event triggers.
if (this.tabsBlock.querySelector('.tab-edit.selected')) { if (this.tabsBlock.querySelector('.tab-edit.selected')) {
e.stopPropagation(); stop = true
e.preventDefault(); this.tabsBlock.querySelector('.tab-preview').click();
this.tabsBlock.getElementsByClassName('tab-preview')[0].click();
} }
} }
if (isModifierKey(e) && this.shortcuts.hasOwnProperty(e.key.toLowerCase())) {
stop = true
this.toolbar.querySelector("." + this.shortcuts[e.key.toLowerCase()]).click();
}
if (stop) {
e.stopPropagation();
e.preventDefault();
}
}, },
stripBaseURL: function(url) { stripBaseURL: function(url) {
if (this.base_url != '') { if (this.base_url != '') {
@@ -540,3 +567,12 @@ function isToogleEditPreviewShortcut(e) {
return false; return false;
} }
} }
function isModifierKey(e) {
if (isMac && e.metaKey) {
return true;
} else if (!isMac && e.ctrlKey) {
return true;
} else {
return false;
}
}

View File

@@ -26,6 +26,7 @@
jsToolBar.prototype.elements.strong = { jsToolBar.prototype.elements.strong = {
type: 'button', type: 'button',
title: 'Strong', title: 'Strong',
shortcut: 'b',
fn: { fn: {
wiki: function() { this.singleTag('**') } wiki: function() { this.singleTag('**') }
} }
@@ -35,6 +36,7 @@ jsToolBar.prototype.elements.strong = {
jsToolBar.prototype.elements.em = { jsToolBar.prototype.elements.em = {
type: 'button', type: 'button',
title: 'Italic', title: 'Italic',
shortcut: 'i',
fn: { fn: {
wiki: function() { this.singleTag("*") } wiki: function() { this.singleTag("*") }
} }
@@ -44,6 +46,7 @@ jsToolBar.prototype.elements.em = {
jsToolBar.prototype.elements.ins = { jsToolBar.prototype.elements.ins = {
type: 'button', type: 'button',
title: 'Underline', title: 'Underline',
shortcut: 'u',
fn: { fn: {
wiki: function() { this.singleTag('_') } wiki: function() { this.singleTag('_') }
} }

View File

@@ -26,6 +26,7 @@
jsToolBar.prototype.elements.strong = { jsToolBar.prototype.elements.strong = {
type: 'button', type: 'button',
title: 'Strong', title: 'Strong',
shortcut: 'b',
fn: { fn: {
wiki: function() { this.singleTag('*') } wiki: function() { this.singleTag('*') }
} }
@@ -35,6 +36,7 @@ jsToolBar.prototype.elements.strong = {
jsToolBar.prototype.elements.em = { jsToolBar.prototype.elements.em = {
type: 'button', type: 'button',
title: 'Italic', title: 'Italic',
shortcut: 'i',
fn: { fn: {
wiki: function() { this.singleTag("_") } wiki: function() { this.singleTag("_") }
} }
@@ -44,6 +46,7 @@ jsToolBar.prototype.elements.em = {
jsToolBar.prototype.elements.ins = { jsToolBar.prototype.elements.ins = {
type: 'button', type: 'button',
title: 'Underline', title: 'Underline',
shortcut: 'u',
fn: { fn: {
wiki: function() { this.singleTag('+') } wiki: function() { this.singleTag('+') }
} }

View File

@@ -68,4 +68,66 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
find 'textarea#issue_notes', :visible => true find 'textarea#issue_notes', :visible => true
find 'div#preview_issue_notes', :visible => false find 'div#preview_issue_notes', :visible => false
end end
def test_keyboard_shortcuts_for_wiki_toolbar_buttons_using_textile
with_settings :text_formatting => 'textile' do
log_user('jsmith', 'jsmith')
visit 'issues/new'
find('#issue_description').click.send_keys([modifier_key, 'b'])
assert_equal '**', find('#issue_description').value
# Clear textarea value
fill_in 'Description', :with => ''
find('#issue_description').send_keys([modifier_key, 'u'])
assert_equal '++', find('#issue_description').value
# Clear textarea value
fill_in 'Description', :with => ''
find('#issue_description').send_keys([modifier_key, 'i'])
assert_equal '__', find('#issue_description').value
end
end
def test_keyboard_shortcuts_for_wiki_toolbar_buttons_using_markdown
with_settings :text_formatting => 'markdown' do
log_user('jsmith', 'jsmith')
visit 'issues/new'
find('#issue_description').click.send_keys([modifier_key, 'b'])
assert_equal '****', find('#issue_description').value
# Clear textarea value
fill_in 'Description', :with => ''
find('#issue_description').send_keys([modifier_key, 'u'])
assert_equal '__', find('#issue_description').value
# Clear textarea value
fill_in 'Description', :with => ''
find('#issue_description').send_keys([modifier_key, 'i'])
assert_equal '**', find('#issue_description').value
end
end
def test_keyboard_shortcuts_keys_should_be_shown_in_button_title
log_user('jsmith', 'jsmith')
visit 'issues/new'
within('.jstBlock .jstElements') do
assert_equal "Strong (#{modifier_key_title}B)", find('button.jstb_strong')['title']
assert_equal "Italic (#{modifier_key_title}I)", find('button.jstb_em')['title']
assert_equal "Underline (#{modifier_key_title}U)", find('button.jstb_ins')['title']
end
end
private
def modifier_key
modifier = osx? ? "command" : "control"
modifier.to_sym
end
def modifier_key_title
osx? ? "" : "Ctrl+"
end
end end