mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 23:46:14 -04:00
Select formatting button on selection (#3301)
[feat] Select button when selection is on formatted text
This commit is contained in:
parent
f1fcd16894
commit
454f539561
3 changed files with 206 additions and 12 deletions
|
@ -400,7 +400,19 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
||||||
this.removeAttributeOnLine(lineNum, attributeName) :
|
this.removeAttributeOnLine(lineNum, attributeName) :
|
||||||
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
hasAttributeOnSelectionOrCaretPosition: function(attributeName) {
|
||||||
|
var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
|
||||||
|
var hasAttrib;
|
||||||
|
if (hasSelection) {
|
||||||
|
hasAttrib = this.getAttributeOnSelection(attributeName);
|
||||||
|
}else {
|
||||||
|
var attributesOnCaretPosition = this.getAttributesOnCaret();
|
||||||
|
hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName);
|
||||||
}
|
}
|
||||||
|
return hasAttrib;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = AttributeManager;
|
module.exports = AttributeManager;
|
||||||
|
|
|
@ -75,6 +75,9 @@ function Ace2Inner(){
|
||||||
var EDIT_BODY_PADDING_TOP = 8;
|
var EDIT_BODY_PADDING_TOP = 8;
|
||||||
var EDIT_BODY_PADDING_LEFT = 8;
|
var EDIT_BODY_PADDING_LEFT = 8;
|
||||||
|
|
||||||
|
var FORMATTING_STYLES = ['bold', 'italic', 'underline', 'strikethrough'];
|
||||||
|
var SELECT_BUTTON_CLASS = 'selected';
|
||||||
|
|
||||||
var caughtErrors = [];
|
var caughtErrors = [];
|
||||||
|
|
||||||
var thisAuthor = '';
|
var thisAuthor = '';
|
||||||
|
@ -2472,17 +2475,11 @@ function Ace2Inner(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionAllHasIt)
|
|
||||||
{
|
var attributeValue = selectionAllHasIt ? '' : 'true';
|
||||||
documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
|
documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [[attributeName, attributeValue]]);
|
||||||
[attributeName, '']
|
if (attribIsFormattingStyle(attributeName)) {
|
||||||
]);
|
updateStyleButtonState(attributeName, !selectionAllHasIt); // italic, bold, ...
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
|
|
||||||
[attributeName, 'true']
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editorInfo.ace_toggleAttributeOnSelection = toggleAttributeOnSelection;
|
editorInfo.ace_toggleAttributeOnSelection = toggleAttributeOnSelection;
|
||||||
|
@ -2911,6 +2908,9 @@ function Ace2Inner(){
|
||||||
rep.selFocusAtStart = newSelFocusAtStart;
|
rep.selFocusAtStart = newSelFocusAtStart;
|
||||||
currentCallStack.repChanged = true;
|
currentCallStack.repChanged = true;
|
||||||
|
|
||||||
|
// select the formatting buttons when there is the style applied on selection
|
||||||
|
selectFormattingButtonIfLineHasStyleApplied(rep);
|
||||||
|
|
||||||
hooks.callAll('aceSelectionChanged', {
|
hooks.callAll('aceSelectionChanged', {
|
||||||
rep: rep,
|
rep: rep,
|
||||||
callstack: currentCallStack,
|
callstack: currentCallStack,
|
||||||
|
@ -2939,6 +2939,22 @@ function Ace2Inner(){
|
||||||
return (eventType === 'setup') || (eventType === 'setBaseText') || (eventType === 'importText');
|
return (eventType === 'setup') || (eventType === 'setBaseText') || (eventType === 'importText');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateStyleButtonState(attribName, hasStyleOnRepSelection) {
|
||||||
|
var $formattingButton = parent.parent.$('[data-key="' + attribName + '"]').find('a');
|
||||||
|
$formattingButton.toggleClass(SELECT_BUTTON_CLASS, hasStyleOnRepSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function attribIsFormattingStyle(attributeName) {
|
||||||
|
return _.contains(FORMATTING_STYLES, attributeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectFormattingButtonIfLineHasStyleApplied (rep) {
|
||||||
|
_.each(FORMATTING_STYLES, function (style) {
|
||||||
|
var hasStyleOnRepSelection = documentAttributeManager.hasAttributeOnSelectionOrCaretPosition(style);
|
||||||
|
updateStyleButtonState(style, hasStyleOnRepSelection);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function doCreateDomLine(nonEmpty)
|
function doCreateDomLine(nonEmpty)
|
||||||
{
|
{
|
||||||
if (browser.msie && (!nonEmpty))
|
if (browser.msie && (!nonEmpty))
|
||||||
|
|
166
tests/frontend/specs/select_formatting_buttons.js
Normal file
166
tests/frontend/specs/select_formatting_buttons.js
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
describe("select formatting buttons when selection has style applied", function(){
|
||||||
|
var STYLES = ['italic', 'bold', 'underline', 'strikethrough'];
|
||||||
|
var SHORTCUT_KEYS = ['I', 'B', 'U', '5']; // italic, bold, underline, strikethrough
|
||||||
|
var FIRST_LINE = 0;
|
||||||
|
|
||||||
|
before(function(cb){
|
||||||
|
helper.newPad(cb);
|
||||||
|
this.timeout(60000);
|
||||||
|
});
|
||||||
|
|
||||||
|
var applyStyleOnLine = function(style, line) {
|
||||||
|
var chrome$ = helper.padChrome$;
|
||||||
|
selectLine(line);
|
||||||
|
var $formattingButton = chrome$('.buttonicon-' + style);
|
||||||
|
$formattingButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
var isButtonSelected = function(style) {
|
||||||
|
var chrome$ = helper.padChrome$;
|
||||||
|
var $formattingButton = chrome$('.buttonicon-' + style);
|
||||||
|
return $formattingButton.parent().hasClass('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectLine = function(lineNumber, offsetStart, offsetEnd) {
|
||||||
|
var inner$ = helper.padInner$;
|
||||||
|
var $line = inner$("div").eq(lineNumber);
|
||||||
|
helper.selectLines($line, $line, offsetStart, offsetEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
var placeCaretOnLine = function(lineNumber) {
|
||||||
|
var inner$ = helper.padInner$;
|
||||||
|
var $line = inner$("div").eq(lineNumber);
|
||||||
|
$line.sendkeys('{leftarrow}');
|
||||||
|
}
|
||||||
|
|
||||||
|
var undo = function() {
|
||||||
|
var $undoButton = helper.padChrome$(".buttonicon-undo");
|
||||||
|
$undoButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
var testIfFormattingButtonIsDeselected = function(style) {
|
||||||
|
it('deselects the ' + style + ' button', function(done) {
|
||||||
|
helper.waitFor(function(){
|
||||||
|
return isButtonSelected(style) === false;
|
||||||
|
}).done(done)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var testIfFormattingButtonIsSelected = function(style) {
|
||||||
|
it('selects the ' + style + ' button', function(done) {
|
||||||
|
helper.waitFor(function(){
|
||||||
|
return isButtonSelected(style);
|
||||||
|
}).done(done)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyStyleOnLineAndSelectIt = function(line, style, cb) {
|
||||||
|
applyStyleOnLineOnFullLineAndRemoveSelection(line, style, selectLine, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyStyleOnLineAndPlaceCaretOnit = function(line, style, cb) {
|
||||||
|
applyStyleOnLineOnFullLineAndRemoveSelection(line, style, placeCaretOnLine, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
var applyStyleOnLineOnFullLineAndRemoveSelection = function(line, style, selectTarget, cb) {
|
||||||
|
applyStyleOnLine(style, line);
|
||||||
|
|
||||||
|
// we have to give some time to Etherpad detects the selection changed
|
||||||
|
setTimeout(function() {
|
||||||
|
// remove selection from previous line
|
||||||
|
selectLine(line + 1);
|
||||||
|
setTimeout(function() {
|
||||||
|
// select the text or place the caret on a position that
|
||||||
|
// has the formatting text applied previously
|
||||||
|
selectTarget(line);
|
||||||
|
cb();
|
||||||
|
}, 1000);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pressFormattingShortcutOnSelection = function(key) {
|
||||||
|
var inner$ = helper.padInner$;
|
||||||
|
var chrome$ = helper.padChrome$;
|
||||||
|
|
||||||
|
//get the first text element out of the inner iframe
|
||||||
|
var $firstTextElement = inner$("div").first();
|
||||||
|
|
||||||
|
//select this text element
|
||||||
|
$firstTextElement.sendkeys('{selectall}');
|
||||||
|
|
||||||
|
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
|
||||||
|
var evtType = "keypress";
|
||||||
|
}else{
|
||||||
|
var evtType = "keydown";
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = inner$.Event(evtType);
|
||||||
|
e.ctrlKey = true; // Control key
|
||||||
|
e.which = key.charCodeAt(0); // I, U, B, 5
|
||||||
|
inner$("#innerdocbody").trigger(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
STYLES.forEach(function(style){
|
||||||
|
context('when selection is in a text with ' + style + ' applied', function(){
|
||||||
|
before(function (done) {
|
||||||
|
this.timeout(4000);
|
||||||
|
applyStyleOnLineAndSelectIt(FIRST_LINE, style, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
undo();
|
||||||
|
});
|
||||||
|
|
||||||
|
testIfFormattingButtonIsSelected(style);
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when caret is in a position with ' + style + ' applied', function(){
|
||||||
|
before(function (done) {
|
||||||
|
this.timeout(4000);
|
||||||
|
applyStyleOnLineAndPlaceCaretOnit(FIRST_LINE, style, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
undo();
|
||||||
|
});
|
||||||
|
|
||||||
|
testIfFormattingButtonIsSelected(style)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when user applies a style and the selection does not change', function() {
|
||||||
|
var style = STYLES[0]; // italic
|
||||||
|
before(function () {
|
||||||
|
applyStyleOnLine(style, FIRST_LINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
// clean the style applied
|
||||||
|
after(function () {
|
||||||
|
applyStyleOnLine(style, FIRST_LINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('selects the style button', function (done) {
|
||||||
|
expect(isButtonSelected(style)).to.be(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
SHORTCUT_KEYS.forEach(function(key, index){
|
||||||
|
var styleOfTheShortcut = STYLES[index]; // italic, bold, ...
|
||||||
|
context('when user presses CMD + ' + key, function() {
|
||||||
|
before(function () {
|
||||||
|
pressFormattingShortcutOnSelection(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
testIfFormattingButtonIsSelected(styleOfTheShortcut);
|
||||||
|
|
||||||
|
context('and user presses CMD + ' + key + ' again', function() {
|
||||||
|
before(function () {
|
||||||
|
pressFormattingShortcutOnSelection(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
testIfFormattingButtonIsDeselected(styleOfTheShortcut);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue