Refit the Block UI logic
Previously contained in richeditor (this is a bit messy, rich editor should be generic), the block UI logic has been moved to its own "figures" plugin
This commit is contained in:
parent
85decc4086
commit
115fc0c1ef
File diff suppressed because one or more lines are too long
|
|
@ -37,6 +37,7 @@
|
|||
=require ../vendor/froala_drm/js/plugins/draggable.min.js
|
||||
=require ../vendor/froala_drm/js/plugins/code_beautifier.min.js
|
||||
|
||||
=require plugins/figures.js
|
||||
=require richeditor.js
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
(function ($) {
|
||||
// Add an option for your plugin.
|
||||
// $.FroalaEditor.DEFAULTS = $.extend($.FroalaEditor.DEFAULTS, {
|
||||
// myOption: false
|
||||
// });
|
||||
|
||||
$.FroalaEditor.PLUGINS.figures = function (editor) {
|
||||
|
||||
/**
|
||||
* Insert UI Blocks
|
||||
*/
|
||||
function insert($el) {
|
||||
var html = $('<div />').append($el.clone()).remove().html()
|
||||
|
||||
editor.html.insert(html)
|
||||
editor.selection.restore()
|
||||
editor.html.cleanEmptyTags()
|
||||
|
||||
// Clean up wrapping paragraphs or empty paragraphs
|
||||
$('figure', editor.$el).each(function() {
|
||||
var $this = $(this),
|
||||
$parent = $this.parent('p'),
|
||||
$next = $this.next('p')
|
||||
|
||||
if (!!$parent.length) {
|
||||
$this.unwrap()
|
||||
}
|
||||
|
||||
if (!!$next.length && $.trim($next.text()).length == 0) {
|
||||
$next.remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Init UI Blocks
|
||||
*/
|
||||
function _initUiBlocks () {
|
||||
$('[data-video], [data-audio]', editor.$el).each(function() {
|
||||
$(this).attr({
|
||||
'data-ui-block': true,
|
||||
'tabindex': '0'
|
||||
})
|
||||
this.contentEditable = false
|
||||
})
|
||||
}
|
||||
|
||||
function _handleUiBlocksKeydown(ev) {
|
||||
if (ev.which == 40 || ev.which == 38 || ev.which == 8 || ev.which == 46) {
|
||||
var $block = $(editor.selection.element())
|
||||
if ($block.is('br')) {
|
||||
$block = $block.parent()
|
||||
}
|
||||
|
||||
if (!!$block.length) {
|
||||
switch (ev.which) {
|
||||
case 38:
|
||||
// Up arrow
|
||||
_handleUiBlockCaretIn($block.prev())
|
||||
break
|
||||
case 40:
|
||||
// Down arrow
|
||||
_handleUiBlockCaretIn($block.next())
|
||||
break
|
||||
case 46:
|
||||
// Delete key
|
||||
_handleUiBlockCaretClearEmpty($block.next(), $block)
|
||||
break
|
||||
case 8:
|
||||
// Backspace key
|
||||
_handleUiBlockCaretClearEmpty($block.prev(), $block)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _handleUiBlockCaretClearEmpty($block, $p) {
|
||||
if ($block.attr('data-ui-block') !== undefined && $.trim($p.text()).length == 0) {
|
||||
$p.remove()
|
||||
_handleUiBlockCaretIn($block)
|
||||
editor.undo.saveStep()
|
||||
}
|
||||
}
|
||||
|
||||
function _handleUiBlockCaretIn($block) {
|
||||
if ($block.attr('data-ui-block') !== undefined) {
|
||||
$block.focus()
|
||||
editor.selection.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function _uiBlockKeyDown(ev, block) {
|
||||
if (ev.which == 40 || ev.which == 38 || ev.which == 13 || ev.which == 8 || ev.which == 46) {
|
||||
switch (ev.which) {
|
||||
case 40:
|
||||
// Down arrow
|
||||
_focusUiBlockOrText($(block).next(), true)
|
||||
break
|
||||
case 38:
|
||||
// Up arrow
|
||||
_focusUiBlockOrText($(block).prev(), false)
|
||||
break
|
||||
case 13:
|
||||
// Enter key
|
||||
var $paragraph = $('<p><br/></p>')
|
||||
$paragraph.insertAfter(block)
|
||||
editor.selection.setAfter(block)
|
||||
editor.selection.restore()
|
||||
editor.undo.saveStep()
|
||||
break
|
||||
case 8:
|
||||
case 46:
|
||||
// Delete / Backspace key
|
||||
var $nextFocus = $(block).next(),
|
||||
gotoStart = true
|
||||
|
||||
if ($nextFocus.length == 0) {
|
||||
$nextFocus = $(block).prev()
|
||||
gotoStart = false
|
||||
}
|
||||
|
||||
_focusUiBlockOrText($nextFocus, gotoStart)
|
||||
|
||||
$(block).remove()
|
||||
editor.undo.saveStep()
|
||||
break
|
||||
}
|
||||
|
||||
ev.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
function _focusUiBlockOrText($block, gotoStart) {
|
||||
if (!!$block.length) {
|
||||
if (!_handleUiBlockCaretIn($block)) {
|
||||
if (gotoStart) {
|
||||
editor.selection.setAtStart($block.get(0))
|
||||
editor.selection.restore()
|
||||
}
|
||||
else {
|
||||
editor.selection.setAtEnd($block.get(0))
|
||||
editor.selection.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keydown
|
||||
*/
|
||||
function _onKeydown (ev) {
|
||||
_handleUiBlocksKeydown(ev)
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function _onFigureKeydown(ev) {
|
||||
if (ev.target && $(ev.target).attr('data-ui-block') !== undefined) {
|
||||
_uiBlockKeyDown(ev, ev.target)
|
||||
}
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync
|
||||
*/
|
||||
function _onSync(html) {
|
||||
var $domTree = $('<div>' + html + '</div>')
|
||||
|
||||
$domTree.find('[data-video], [data-audio]').each(function(){
|
||||
$(this).removeAttr('contenteditable data-ui-block tabindex')
|
||||
})
|
||||
|
||||
return $domTree.html()
|
||||
}
|
||||
|
||||
// The start point for your plugin.
|
||||
function _init () {
|
||||
editor.events.on('initialized', _initUiBlocks)
|
||||
|
||||
editor.events.on('html.get', _onSync)
|
||||
|
||||
editor.events.on('keydown', _onKeydown)
|
||||
|
||||
editor.events.on('destroy', _destroy, true)
|
||||
|
||||
editor.$el.on('keydown', 'figure', _onFigureKeydown)
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy.
|
||||
*/
|
||||
function _destroy () {
|
||||
editor.$el.off('keydown', 'figure', _onFigureKeydown)
|
||||
}
|
||||
|
||||
return {
|
||||
_init: _init,
|
||||
insert: insert
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
* $('textarea').richEditor()
|
||||
*
|
||||
* Dependancies:
|
||||
* - Redactor Editor (redactor.js)
|
||||
* - Froala Editor (froala_editor.js)
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
var Base = $.oc.foundation.base,
|
||||
|
|
@ -22,8 +22,7 @@
|
|||
this.$el = $(element)
|
||||
this.$textarea = this.$el.find('>textarea:first')
|
||||
this.$form = this.$el.closest('form')
|
||||
this.$editor = null
|
||||
this.redactor = null
|
||||
this.editor = null
|
||||
|
||||
$.oc.foundation.controlUtils.markDisposable(element)
|
||||
|
||||
|
|
@ -95,6 +94,8 @@
|
|||
froalaOptions.toolbarButtonsXS = froalaOptions.toolbarButtons
|
||||
froalaOptions.htmlAllowedEmptyTags = ['figure', 'textarea', 'a', 'iframe', 'object', 'video', 'style', 'script']
|
||||
froalaOptions.htmlDoNotWrapTags = ['figure', 'script', 'style']
|
||||
froalaOptions.lineBreakerTags = ['figure', 'table', 'hr', 'iframe', 'form', 'dl']
|
||||
froalaOptions.shortcutsEnabled = ['show', 'bold', 'italic', 'underline', 'indent', 'outdent', 'undo', 'redo']
|
||||
|
||||
var placeholder = this.$textarea.attr('placeholder')
|
||||
froalaOptions.placeholderText = placeholder ? placeholder : ''
|
||||
|
|
@ -110,8 +111,16 @@
|
|||
}
|
||||
|
||||
this.$textarea.on('froalaEditor.initialized', this.proxy(this.build))
|
||||
this.$textarea.on('froalaEditor.contentChanged', this.proxy(this.onChange))
|
||||
this.$textarea.on('froalaEditor.keydown', this.proxy(this.onKeydown))
|
||||
this.$textarea.on('froalaEditor.html.get', this.proxy(this.onSyncContent))
|
||||
this.$textarea.on('froalaEditor.html.set', this.proxy(this.onSetContent))
|
||||
|
||||
this.$textarea.froalaEditor(froalaOptions)
|
||||
|
||||
this.editor = this.$textarea.data('froala.editor')
|
||||
|
||||
this.$el.on('keydown', '.fr-view figure', this.proxy(this.onFigureKeydown))
|
||||
}
|
||||
|
||||
RichEditor.prototype.dispose = function() {
|
||||
|
|
@ -125,12 +134,20 @@
|
|||
this.$el = null
|
||||
this.$textarea = null
|
||||
this.$form = null
|
||||
this.$editor = null
|
||||
this.editor = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
RichEditor.prototype.unregisterHandlers = function() {
|
||||
this.$el.off('keydown', '.fr-view figure', this.proxy(this.onFigureKeydown))
|
||||
|
||||
this.$textarea.off('froalaEditor.initialized', this.proxy(this.build))
|
||||
this.$textarea.off('froalaEditor.contentChanged', this.proxy(this.onChange))
|
||||
this.$textarea.off('froalaEditor.keydown', this.proxy(this.onKeydown))
|
||||
this.$textarea.off('froalaEditor.html.get', this.proxy(this.onSyncContent))
|
||||
this.$textarea.off('froalaEditor.html.set', this.proxy(this.onSetContent))
|
||||
|
||||
$(window).off('resize', this.proxy(this.updateLayout))
|
||||
$(window).off('oc.updateUi', this.proxy(this.updateLayout))
|
||||
this.$el.off('dispose-control', this.proxy(this.dispose))
|
||||
|
|
@ -143,15 +160,6 @@
|
|||
$(window).on('oc.updateUi', this.proxy(this.updateLayout))
|
||||
|
||||
this.$textarea.trigger('init.oc.richeditor', [this])
|
||||
|
||||
this.initUiBlocks()
|
||||
|
||||
// var self = this
|
||||
// redactor.default = {
|
||||
// onShow: function($figure, $toolbar) {
|
||||
// self.onShowFigureToolbar($figure, $toolbar)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
RichEditor.prototype.getElement = function() {
|
||||
|
|
@ -159,17 +167,23 @@
|
|||
}
|
||||
|
||||
RichEditor.prototype.getEditor = function() {
|
||||
return this.$editor
|
||||
return this.editor
|
||||
}
|
||||
|
||||
RichEditor.prototype.getTextarea = function() {
|
||||
return this.$textarea
|
||||
}
|
||||
|
||||
RichEditor.prototype.setContent = function(html) {
|
||||
this.$textarea.redactor('code.set', html)
|
||||
RichEditor.prototype.getContent = function() {
|
||||
return this.$textarea.froalaEditor('html.get')
|
||||
}
|
||||
|
||||
this.$textarea.trigger('setContent.oc.richeditor', [this])
|
||||
RichEditor.prototype.setContent = function(html) {
|
||||
this.$textarea.froalaEditor('html.set', html)
|
||||
}
|
||||
|
||||
RichEditor.prototype.syncContent = function() {
|
||||
this.editor.events.trigger('contentChanged')
|
||||
}
|
||||
|
||||
RichEditor.prototype.updateLayout = function() {
|
||||
|
|
@ -193,210 +207,37 @@
|
|||
}
|
||||
}
|
||||
|
||||
RichEditor.prototype.sanityCheckContent = function() {
|
||||
// First and last elements should always be paragraphs, lists or pre
|
||||
var safeElements = 'p, h1, h2, h3, h4, h5, pre, figure, ol, ul';
|
||||
|
||||
if (!this.$editor.children(':last-child').is(safeElements)) {
|
||||
this.$editor.append('<p><br></p>')
|
||||
}
|
||||
|
||||
if (!this.$editor.children(':first-child').is(safeElements)) {
|
||||
this.$editor.prepend('<p><br></p>')
|
||||
}
|
||||
|
||||
this.$textarea.trigger('sanitize.oc.richeditor', [this])
|
||||
RichEditor.prototype.insertHtml = function(html) {
|
||||
this.editor.html.insert(html)
|
||||
this.editor.selection.restore()
|
||||
}
|
||||
|
||||
RichEditor.prototype.syncBefore = function(html) {
|
||||
var container = {
|
||||
html: html
|
||||
}
|
||||
|
||||
this.$textarea.trigger('syncBefore.oc.richeditor', [this, container])
|
||||
|
||||
var $domTree = $('<div>'+container.html+'</div>')
|
||||
|
||||
// This code removes Redactor-specific attributes and tags from the code.
|
||||
// It seems to be a known problem with Redactor, try googling for
|
||||
// "data-redactor-tag" or "redactor-invisible-space" (with quotes)
|
||||
$('*', $domTree).removeAttr('data-redactor-tag')
|
||||
|
||||
$domTree.find('span[data-redactor-class="redactor-invisible-space"]').each(function(){
|
||||
$(this).children().insertBefore(this)
|
||||
$(this).remove()
|
||||
})
|
||||
|
||||
$domTree.find('span.redactor-invisible-space').each(function(){
|
||||
$(this).children().insertBefore(this)
|
||||
$(this).remove()
|
||||
})
|
||||
|
||||
$domTree.find('[data-video], [data-audio]').each(function(){
|
||||
$(this).removeAttr('contenteditable data-ui-block tabindex')
|
||||
})
|
||||
|
||||
$domTree.find('div.oc-figure-controls').remove()
|
||||
|
||||
return $domTree.html()
|
||||
}
|
||||
|
||||
RichEditor.prototype.onShowFigureToolbar = function($figure, $toolbar) {
|
||||
// Deal with the case when the toolbar top has negative value
|
||||
var toolbarTop = $figure.position().top - $toolbar.height() - 10
|
||||
|
||||
$toolbar.toggleClass('bottom', toolbarTop < 0)
|
||||
RichEditor.prototype.insertElement = function($el) {
|
||||
this.insertHtml($('<div />').append($el.clone()).remove().html())
|
||||
}
|
||||
|
||||
/*
|
||||
* Inserts non-editable block (used for snippets, audio and video)
|
||||
*/
|
||||
RichEditor.prototype.insertUiBlock = function($node) {
|
||||
var current = this.redactor.selection.getCurrent(),
|
||||
inserted = false
|
||||
|
||||
if (current === false) {
|
||||
this.redactor.focus.setStart()
|
||||
}
|
||||
|
||||
current = this.redactor.selection.getCurrent()
|
||||
|
||||
if (current !== false) {
|
||||
// If the block is inserted into a paragraph, insert it after the paragraph.
|
||||
var $paragraph = $(current).closest('p')
|
||||
if ($paragraph.length > 0) {
|
||||
this.redactor.caret.setAfter($paragraph.get(0))
|
||||
|
||||
// If the paragraph is empty, remove it.
|
||||
if ($.trim($paragraph.text()).length == 0) {
|
||||
$paragraph.remove()
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If block is inserted into another UI block, insert it after the existing block.
|
||||
var $closestBlock = $(current).closest('[data-ui-block]')
|
||||
if ($closestBlock.length > 0) {
|
||||
$node.insertBefore($closestBlock.get(0))
|
||||
inserted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
this.redactor.insert.node($node)
|
||||
}
|
||||
|
||||
this.redactor.code.sync()
|
||||
|
||||
$node.focus()
|
||||
}
|
||||
|
||||
RichEditor.prototype.initUiBlocks = function() {
|
||||
$('.fr-wrapper [data-video], .fr-wrapper [data-audio]', this.$el).each(function() {
|
||||
$(this).attr({
|
||||
'data-ui-block': true,
|
||||
'tabindex': '0'
|
||||
})
|
||||
this.contentEditable = false
|
||||
})
|
||||
}
|
||||
|
||||
RichEditor.prototype.handleUiBlocksKeydown = function(ev) {
|
||||
if (this.$textarea === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.target && $(ev.target).attr('data-ui-block') !== undefined) {
|
||||
this.uiBlockKeyDown(ev, ev.target)
|
||||
|
||||
ev.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
switch (ev.which) {
|
||||
case 38:
|
||||
// Up arrow
|
||||
var block = this.redactor.selection.getBlock()
|
||||
if (block)
|
||||
this.handleUiBlockCaretIn($(block).prev())
|
||||
break
|
||||
case 40:
|
||||
// Down arrow
|
||||
var block = this.redactor.selection.getBlock()
|
||||
if (block)
|
||||
this.handleUiBlockCaretIn($(block).next())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
RichEditor.prototype.handleUiBlockCaretIn = function($block) {
|
||||
if ($block.attr('data-ui-block') !== undefined) {
|
||||
$block.focus()
|
||||
this.redactor.selection.remove()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
RichEditor.prototype.uiBlockKeyDown = function(ev, block) {
|
||||
if (ev.which == 40 || ev.which == 38 || ev.which == 13 || ev.which == 8) {
|
||||
switch (ev.which) {
|
||||
case 40:
|
||||
// Down arrow
|
||||
this.focusUiBlockOrText($(block).next(), true)
|
||||
break
|
||||
case 38:
|
||||
// Up arrow
|
||||
this.focusUiBlockOrText($(block).prev(), false)
|
||||
break
|
||||
case 13:
|
||||
// Enter key
|
||||
var $paragraph = $('<p><br/></p>')
|
||||
$paragraph.insertAfter(block)
|
||||
this.redactor.caret.setStart($paragraph.get(0))
|
||||
break
|
||||
case 8:
|
||||
// Backspace key
|
||||
var $nextFocus = $(block).next(),
|
||||
gotoStart = true
|
||||
|
||||
if ($nextFocus.length == 0) {
|
||||
$nextFocus = $(block).prev()
|
||||
gotoStart = false
|
||||
}
|
||||
|
||||
this.focusUiBlockOrText($nextFocus, gotoStart)
|
||||
|
||||
$(block).remove()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RichEditor.prototype.focusUiBlockOrText = function($block, gotoStart) {
|
||||
if ($block.length > 0) {
|
||||
if (!this.handleUiBlockCaretIn($block, this.redactor)) {
|
||||
if (gotoStart) {
|
||||
this.redactor.caret.setStart($block.get(0))
|
||||
}
|
||||
else {
|
||||
this.redactor.caret.setEnd($block.get(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$textarea.froalaEditor('figures.insert', $node)
|
||||
}
|
||||
|
||||
// EVENT HANDLERS
|
||||
// ============================
|
||||
|
||||
RichEditor.prototype.onVisualMode = function(html) {
|
||||
this.$textarea.trigger('visualMode.oc.richeditor', [this])
|
||||
RichEditor.prototype.onSetContent = function(ev, editor) {
|
||||
this.$textarea.trigger('setContent.oc.richeditor', [this])
|
||||
}
|
||||
|
||||
RichEditor.prototype.onSyncBefore = function(html) {
|
||||
return this.syncBefore(html)
|
||||
RichEditor.prototype.onSyncContent = function(ev, editor, html) {
|
||||
var container = {
|
||||
html: html
|
||||
}
|
||||
|
||||
this.$textarea.trigger('syncContent.oc.richeditor', [this, container])
|
||||
|
||||
return container.html
|
||||
}
|
||||
|
||||
RichEditor.prototype.onFocus = function() {
|
||||
|
|
@ -407,28 +248,12 @@
|
|||
this.$el.removeClass('editor-focus')
|
||||
}
|
||||
|
||||
RichEditor.prototype.onKeydown = function(ev) {
|
||||
this.$textarea.trigger('keydown.oc.richeditor', [ev, this])
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.handleUiBlocksKeydown(ev)
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
RichEditor.prototype.onFigureKeydown = function(ev) {
|
||||
this.$textarea.trigger('figureKeydown.oc.richeditor', [ev, this])
|
||||
}
|
||||
|
||||
RichEditor.prototype.onEnter = function(ev) {
|
||||
this.$textarea.trigger('enter.oc.richeditor', [ev, this])
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.handleUiBlocksKeydown(ev)
|
||||
RichEditor.prototype.onKeydown = function(ev, editor, keyEv) {
|
||||
this.$textarea.trigger('keydown.oc.richeditor', [keyEv, this])
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
|
|
@ -436,8 +261,6 @@
|
|||
}
|
||||
|
||||
RichEditor.prototype.onChange = function(ev) {
|
||||
this.sanityCheckContent()
|
||||
this.$editor.trigger('mutate')
|
||||
this.$form.trigger('change')
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue