Create custom redactor stylesheet, add figure and quote redactor plugins. So v. cool! :-)

This will break the stretch implementation, since we no longer using iframe. Att: @alekseybobkov
This commit is contained in:
Sam Georges 2014-09-18 19:20:47 +10:00
parent bb78bb5fb3
commit d31f2eb97e
8 changed files with 2877 additions and 164 deletions

View File

@ -52,9 +52,11 @@ class RichEditor extends FormWidgetBase
// Plugins
$this->addJs('js/plugin.cleanup.js', 'core');
$this->addJs('js/plugin.fullscreen.js', 'core');
$this->addJs('js/plugin.figure.js', 'core');
$this->addJs('js/plugin.quote.js', 'core');
// Redactor
$this->addCss('vendor/redactor/redactor.css', 'core');
// $this->addCss('vendor/redactor/redactor.css', 'core');
$this->addJs('vendor/redactor/redactor.js', 'core');
// Rich editor

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,295 @@
(function ($) {
'use strict';
window.RedactorPlugins = window.RedactorPlugins || {}
var Figure = function (redactor) {
this.redactor = redactor
this.toolbar = {}
this.init()
}
Figure.prototype = {
control: {
up : { classSuffix: 'arrow-up' },
down : { classSuffix: 'arrow-down' },
'|' : { classSuffix: 'divider' },
remove: { classSuffix: 'delete' }
},
controlGroup: ['up', 'down', 'remove'],
init: function () {
this.observeCaptions()
this.observeToolbars()
this.observeKeyboard()
},
observeCaptions: function () {
/*
* Adding a line-break to empty captions and citations on click will place the cursor in the expected place
*/
this.redactor.$editor.on('click', 'figcaption:empty, cite:empty', $.proxy(function (event) {
$(event.target).prepend('<br />')
this.redactor.selectionEnd(event.target)
event.stopPropagation()
}, this))
/*
* Remove generated line-breaks empty figcaptions
*/
$(window).on('click', $.proxy(this.cleanCaptions, this))
this.redactor.$editor.on('blur', $.proxy(this.cleanCaptions, this))
this.redactor.$editor.closest('form').one('submit', $.proxy(this.clearCaptions, this))
/*
* Prevent user from removing captions or citations with delete/backspace keys
*/
this.redactor.$editor.on('keydown', $.proxy(function (event) {
var current = this.redactor.getCurrent(),
isEmpty = !current.length,
isCaptionNode = !!$(current).closest('figcaption, cite').length,
isDeleteKey = $.inArray(event.keyCode, [this.redactor.keyCode.BACKSPACE, this.redactor.keyCode.DELETE]) >= 0
if (isEmpty && isDeleteKey && isCaptionNode) {
event.preventDefault()
}
}, this))
},
cleanCaptions: function () {
this.redactor.$editor.find('figcaption, cite').filter(function () {
return $(this).text() == ''
}).empty()
},
clearCaptions: function () {
this.redactor.$editor.find('figcaption, cite').filter(function () {
return $(this).text() == ''
}).remove()
if (this.redactor.opts.visual) {
this.redactor.sync()
}
},
showToolbar: function (event) {
var $figure = $(event.currentTarget),
$toolbar = this.getToolbar(type).data('figure', $figure).prependTo($figure),
type = $figure.data('type') || 'default'
if (this.redactor[type] && this.redactor[type].onShow) {
this.redactor[type].onShow($figure, $toolbar)
}
},
hideToolbar: function (event) {
$(event.currentTarget).find('.wy-figure-controls').appendTo(this.redactor.$box)
},
observeToolbars: function () {
/*
* Before clicking a command, make sure we save the current node within the editor
*/
this.redactor.$editor.on('mousedown', '.wy-figure-controls', $.proxy(function () {
event.preventDefault()
this.current = this.redactor.getCurrent()
}, this))
this.redactor.$editor.on('click', '.wy-figure-controls span, .wy-figure-controls a', $.proxy(function (event) {
event.stopPropagation()
var $target = $(event.currentTarget),
command = $target.data('command'),
$figure = $target.closest('figure'),
plugin = this.redactor[$figure.data('type')]
this.command(command, $figure, plugin)
}, this))
this.redactor.$editor.on('keydown', function () {
$(this).find('figure').triggerHandler('mouseleave')
})
/*
* Mobile
*/
if (this.redactor.isMobile()) {
/*
* If $editor is focused, click doesn't seem to fire
*/
this.redactor.$editor.on('touchstart', 'figure', function (event) {
if (event.target.nodeName !== 'FIGCAPTION' && $(event.target).parents('.wy-figure-controls').length) {
$(this).trigger('click', event)
}
})
this.redactor.$editor.on('click', 'figure', $.proxy(function (event) {
if (event.target.nodeName !== 'FIGCAPTION') {
this.redactor.$editor.trigger('blur')
}
this.showToolbar(event)
}, this))
}
/*
* Desktop
*/
else {
/*
* Move toolbar into figure on mouseenter
*/
this.redactor.$editor.on('mouseenter', 'figure', $.proxy(this.showToolbar, this))
/*
* Remove toolbar from figure on mouseleave
*/
this.redactor.$editor.on('mouseleave', 'figure', $.proxy(this.hideToolbar, this))
}
},
getToolbar: function (type) {
if (this.toolbar[type]) {
return this.toolbar[type]
}
var controlGroup = (this.redactor[type] && this.redactor[type].controlGroup) || this.controlGroup,
controls = $.extend({}, this.control, (this.redactor[type] && this.redactor[type].control) || {}),
$controls = this.buildControls(controlGroup, controls),
$toolbar = $('<div class="wy-figure-controls">').append($controls)
this.toolbar[type] = $toolbar
return $toolbar
},
buildControls: function (controlGroup, controls) {
var $controls = $()
$.each(controlGroup, $.proxy(function (index, command) {
var control
/*
* Basic command
*/
if (typeof command === 'string') {
control = controls[command]
$controls = $controls.add($('<span>', {
'class': 'wy-figure-controls-' + control.classSuffix,
'text': control.text
}).data({
command: command,
control: control
}))
}
/*
* Dropdown
*/
else if (typeof command === 'object') {
$.each(command, $.proxy(function (text, commands) {
var dropdown = $('<span>').text(' ' + text).addClass('wy-figure-controls-table wy-dropdown')
$('<span class="caret">').appendTo(dropdown)
var list = $('<dl class="wy-dropdown-menu wy-dropdown-bubble wy-dropdown-arrow wy-dropdown-arrow-left">').appendTo(dropdown)
dropdown.on('mouseover', function () { list.show() })
dropdown.on('mouseout', function () { list.hide() })
$.each(commands, $.proxy(function (index, command) {
control = controls[command]
if (command === '|') {
$('<dd class="divider">').appendTo(list)
}
else {
$('<a>', {
text: control.text
}).data({
command: command,
control: control
}).appendTo($('<dd>').appendTo(list))
}
}, this))
$controls = $controls.add(dropdown)
}, this))
}
}, this))
return $controls
},
command: function (command, $figure, plugin) {
/*
* Move the toolbar before carrying out the command so it doesn't break when undoing/redoing
*/
$figure.find('.wy-figure-controls').appendTo(this.redactor.$box)
/*
* Maintain undo history
*/
this.redactor.bufferSet(this.redactor.$editor.html())
/*
* Shared functions
*/
switch (command) {
case 'up':
$figure.prev().before($figure)
break
case 'down':
$figure.next().after($figure)
break
case 'remove':
$figure.remove()
break
default:
if (plugin && plugin.command) {
plugin.command(command, $figure, $(this.current))
}
break
}
this.redactor.sync()
},
observeKeyboard: function () {
var redactor = this.redactor
redactor.$editor.on('keydown', function (event) {
/*
* Node at cursor
*/
var currentNode = redactor.getBlock()
/*
* Delete key
*/
if (event.keyCode === 8 && !redactor.getCaretOffset(currentNode) && currentNode.previousSibling && currentNode.previousSibling.nodeName === 'FIGURE') {
event.preventDefault()
}
})
}
}
window.RedactorPlugins.figure = {
init: function () {
this.figure = new Figure(this)
}
}
}(jQuery));

View File

@ -0,0 +1,148 @@
(function ($) {
'use strict';
window.RedactorPlugins = window.RedactorPlugins || {};
var Quote = function (redactor) {
this.redactor = redactor
this.init()
}
Quote.prototype = {
control: {
left : { classSuffix: 'arrow-left' },
right : { classSuffix: 'arrow-right' },
small : { classSuffix: 'small', text: 'S' },
medium : { classSuffix: 'medium', text: 'M' },
large : { classSuffix: 'large', text: 'L' },
resizeFull : { classSuffix: 'resize-full' },
resizeSmall : { classSuffix: 'resize-small' }
},
controlGroup: ['left', 'up', 'down', 'right', '|', 'small', 'medium', 'large', 'resizeFull', 'resizeSmall', 'remove'],
init: function () {
this.redactor.$editor.on('focus', $.proxy(this.addCites, this))
this.addCites()
this.observe()
},
addCites: function () {
/*
* Find any quotes missing citations and add an empty one
*/
this.redactor.$editor
.find('figure[data-type=quote] blockquote:not(:has(cite))')
.each(function () {
$(this).append('<cite>')
})
},
observe: function () {
this.redactor.$editor.on('mutate', $.proxy(this.orphanCheck, this))
},
orphanCheck: function () {
this.redactor.$editor.find('blockquote').filter(function () {
return $(this).closest('figure').length == 0
}).each(function () {
$(this).append($('<cite />'))
$(this).wrap('<figure data-type="quote" />')
})
},
onShow: function ($figure, $toolbar) {
$toolbar.children().removeClass('on')
if ($figure.hasClass('wy-figure-medium')) {
$toolbar.find('.wy-figure-controls-medium').addClass('on')
}
else if ($figure.hasClass('wy-figure-large')) {
$toolbar.find('.wy-figure-controls-large').addClass('on')
}
else {
$toolbar.find('.wy-figure-controls-small').addClass('on')
}
if ($figure.hasClass('wy-figure-left')) {
$toolbar.find('.wy-figure-controls-arrow-left').addClass('on')
$toolbar.find('.wy-figure-controls-resize-small').hide()
$toolbar.find('.wy-figure-controls-resize-full').show()
}
else if ($figure.hasClass('wy-figure-right')) {
$toolbar.find('.wy-figure-controls-arrow-right').addClass('on')
$toolbar.find('.wy-figure-controls-resize-small').hide()
$toolbar.find('.wy-figure-controls-resize-full').show()
}
else {
$toolbar.find('.wy-figure-controls-resize-small').show()
$toolbar.find('.wy-figure-controls-resize-full').hide()
}
},
command: function (command, $figure) {
switch (command) {
case 'left':
$figure.removeClass('wy-figure-right').addClass('wy-figure-left')
break
case 'right':
$figure.removeClass('wy-figure-left').addClass('wy-figure-right')
break
case 'resize_full':
$figure.removeClass('wy-figure-left wy-figure-right')
break
case 'resize_small':
$figure.addClass('wy-figure-left')
break
case 'small':
$figure.removeClass('wy-figure-medium wy-figure-large').addClass('wy-figure-small')
break
case 'medium':
$figure.removeClass('wy-figure-small wy-figure-large').addClass('wy-figure-medium')
break
case 'large':
$figure.removeClass('wy-figure-small wy-figure-medium').addClass('wy-figure-large')
break
}
},
toggle: function () {
this.redactor.formatQuote()
var $target = $(this.redactor.getBlock() || this.redactor.getCurrent())
if ($target.is('blockquote')) {
$target.append($('<cite />'))
$target.wrap('<figure data-type="quote" />')
}
else {
$target.find('cite').remove()
$target.closest('figure').before($target).remove()
}
this.redactor.sync()
}
}
window.RedactorPlugins.quote = {
init: function () {
this.quote = new Quote(this)
this.buttonAddBefore('link', 'quote', 'Quote', $.proxy(this.quote.toggle, this.quote))
this.buttonGet('quote').addClass('redactor_btn_quote').removeClass('redactor-btn-image')
}
}
}(jQuery));

View File

@ -61,7 +61,7 @@
initCallback: function() { self.build() },
changeCallback: function() {
self.sanityCheckContent(this.$editor)
// this.$editor.trigger('mutate')
this.$editor.trigger('mutate')
self.$form.trigger('change')
if (self.$dataLocker)
@ -74,7 +74,7 @@
}
// redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'table', 'quote']
redactorOptions.plugins = ['cleanup', 'fullscreen']
redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'quote']
this.$textarea.redactor(redactorOptions)
}

View File

@ -0,0 +1,247 @@
//
// Figures
//
.redactor_editor {
figure {
position: relative;
}
figcaption {
text-align: center;
line-height: @line-height-computed;
font-size: @font-size-base;
}
figure[data-type=table] {
clear: both;
}
figure[data-type=video] {
position: relative;
margin-bottom: @line-height-computed;
text-align: center;
clear: both;
p {
margin: 0;
}
&.wy-figure-full {
&:before {
position: relative;
padding-bottom: 51%;
width: 100%;
height: 0;
content: "";
display: block;
}
iframe {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
}
}
figure[data-type=image] {
position: relative;
margin-bottom: @line-height-computed;
.wy-figure-controls {
top: 0px;
}
img {
width: 100%;
}
&.wy-figure-large {
width: 100%;
clear: both;
}
&.wy-figure-medium {
width: 50%;
}
&.wy-figure-small {
width: 33%;
}
}
figure[data-type=quote] {
font-family: Georgia, serif;
margin-bottom: @line-height-computed;
margin-left: @line-height-computed;
font-style: italic;
position: relative;
border-left: solid 4px @color-border;
padding-left: @line-height-computed;
figcaption {
font-weight: bold;
text-align: left;
}
.wy-figure-controls {
margin-left: -5px;
}
&.wy-figure-medium {
&, blockquote { font-size: 20px; }
}
&.wy-figure-large {
&, blockquote { font-size: 24px; }
}
&.wy-figure-right {
width: 33%;
}
&.wy-figure-left {
width: 33%;
border-left: none;
border-right: solid 5px @text-color;
padding-left: 0;
padding-right: @line-height-computed;
margin-left: 0;
margin-right: @line-height-computed;
.wy-figure-controls {
margin-left: 0;
margin-right: -5px;
}
}
cite {
display: block;
text-align: left;
font-weight: bold;
&:before {
content: "\2014\00a0";
}
&:empty:before {
opacity: 0.4;
content: "\2014 Type to add citation (optional)";
}
}
}
}
.redactor_box {
figure:hover .wy-figure-controls {
display: block
}
.wy-figure-controls {
background: @color-richeditor-toolbar !important;
padding: 0;
position: absolute;
display: none;
min-width: 100%;
white-space: nowrap;
left: 0;
height: 30px;
top: -30px;
margin: 0 auto;
font-family: @font-family-base;
line-height: @line-height-computed;
font-style: normal;
z-index: @richeditor-zindex + 500;
text-align: center;
}
.wy-figure-controls span {
display: inline-block;
border: none;
background: none;
color: @color-richeditor-toolbar-btn-color;
vertical-align: top;
font-size: 14px;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
&:before {
line-height: 24px;
}
&:hover {
background: rgba(255,255,255,.3);
color: #fff;
background: @color-richeditor-toolbar-btn-bg-hover;
color: @color-richeditor-toolbar-btn-color-hover;
}
&.on {
background: #fff;
color: @gray;
background-color: @color-richeditor-toolbar-btn-bg-active;
color: @color-richeditor-toolbar-btn-color-hover;
}
}
.wy-figure-controls span {
&.wy-figure-controls-divider {
width: 1px;
background: @color-border;
padding: 0;
margin: 0 4px;
cursor: normal;
}
&.wy-figure-controls-small {
font-size: 7px;
}
&.wy-figure-controls-medium {
font-size: 10px;
}
&.wy-figure-controls-arrow-left:before {
.icon(@arrow-left);
}
&.wy-figure-controls-arrow-right:before {
.icon(@arrow-right);
}
&.wy-figure-controls-arrow-up:before {
.icon(@arrow-up);
}
&.wy-figure-controls-arrow-down:before {
.icon(@arrow-down);
}
&.wy-figure-controls-resize-full:before {
.icon(@expand);
}
&.wy-figure-controls-resize-small:before {
.icon(@compress);
}
&.wy-figure-controls-delete {
margin-left: @line-height-computed;
&:before { .icon(@trash-o); }
&:hover { background: @color-btn-danger; }
}
&.wy-figure-controls-table {
width: auto;
padding-left: @line-height-computed / 4;
text-align: left;
&:before { .icon(@table); }
}
}
.wy-figure-right {
float: right;
margin-left: @line-height-computed;
.wy-figure-controls {
right: 0;
}
}
.wy-figure-left {
float: left;
margin-right: @line-height-computed;
}
@media (max-width: @menu-breakpoint-max) {
figure[data-type=image] {
width: 100% !important;
float: none !important;
margin-left: 0;
margin-right: 0;
}
figure[data-type=video] iframe {
width: 100% !important;
height: auto !important;
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,9 @@
@richeditor-zindex: 1060;
@richeditor-gutter: 20px;
@import "redactor.less";
@import "figures.less";
.field-flush .field-richeditor {
&, &.editor-focus {
border: none;
@ -12,9 +15,12 @@
.field-richeditor {
border: 1px solid @color-form-field-border;
&, .redactor_box, .redactor_toolbar {
&, .redactor_box {
.border-radius(5px);
}
.redactor_toolbar {
.border-top-radius(3px);
}
&.editor-focus {
border-color: @color-form-field-border-focus;
@ -28,98 +34,33 @@
}
//
// Redactor Box
// Stretch
//
.field-richeditor .redactor_box,
.redactor_box_fullscreen.redactor_box {
border: none;
iframe {
border: none;
}
.redactor_toolbar {
.border-top-radius(3px);
.border-bottom-radius(0);
background: @color-richeditor-toolbar;
border-bottom: none;
.box-shadow(none);
border: none;
.field-richeditor.stretch {
.redactor_box {
display: block;
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
li.redactor_btn_right {
float: right;
margin-right: 2px;
}
li a {
zoom: 1;
font-size: 14px;
color: @color-richeditor-toolbar-btn-color;
width: 20px;
line-height: 20px;
text-align: center;
&:hover {
outline: none;
background-color: @color-richeditor-toolbar-btn-bg-hover;
color: @color-richeditor-toolbar-btn-color-hover;
}
&:active, &.redactor_act {
outline: none;
background-color: @color-richeditor-toolbar-btn-bg;
color: @color-richeditor-toolbar-btn-color-hover;
}
}
}
&.stretch {
.redactor_box {
.redactor_toolbar {
display: block;
position: relative;
height: 100%;
border-bottom: none;
position: absolute;
top: 0;
width: 100%;
overflow: hidden;
}
.redactor_toolbar {
display: block;
border-bottom: none;
position: absolute;
top: 0;
width: 100%;
}
.redactor_editor {
height: 100% !important;
}
iframe, textarea {
display: block;
position: absolute;
height: 100% !important;
}
iframe, textarea {
display: block;
position: absolute;
height: 100% !important;
}
}
}
//
// Fullscreen specific
//
.redactor_box_fullscreen {
background: @color-body-bg;
overflow-y: scroll !important;
.redactor_editor {
max-width: 960px;
margin: @richeditor-gutter auto !important;
padding: @richeditor-gutter;
}
}
//
// zIndex
//
.redactor_box_fullscreen {
z-index: @richeditor-zindex !important;
}
#redactor_modal_overlay,
#redactor_modal,
.redactor_dropdown {
z-index: @richeditor-zindex + 1 !important;
}