mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 23:46:14 -04:00
pad_editbar: Move syncAnimation
out of padeditbar
IIFE
This avoids the need for an IIFE.
This commit is contained in:
parent
ee996f530f
commit
42b0b1bf00
1 changed files with 395 additions and 400 deletions
|
@ -77,411 +77,406 @@ ToolbarItem.prototype.bind = function (callback) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const syncAnimation = (() => {
|
||||||
const padeditbar = (() => {
|
const SYNCING = -100;
|
||||||
const syncAnimation = (() => {
|
const DONE = 100;
|
||||||
const SYNCING = -100;
|
let state = DONE;
|
||||||
const DONE = 100;
|
const fps = 25;
|
||||||
let state = DONE;
|
const step = 1 / fps;
|
||||||
const fps = 25;
|
const T_START = -0.5;
|
||||||
const step = 1 / fps;
|
const T_FADE = 1.0;
|
||||||
const T_START = -0.5;
|
const T_GONE = 1.5;
|
||||||
const T_FADE = 1.0;
|
const animator = padutils.makeAnimationScheduler(() => {
|
||||||
const T_GONE = 1.5;
|
if (state === SYNCING || state === DONE) {
|
||||||
const animator = padutils.makeAnimationScheduler(() => {
|
return false;
|
||||||
if (state === SYNCING || state === DONE) {
|
} else if (state >= T_GONE) {
|
||||||
return false;
|
state = DONE;
|
||||||
} else if (state >= T_GONE) {
|
$('#syncstatussyncing').css('display', 'none');
|
||||||
state = DONE;
|
$('#syncstatusdone').css('display', 'none');
|
||||||
|
return false;
|
||||||
|
} else if (state < 0) {
|
||||||
|
state += step;
|
||||||
|
if (state >= 0) {
|
||||||
$('#syncstatussyncing').css('display', 'none');
|
$('#syncstatussyncing').css('display', 'none');
|
||||||
$('#syncstatusdone').css('display', 'none');
|
$('#syncstatusdone').css('display', 'block').css('opacity', 1);
|
||||||
return false;
|
|
||||||
} else if (state < 0) {
|
|
||||||
state += step;
|
|
||||||
if (state >= 0) {
|
|
||||||
$('#syncstatussyncing').css('display', 'none');
|
|
||||||
$('#syncstatusdone').css('display', 'block').css('opacity', 1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
state += step;
|
|
||||||
if (state >= T_FADE) {
|
|
||||||
$('#syncstatusdone').css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}, step * 1000);
|
return true;
|
||||||
return {
|
} else {
|
||||||
syncing: () => {
|
state += step;
|
||||||
state = SYNCING;
|
if (state >= T_FADE) {
|
||||||
$('#syncstatussyncing').css('display', 'block');
|
$('#syncstatusdone').css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
|
||||||
$('#syncstatusdone').css('display', 'none');
|
}
|
||||||
},
|
return true;
|
||||||
done: () => {
|
}
|
||||||
state = T_START;
|
}, step * 1000);
|
||||||
animator.scheduleAnimation();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_editbarPosition: 0,
|
syncing: () => {
|
||||||
|
state = SYNCING;
|
||||||
init() {
|
$('#syncstatussyncing').css('display', 'block');
|
||||||
this.dropdowns = [];
|
$('#syncstatusdone').css('display', 'none');
|
||||||
|
|
||||||
$('#editbar .editbarbutton').attr('unselectable', 'on'); // for IE
|
|
||||||
this.enable();
|
|
||||||
$('#editbar [data-key]').each((i, elt) => {
|
|
||||||
$(elt).unbind('click');
|
|
||||||
new ToolbarItem($(elt)).bind((command, item) => {
|
|
||||||
this.triggerCommand(command, item);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('body:not(#editorcontainerbox)').on('keydown', (evt) => {
|
|
||||||
this._bodyKeyEvent(evt);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.show-more-icon-btn').click(() => {
|
|
||||||
$('.toolbar').toggleClass('full-icons');
|
|
||||||
});
|
|
||||||
this.checkAllIconsAreDisplayedInToolbar();
|
|
||||||
$(window).resize(_.debounce(() => this.checkAllIconsAreDisplayedInToolbar(), 100));
|
|
||||||
|
|
||||||
this._registerDefaultCommands();
|
|
||||||
|
|
||||||
hooks.callAll('postToolbarInit', {
|
|
||||||
toolbar: this,
|
|
||||||
ace: padeditor.ace,
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* On safari, the dropdown in the toolbar gets hidden because of toolbar
|
|
||||||
* overflow:hidden property. This is a bug from Safari: any children with
|
|
||||||
* position:fixed (like the dropdown) should be displayed no matter
|
|
||||||
* overflow:hidden on parent
|
|
||||||
*/
|
|
||||||
if (!browser.safari) {
|
|
||||||
$('select').niceSelect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// When editor is scrolled, we add a class to style the editbar differently
|
|
||||||
$('iframe[name="ace_outer"]').contents().scroll((ev) => {
|
|
||||||
$('#editbar').toggleClass('editor-scrolled', $(ev.currentTarget).scrollTop() > 2);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
isEnabled: () => true,
|
done: () => {
|
||||||
disable: () => {
|
state = T_START;
|
||||||
$('#editbar').addClass('disabledtoolbar').removeClass('enabledtoolbar');
|
animator.scheduleAnimation();
|
||||||
},
|
|
||||||
enable: () => {
|
|
||||||
$('#editbar').addClass('enabledtoolbar').removeClass('disabledtoolbar');
|
|
||||||
},
|
|
||||||
commands: {},
|
|
||||||
registerCommand(cmd, callback) {
|
|
||||||
this.commands[cmd] = callback;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
registerDropdownCommand(cmd, dropdown) {
|
|
||||||
dropdown = dropdown || cmd;
|
|
||||||
this.dropdowns.push(dropdown);
|
|
||||||
this.registerCommand(cmd, () => {
|
|
||||||
this.toggleDropDown(dropdown);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
registerAceCommand(cmd, callback) {
|
|
||||||
this.registerCommand(cmd, (cmd, ace, item) => {
|
|
||||||
ace.callWithAce((ace) => {
|
|
||||||
callback(cmd, ace, item);
|
|
||||||
}, cmd, true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
triggerCommand(cmd, item) {
|
|
||||||
if (this.isEnabled() && this.commands[cmd]) {
|
|
||||||
this.commands[cmd](cmd, padeditor.ace, item);
|
|
||||||
}
|
|
||||||
if (padeditor.ace) padeditor.ace.focus();
|
|
||||||
},
|
|
||||||
toggleDropDown(moduleName, cb) {
|
|
||||||
// do nothing if users are sticked
|
|
||||||
if (moduleName === 'users' && $('#users').hasClass('stickyUsers')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.nice-select').removeClass('open');
|
|
||||||
$('.toolbar-popup').removeClass('popup-show');
|
|
||||||
|
|
||||||
// hide all modules and remove highlighting of all buttons
|
|
||||||
if (moduleName === 'none') {
|
|
||||||
const returned = false;
|
|
||||||
for (const thisModuleName of this.dropdowns) {
|
|
||||||
// skip the userlist
|
|
||||||
if (thisModuleName === 'users') continue;
|
|
||||||
|
|
||||||
const module = $(`#${thisModuleName}`);
|
|
||||||
|
|
||||||
// skip any "force reconnect" message
|
|
||||||
const isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
|
|
||||||
if (isAForceReconnectMessage) continue;
|
|
||||||
if (module.hasClass('popup-show')) {
|
|
||||||
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
|
|
||||||
module.removeClass('popup-show');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!returned && cb) return cb();
|
|
||||||
} else {
|
|
||||||
// hide all modules that are not selected and remove highlighting
|
|
||||||
// respectively add highlighting to the corresponding button
|
|
||||||
for (const thisModuleName of this.dropdowns) {
|
|
||||||
const module = $(`#${thisModuleName}`);
|
|
||||||
|
|
||||||
if (module.hasClass('popup-show')) {
|
|
||||||
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
|
|
||||||
module.removeClass('popup-show');
|
|
||||||
} else if (thisModuleName === moduleName) {
|
|
||||||
$(`li[data-key=${thisModuleName}] > a`).addClass('selected');
|
|
||||||
module.addClass('popup-show');
|
|
||||||
if (cb) {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setSyncStatus: (status) => {
|
|
||||||
if (status === 'syncing') {
|
|
||||||
syncAnimation.syncing();
|
|
||||||
} else if (status === 'done') {
|
|
||||||
syncAnimation.done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setEmbedLinks: () => {
|
|
||||||
const padUrl = window.location.href.split('?')[0];
|
|
||||||
const params = '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false';
|
|
||||||
const props = 'width="100%" height="600" frameborder="0"';
|
|
||||||
|
|
||||||
if ($('#readonlyinput').is(':checked')) {
|
|
||||||
const urlParts = padUrl.split('/');
|
|
||||||
urlParts.pop();
|
|
||||||
const readonlyLink = `${urlParts.join('/')}/${clientVars.readOnlyId}`;
|
|
||||||
$('#embedinput')
|
|
||||||
.val(`<iframe name="embed_readonly" src="${readonlyLink}${params}" ${props}></iframe>`);
|
|
||||||
$('#linkinput').val(readonlyLink);
|
|
||||||
} else {
|
|
||||||
$('#embedinput')
|
|
||||||
.val(`<iframe name="embed_readwrite" src="${padUrl}${params}" ${props}></iframe>`);
|
|
||||||
$('#linkinput').val(padUrl);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkAllIconsAreDisplayedInToolbar: () => {
|
|
||||||
// reset style
|
|
||||||
$('.toolbar').removeClass('cropped');
|
|
||||||
$('body').removeClass('mobile-layout');
|
|
||||||
const menu_left = $('.toolbar .menu_left')[0];
|
|
||||||
|
|
||||||
// this is approximate, we cannot measure it because on mobile
|
|
||||||
// Layout it takes the full width on the bottom of the page
|
|
||||||
const menuRightWidth = 280;
|
|
||||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth ||
|
|
||||||
$('.toolbar').width() < 1000) {
|
|
||||||
$('body').addClass('mobile-layout');
|
|
||||||
}
|
|
||||||
if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) {
|
|
||||||
$('.toolbar').addClass('cropped');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_bodyKeyEvent(evt) {
|
|
||||||
// If the event is Alt F9 or Escape & we're already in the editbar menu
|
|
||||||
// Send the users focus back to the pad
|
|
||||||
if ((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27) {
|
|
||||||
if ($(':focus').parents('.toolbar').length === 1) {
|
|
||||||
// If we're in the editbar already..
|
|
||||||
// Close any dropdowns we have open..
|
|
||||||
this.toggleDropDown('none');
|
|
||||||
// Check we're on a pad and not on the timeslider
|
|
||||||
// Or some other window I haven't thought about!
|
|
||||||
if (typeof pad === 'undefined') {
|
|
||||||
// Timeslider probably..
|
|
||||||
// Shift focus away from any drop downs
|
|
||||||
$(':focus').blur(); // required to do not try to remove!
|
|
||||||
$('#editorcontainerbox').focus(); // Focus back onto the pad
|
|
||||||
} else {
|
|
||||||
// Shift focus away from any drop downs
|
|
||||||
$(':focus').blur(); // required to do not try to remove!
|
|
||||||
padeditor.ace.focus(); // Sends focus back to pad
|
|
||||||
// The above focus doesn't always work in FF, you have to hit enter afterwards
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Focus on the editbar :)
|
|
||||||
const firstEditbarElement = parent.parent.$('#editbar button').first();
|
|
||||||
|
|
||||||
$(evt.currentTarget).blur();
|
|
||||||
firstEditbarElement.focus();
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Are we in the toolbar??
|
|
||||||
if ($(':focus').parents('.toolbar').length === 1) {
|
|
||||||
// On arrow keys go to next/previous button item in editbar
|
|
||||||
if (evt.keyCode !== 39 && evt.keyCode !== 37) return;
|
|
||||||
|
|
||||||
// Get all the focusable items in the editbar
|
|
||||||
const focusItems = $('#editbar').find('button, select');
|
|
||||||
|
|
||||||
// On left arrow move to next button in editbar
|
|
||||||
if (evt.keyCode === 37) {
|
|
||||||
// If a dropdown is visible or we're in an input don't move to the next button
|
|
||||||
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
|
|
||||||
|
|
||||||
this._editbarPosition--;
|
|
||||||
// Allow focus to shift back to end of row and start of row
|
|
||||||
if (this._editbarPosition === -1) this._editbarPosition = focusItems.length - 1;
|
|
||||||
$(focusItems[this._editbarPosition]).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// On right arrow move to next button in editbar
|
|
||||||
if (evt.keyCode === 39) {
|
|
||||||
// If a dropdown is visible or we're in an input don't move to the next button
|
|
||||||
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
|
|
||||||
|
|
||||||
this._editbarPosition++;
|
|
||||||
// Allow focus to shift back to end of row and start of row
|
|
||||||
if (this._editbarPosition >= focusItems.length) this._editbarPosition = 0;
|
|
||||||
$(focusItems[this._editbarPosition]).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_registerDefaultCommands() {
|
|
||||||
this.registerDropdownCommand('showusers', 'users');
|
|
||||||
this.registerDropdownCommand('settings');
|
|
||||||
this.registerDropdownCommand('connectivity');
|
|
||||||
this.registerDropdownCommand('import_export');
|
|
||||||
this.registerDropdownCommand('embed');
|
|
||||||
|
|
||||||
this.registerCommand('settings', () => {
|
|
||||||
this.toggleDropDown('settings', () => {
|
|
||||||
$('#options-stickychat').focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerCommand('import_export', () => {
|
|
||||||
this.toggleDropDown('import_export', () => {
|
|
||||||
// If Import file input exists then focus on it..
|
|
||||||
if ($('#importfileinput').length !== 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
$('#importfileinput').focus();
|
|
||||||
}, 100);
|
|
||||||
} else {
|
|
||||||
$('.exportlink').first().focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerCommand('showusers', () => {
|
|
||||||
this.toggleDropDown('users', () => {
|
|
||||||
$('#myusernameedit').focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerCommand('embed', () => {
|
|
||||||
this.setEmbedLinks();
|
|
||||||
this.toggleDropDown('embed', () => {
|
|
||||||
$('#linkinput').focus().select();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerCommand('savedRevision', () => {
|
|
||||||
padsavedrevs.saveNow();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerCommand('showTimeSlider', () => {
|
|
||||||
document.location = `${document.location.pathname}/timeslider`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const aceAttributeCommand = (cmd, ace) => {
|
|
||||||
ace.ace_toggleAttributeOnSelection(cmd);
|
|
||||||
};
|
|
||||||
this.registerAceCommand('bold', aceAttributeCommand);
|
|
||||||
this.registerAceCommand('italic', aceAttributeCommand);
|
|
||||||
this.registerAceCommand('underline', aceAttributeCommand);
|
|
||||||
this.registerAceCommand('strikethrough', aceAttributeCommand);
|
|
||||||
|
|
||||||
this.registerAceCommand('undo', (cmd, ace) => {
|
|
||||||
ace.ace_doUndoRedo(cmd);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerAceCommand('redo', (cmd, ace) => {
|
|
||||||
ace.ace_doUndoRedo(cmd);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerAceCommand('insertunorderedlist', (cmd, ace) => {
|
|
||||||
ace.ace_doInsertUnorderedList();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerAceCommand('insertorderedlist', (cmd, ace) => {
|
|
||||||
ace.ace_doInsertOrderedList();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerAceCommand('indent', (cmd, ace) => {
|
|
||||||
if (!ace.ace_doIndentOutdent(false)) {
|
|
||||||
ace.ace_doInsertUnorderedList();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerAceCommand('outdent', (cmd, ace) => {
|
|
||||||
ace.ace_doIndentOutdent(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerAceCommand('clearauthorship', (cmd, ace) => {
|
|
||||||
// If we have the whole document selected IE control A has been hit
|
|
||||||
const rep = ace.ace_getRep();
|
|
||||||
let doPrompt = false;
|
|
||||||
const lastChar = rep.lines.atIndex(rep.lines.length() - 1).width - 1;
|
|
||||||
const lastLineIndex = rep.lines.length() - 1;
|
|
||||||
if (rep.selStart[0] === 0 && rep.selStart[1] === 0) {
|
|
||||||
// nesting intentionally here to make things readable
|
|
||||||
if (rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar) {
|
|
||||||
doPrompt = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* NOTICE: This command isn't fired on Control Shift C.
|
|
||||||
* I intentionally didn't create duplicate code because if you are hitting
|
|
||||||
* Control Shift C we make the assumption you are a "power user"
|
|
||||||
* and as such we assume you don't need the prompt to bug you each time!
|
|
||||||
* This does make wonder if it's worth having a checkbox to avoid being
|
|
||||||
* prompted again but that's probably overkill for this contribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// if we don't have any text selected, we have a caret or we have already said to prompt
|
|
||||||
if ((!(rep.selStart && rep.selEnd)) || ace.ace_isCaret() || doPrompt) {
|
|
||||||
if (window.confirm(html10n.get('pad.editbar.clearcolors'))) {
|
|
||||||
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
|
|
||||||
['author', ''],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ace.ace_setAttributeOnSelection('author', '');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerCommand('timeslider_returnToPad', (cmd) => {
|
|
||||||
if (document.referrer.length > 0 &&
|
|
||||||
document.referrer.substring(document.referrer.lastIndexOf('/') - 1,
|
|
||||||
document.referrer.lastIndexOf('/')) === 'p') {
|
|
||||||
document.location = document.referrer;
|
|
||||||
} else {
|
|
||||||
document.location = document.location.href
|
|
||||||
.substring(0, document.location.href.lastIndexOf('/'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
exports.padeditbar = padeditbar;
|
exports.padeditbar = {
|
||||||
|
_editbarPosition: 0,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.dropdowns = [];
|
||||||
|
|
||||||
|
$('#editbar .editbarbutton').attr('unselectable', 'on'); // for IE
|
||||||
|
this.enable();
|
||||||
|
$('#editbar [data-key]').each((i, elt) => {
|
||||||
|
$(elt).unbind('click');
|
||||||
|
new ToolbarItem($(elt)).bind((command, item) => {
|
||||||
|
this.triggerCommand(command, item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body:not(#editorcontainerbox)').on('keydown', (evt) => {
|
||||||
|
this._bodyKeyEvent(evt);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.show-more-icon-btn').click(() => {
|
||||||
|
$('.toolbar').toggleClass('full-icons');
|
||||||
|
});
|
||||||
|
this.checkAllIconsAreDisplayedInToolbar();
|
||||||
|
$(window).resize(_.debounce(() => this.checkAllIconsAreDisplayedInToolbar(), 100));
|
||||||
|
|
||||||
|
this._registerDefaultCommands();
|
||||||
|
|
||||||
|
hooks.callAll('postToolbarInit', {
|
||||||
|
toolbar: this,
|
||||||
|
ace: padeditor.ace,
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On safari, the dropdown in the toolbar gets hidden because of toolbar
|
||||||
|
* overflow:hidden property. This is a bug from Safari: any children with
|
||||||
|
* position:fixed (like the dropdown) should be displayed no matter
|
||||||
|
* overflow:hidden on parent
|
||||||
|
*/
|
||||||
|
if (!browser.safari) {
|
||||||
|
$('select').niceSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When editor is scrolled, we add a class to style the editbar differently
|
||||||
|
$('iframe[name="ace_outer"]').contents().scroll((ev) => {
|
||||||
|
$('#editbar').toggleClass('editor-scrolled', $(ev.currentTarget).scrollTop() > 2);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isEnabled: () => true,
|
||||||
|
disable: () => {
|
||||||
|
$('#editbar').addClass('disabledtoolbar').removeClass('enabledtoolbar');
|
||||||
|
},
|
||||||
|
enable: () => {
|
||||||
|
$('#editbar').addClass('enabledtoolbar').removeClass('disabledtoolbar');
|
||||||
|
},
|
||||||
|
commands: {},
|
||||||
|
registerCommand(cmd, callback) {
|
||||||
|
this.commands[cmd] = callback;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
registerDropdownCommand(cmd, dropdown) {
|
||||||
|
dropdown = dropdown || cmd;
|
||||||
|
this.dropdowns.push(dropdown);
|
||||||
|
this.registerCommand(cmd, () => {
|
||||||
|
this.toggleDropDown(dropdown);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
registerAceCommand(cmd, callback) {
|
||||||
|
this.registerCommand(cmd, (cmd, ace, item) => {
|
||||||
|
ace.callWithAce((ace) => {
|
||||||
|
callback(cmd, ace, item);
|
||||||
|
}, cmd, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerCommand(cmd, item) {
|
||||||
|
if (this.isEnabled() && this.commands[cmd]) {
|
||||||
|
this.commands[cmd](cmd, padeditor.ace, item);
|
||||||
|
}
|
||||||
|
if (padeditor.ace) padeditor.ace.focus();
|
||||||
|
},
|
||||||
|
toggleDropDown(moduleName, cb) {
|
||||||
|
// do nothing if users are sticked
|
||||||
|
if (moduleName === 'users' && $('#users').hasClass('stickyUsers')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.nice-select').removeClass('open');
|
||||||
|
$('.toolbar-popup').removeClass('popup-show');
|
||||||
|
|
||||||
|
// hide all modules and remove highlighting of all buttons
|
||||||
|
if (moduleName === 'none') {
|
||||||
|
const returned = false;
|
||||||
|
for (const thisModuleName of this.dropdowns) {
|
||||||
|
// skip the userlist
|
||||||
|
if (thisModuleName === 'users') continue;
|
||||||
|
|
||||||
|
const module = $(`#${thisModuleName}`);
|
||||||
|
|
||||||
|
// skip any "force reconnect" message
|
||||||
|
const isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
|
||||||
|
if (isAForceReconnectMessage) continue;
|
||||||
|
if (module.hasClass('popup-show')) {
|
||||||
|
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
|
||||||
|
module.removeClass('popup-show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!returned && cb) return cb();
|
||||||
|
} else {
|
||||||
|
// hide all modules that are not selected and remove highlighting
|
||||||
|
// respectively add highlighting to the corresponding button
|
||||||
|
for (const thisModuleName of this.dropdowns) {
|
||||||
|
const module = $(`#${thisModuleName}`);
|
||||||
|
|
||||||
|
if (module.hasClass('popup-show')) {
|
||||||
|
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
|
||||||
|
module.removeClass('popup-show');
|
||||||
|
} else if (thisModuleName === moduleName) {
|
||||||
|
$(`li[data-key=${thisModuleName}] > a`).addClass('selected');
|
||||||
|
module.addClass('popup-show');
|
||||||
|
if (cb) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSyncStatus: (status) => {
|
||||||
|
if (status === 'syncing') {
|
||||||
|
syncAnimation.syncing();
|
||||||
|
} else if (status === 'done') {
|
||||||
|
syncAnimation.done();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setEmbedLinks: () => {
|
||||||
|
const padUrl = window.location.href.split('?')[0];
|
||||||
|
const params = '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false';
|
||||||
|
const props = 'width="100%" height="600" frameborder="0"';
|
||||||
|
|
||||||
|
if ($('#readonlyinput').is(':checked')) {
|
||||||
|
const urlParts = padUrl.split('/');
|
||||||
|
urlParts.pop();
|
||||||
|
const readonlyLink = `${urlParts.join('/')}/${clientVars.readOnlyId}`;
|
||||||
|
$('#embedinput')
|
||||||
|
.val(`<iframe name="embed_readonly" src="${readonlyLink}${params}" ${props}></iframe>`);
|
||||||
|
$('#linkinput').val(readonlyLink);
|
||||||
|
} else {
|
||||||
|
$('#embedinput')
|
||||||
|
.val(`<iframe name="embed_readwrite" src="${padUrl}${params}" ${props}></iframe>`);
|
||||||
|
$('#linkinput').val(padUrl);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkAllIconsAreDisplayedInToolbar: () => {
|
||||||
|
// reset style
|
||||||
|
$('.toolbar').removeClass('cropped');
|
||||||
|
$('body').removeClass('mobile-layout');
|
||||||
|
const menu_left = $('.toolbar .menu_left')[0];
|
||||||
|
|
||||||
|
// this is approximate, we cannot measure it because on mobile
|
||||||
|
// Layout it takes the full width on the bottom of the page
|
||||||
|
const menuRightWidth = 280;
|
||||||
|
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth ||
|
||||||
|
$('.toolbar').width() < 1000) {
|
||||||
|
$('body').addClass('mobile-layout');
|
||||||
|
}
|
||||||
|
if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) {
|
||||||
|
$('.toolbar').addClass('cropped');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_bodyKeyEvent(evt) {
|
||||||
|
// If the event is Alt F9 or Escape & we're already in the editbar menu
|
||||||
|
// Send the users focus back to the pad
|
||||||
|
if ((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27) {
|
||||||
|
if ($(':focus').parents('.toolbar').length === 1) {
|
||||||
|
// If we're in the editbar already..
|
||||||
|
// Close any dropdowns we have open..
|
||||||
|
this.toggleDropDown('none');
|
||||||
|
// Check we're on a pad and not on the timeslider
|
||||||
|
// Or some other window I haven't thought about!
|
||||||
|
if (typeof pad === 'undefined') {
|
||||||
|
// Timeslider probably..
|
||||||
|
// Shift focus away from any drop downs
|
||||||
|
$(':focus').blur(); // required to do not try to remove!
|
||||||
|
$('#editorcontainerbox').focus(); // Focus back onto the pad
|
||||||
|
} else {
|
||||||
|
// Shift focus away from any drop downs
|
||||||
|
$(':focus').blur(); // required to do not try to remove!
|
||||||
|
padeditor.ace.focus(); // Sends focus back to pad
|
||||||
|
// The above focus doesn't always work in FF, you have to hit enter afterwards
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Focus on the editbar :)
|
||||||
|
const firstEditbarElement = parent.parent.$('#editbar button').first();
|
||||||
|
|
||||||
|
$(evt.currentTarget).blur();
|
||||||
|
firstEditbarElement.focus();
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Are we in the toolbar??
|
||||||
|
if ($(':focus').parents('.toolbar').length === 1) {
|
||||||
|
// On arrow keys go to next/previous button item in editbar
|
||||||
|
if (evt.keyCode !== 39 && evt.keyCode !== 37) return;
|
||||||
|
|
||||||
|
// Get all the focusable items in the editbar
|
||||||
|
const focusItems = $('#editbar').find('button, select');
|
||||||
|
|
||||||
|
// On left arrow move to next button in editbar
|
||||||
|
if (evt.keyCode === 37) {
|
||||||
|
// If a dropdown is visible or we're in an input don't move to the next button
|
||||||
|
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
|
||||||
|
|
||||||
|
this._editbarPosition--;
|
||||||
|
// Allow focus to shift back to end of row and start of row
|
||||||
|
if (this._editbarPosition === -1) this._editbarPosition = focusItems.length - 1;
|
||||||
|
$(focusItems[this._editbarPosition]).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// On right arrow move to next button in editbar
|
||||||
|
if (evt.keyCode === 39) {
|
||||||
|
// If a dropdown is visible or we're in an input don't move to the next button
|
||||||
|
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
|
||||||
|
|
||||||
|
this._editbarPosition++;
|
||||||
|
// Allow focus to shift back to end of row and start of row
|
||||||
|
if (this._editbarPosition >= focusItems.length) this._editbarPosition = 0;
|
||||||
|
$(focusItems[this._editbarPosition]).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_registerDefaultCommands() {
|
||||||
|
this.registerDropdownCommand('showusers', 'users');
|
||||||
|
this.registerDropdownCommand('settings');
|
||||||
|
this.registerDropdownCommand('connectivity');
|
||||||
|
this.registerDropdownCommand('import_export');
|
||||||
|
this.registerDropdownCommand('embed');
|
||||||
|
|
||||||
|
this.registerCommand('settings', () => {
|
||||||
|
this.toggleDropDown('settings', () => {
|
||||||
|
$('#options-stickychat').focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerCommand('import_export', () => {
|
||||||
|
this.toggleDropDown('import_export', () => {
|
||||||
|
// If Import file input exists then focus on it..
|
||||||
|
if ($('#importfileinput').length !== 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
$('#importfileinput').focus();
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
$('.exportlink').first().focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerCommand('showusers', () => {
|
||||||
|
this.toggleDropDown('users', () => {
|
||||||
|
$('#myusernameedit').focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerCommand('embed', () => {
|
||||||
|
this.setEmbedLinks();
|
||||||
|
this.toggleDropDown('embed', () => {
|
||||||
|
$('#linkinput').focus().select();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerCommand('savedRevision', () => {
|
||||||
|
padsavedrevs.saveNow();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerCommand('showTimeSlider', () => {
|
||||||
|
document.location = `${document.location.pathname}/timeslider`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const aceAttributeCommand = (cmd, ace) => {
|
||||||
|
ace.ace_toggleAttributeOnSelection(cmd);
|
||||||
|
};
|
||||||
|
this.registerAceCommand('bold', aceAttributeCommand);
|
||||||
|
this.registerAceCommand('italic', aceAttributeCommand);
|
||||||
|
this.registerAceCommand('underline', aceAttributeCommand);
|
||||||
|
this.registerAceCommand('strikethrough', aceAttributeCommand);
|
||||||
|
|
||||||
|
this.registerAceCommand('undo', (cmd, ace) => {
|
||||||
|
ace.ace_doUndoRedo(cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerAceCommand('redo', (cmd, ace) => {
|
||||||
|
ace.ace_doUndoRedo(cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerAceCommand('insertunorderedlist', (cmd, ace) => {
|
||||||
|
ace.ace_doInsertUnorderedList();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerAceCommand('insertorderedlist', (cmd, ace) => {
|
||||||
|
ace.ace_doInsertOrderedList();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerAceCommand('indent', (cmd, ace) => {
|
||||||
|
if (!ace.ace_doIndentOutdent(false)) {
|
||||||
|
ace.ace_doInsertUnorderedList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerAceCommand('outdent', (cmd, ace) => {
|
||||||
|
ace.ace_doIndentOutdent(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerAceCommand('clearauthorship', (cmd, ace) => {
|
||||||
|
// If we have the whole document selected IE control A has been hit
|
||||||
|
const rep = ace.ace_getRep();
|
||||||
|
let doPrompt = false;
|
||||||
|
const lastChar = rep.lines.atIndex(rep.lines.length() - 1).width - 1;
|
||||||
|
const lastLineIndex = rep.lines.length() - 1;
|
||||||
|
if (rep.selStart[0] === 0 && rep.selStart[1] === 0) {
|
||||||
|
// nesting intentionally here to make things readable
|
||||||
|
if (rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar) {
|
||||||
|
doPrompt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* NOTICE: This command isn't fired on Control Shift C.
|
||||||
|
* I intentionally didn't create duplicate code because if you are hitting
|
||||||
|
* Control Shift C we make the assumption you are a "power user"
|
||||||
|
* and as such we assume you don't need the prompt to bug you each time!
|
||||||
|
* This does make wonder if it's worth having a checkbox to avoid being
|
||||||
|
* prompted again but that's probably overkill for this contribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// if we don't have any text selected, we have a caret or we have already said to prompt
|
||||||
|
if ((!(rep.selStart && rep.selEnd)) || ace.ace_isCaret() || doPrompt) {
|
||||||
|
if (window.confirm(html10n.get('pad.editbar.clearcolors'))) {
|
||||||
|
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
|
||||||
|
['author', ''],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ace.ace_setAttributeOnSelection('author', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerCommand('timeslider_returnToPad', (cmd) => {
|
||||||
|
if (document.referrer.length > 0 &&
|
||||||
|
document.referrer.substring(document.referrer.lastIndexOf('/') - 1,
|
||||||
|
document.referrer.lastIndexOf('/')) === 'p') {
|
||||||
|
document.location = document.referrer;
|
||||||
|
} else {
|
||||||
|
document.location = document.location.href
|
||||||
|
.substring(0, document.location.href.lastIndexOf('/'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue