Init version of native contenteditable of browsers for testing.
This commit is contained in:
parent
7e1e6c6b6e
commit
642b110329
9 changed files with 4083 additions and 1991 deletions
|
@ -641,8 +641,76 @@ class ReferenceSetupWizard extends App.ControllerWizard
|
||||||
@agentEmail.add(@agentFirstName).add(@agentLastName).val('')
|
@agentEmail.add(@agentFirstName).add(@agentLastName).val('')
|
||||||
@agentFirstName.focus()
|
@agentFirstName.focus()
|
||||||
|
|
||||||
|
App.Config.set( 'layout_ref/richtext', ReferenceSetupWizard, 'Routes' )
|
||||||
|
|
||||||
|
class RichText extends App.ControllerContent
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
App.Config.set( 'layout_ref/setup', ReferenceSetupWizard, 'Routes' )
|
@$('.js-text-oneline').ce({
|
||||||
|
mode: 'textonly'
|
||||||
|
multiline: false
|
||||||
|
maxlength: 250
|
||||||
|
})
|
||||||
|
|
||||||
|
@$('.js-text-multiline').ce({
|
||||||
|
mode: 'textonly'
|
||||||
|
multiline: true
|
||||||
|
maxlength: 250
|
||||||
|
})
|
||||||
|
|
||||||
|
@$('.js-text-richtext').ce({
|
||||||
|
mode: 'richtext'
|
||||||
|
multiline: true
|
||||||
|
maxlength: 250
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
@$('.js-textarea').on('keyup', (e) =>
|
||||||
|
console.log('KU')
|
||||||
|
textarea = @$('.js-textarea')
|
||||||
|
App.Utils.htmlClanup(textarea)
|
||||||
|
)
|
||||||
|
|
||||||
|
@$('.js-textarea').on('paste', (e) =>
|
||||||
|
console.log('paste')
|
||||||
|
#console.log('PPP', e, e.originalEvent.clipboardData)
|
||||||
|
|
||||||
|
execute = =>
|
||||||
|
|
||||||
|
# add marker for cursor
|
||||||
|
getFirstRange = ->
|
||||||
|
sel = rangy.getSelection();
|
||||||
|
if sel.rangeCount
|
||||||
|
sel.getRangeAt(0)
|
||||||
|
else
|
||||||
|
null
|
||||||
|
range = getFirstRange()
|
||||||
|
if range
|
||||||
|
el = document.createElement('span')
|
||||||
|
$(el).attr('data-cursor', 1)
|
||||||
|
range.insertNode(el)
|
||||||
|
rangy.getSelection().setSingleRange(range)
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
textarea = @$('.js-textarea')
|
||||||
|
App.Utils.htmlClanup(textarea)
|
||||||
|
|
||||||
|
# remove marker for cursor
|
||||||
|
textarea.find('[data-cursor=1]').focus()
|
||||||
|
textarea.find('[data-cursor=1]').remove()
|
||||||
|
@delay( execute, 1)
|
||||||
|
|
||||||
|
return
|
||||||
|
)
|
||||||
|
#editable.style.borderColor = '#54c8eb';
|
||||||
|
#aloha(editable);
|
||||||
|
return
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
@html App.view('layout_ref/richtext')()
|
||||||
|
|
||||||
|
App.Config.set( 'layout_ref/richtext', RichText, 'Routes' )
|
||||||
|
|
||||||
App.Config.set( 'LayoutRef', { prio: 1700, parent: '#current_user', name: 'Layout Reference', target: '#layout_ref', role: [ 'Admin' ] }, 'NavBarRight' )
|
App.Config.set( 'LayoutRef', { prio: 1700, parent: '#current_user', name: 'Layout Reference', target: '#layout_ref', role: [ 'Admin' ] }, 'NavBarRight' )
|
|
@ -1488,16 +1488,17 @@ class ArticleView extends App.Controller
|
||||||
|
|
||||||
# add quoted text if needed
|
# add quoted text if needed
|
||||||
selectedText = App.ClipBoard.getSelected()
|
selectedText = App.ClipBoard.getSelected()
|
||||||
|
|
||||||
if selectedText
|
if selectedText
|
||||||
body = @ui.el.find('[data-name="body"]').html() || ''
|
body = @ui.el.find('[data-name="body"]').html() || ''
|
||||||
|
|
||||||
# quote text
|
# quote text
|
||||||
selectedText = App.Utils.textCleanup( selectedText )
|
selectedText = App.Utils.textCleanup( selectedText )
|
||||||
selectedText = App.Utils.quote( selectedText )
|
#selectedText = App.Utils.quote( selectedText )
|
||||||
|
|
||||||
# convert to html
|
# convert to html
|
||||||
selectedText = App.Utils.text2html( selectedText )
|
selectedText = App.Utils.text2html( selectedText )
|
||||||
|
if selectedText
|
||||||
|
selectedText = "<div><br><br/></div><div><blockquote type=\"cite\">#{selectedText}</blockquote></div><div><br></div>"
|
||||||
|
|
||||||
articleNew.body = selectedText + body
|
articleNew.body = selectedText + body
|
||||||
|
|
||||||
|
|
|
@ -77,3 +77,52 @@ class App.Utils
|
||||||
'> ' + match
|
'> ' + match
|
||||||
else
|
else
|
||||||
'>'
|
'>'
|
||||||
|
|
||||||
|
@htmlRemoveTags: (textarea) ->
|
||||||
|
# remove tags, keep content
|
||||||
|
textarea.find('a, div, span, li, ul, ol, a, hr, blockquote, br').replaceWith( ->
|
||||||
|
$(@).contents()
|
||||||
|
)
|
||||||
|
|
||||||
|
@htmlRemoveRichtext: (textarea) ->
|
||||||
|
|
||||||
|
# remove style and class
|
||||||
|
textarea.find('div, span, li, ul, ol, a').removeAttr( 'style' ).removeAttr( 'class' ).removeAttr( 'title' )
|
||||||
|
|
||||||
|
# remove tags, keep content
|
||||||
|
textarea.find('a, li, ul, ol, a, hr').replaceWith( ->
|
||||||
|
$(@).contents()
|
||||||
|
)
|
||||||
|
|
||||||
|
@htmlClanup: (textarea) ->
|
||||||
|
|
||||||
|
# remove style and class
|
||||||
|
textarea.find('div, span, li, ul, ol, a').removeAttr( 'style' ).removeAttr( 'class' ).removeAttr( 'title' )
|
||||||
|
|
||||||
|
# remove tags & content
|
||||||
|
textarea.find('hr').remove()
|
||||||
|
|
||||||
|
# remove tags, keep content
|
||||||
|
textarea.find('a').replaceWith( ->
|
||||||
|
$(@).contents()
|
||||||
|
)
|
||||||
|
|
||||||
|
# replace tags with generic div
|
||||||
|
# New type of the tag
|
||||||
|
replacementTag = 'div';
|
||||||
|
|
||||||
|
# Replace all a tags with the type of replacementTag
|
||||||
|
textarea.find('h1, h2, h3, h4, h5, h6').each( ->
|
||||||
|
outer = this.outerHTML;
|
||||||
|
|
||||||
|
# Replace opening tag
|
||||||
|
regex = new RegExp('<' + this.tagName, 'i');
|
||||||
|
newTag = outer.replace(regex, '<' + replacementTag);
|
||||||
|
|
||||||
|
# Replace closing tag
|
||||||
|
regex = new RegExp('</' + this.tagName, 'i');
|
||||||
|
newTag = newTag.replace(regex, '</' + replacementTag);
|
||||||
|
|
||||||
|
$(@).replaceWith(newTag);
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,32 @@
|
||||||
defaults = {
|
defaults = {
|
||||||
mode: 'richtext',
|
mode: 'richtext',
|
||||||
multiline: true,
|
multiline: true,
|
||||||
|
allowKey: {
|
||||||
|
8: true, // backspace
|
||||||
|
9: true, // tab
|
||||||
|
16: true, // shift
|
||||||
|
17: true, // ctrl
|
||||||
|
18: true, // alt
|
||||||
|
20: true, // cabslock
|
||||||
|
37: true, // up
|
||||||
|
38: true, // right
|
||||||
|
39: true, // down
|
||||||
|
40: true, // left
|
||||||
|
91: true, // cmd left
|
||||||
|
92: true, // cmd right
|
||||||
|
},
|
||||||
|
extraAllowKey: {
|
||||||
|
65: true, // a + ctrl - select all
|
||||||
|
67: true, // c + ctrl - copy
|
||||||
|
86: true, // v + ctrl - paste
|
||||||
|
88: true, // x + ctrl - cut
|
||||||
|
90: true, // z + ctrl - undo
|
||||||
|
},
|
||||||
|
richTextFormatKey: {
|
||||||
|
66: true, // b
|
||||||
|
73: true, // i
|
||||||
|
85: true, // u
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function Plugin( element, options ) {
|
function Plugin( element, options ) {
|
||||||
|
@ -28,26 +54,157 @@
|
||||||
this.options.placeholder = this.$element.data('placeholder')
|
this.options.placeholder = this.$element.data('placeholder')
|
||||||
}
|
}
|
||||||
|
|
||||||
// link input
|
this.preventInput = false
|
||||||
if ( !this.options.multiline ) {
|
|
||||||
editorMode = Medium.inlineMode
|
this.init();
|
||||||
|
// bind
|
||||||
|
|
||||||
|
// bind paste
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Plugin.prototype.init = function () {
|
||||||
|
var _this = this
|
||||||
|
|
||||||
|
// handle enter
|
||||||
|
this.$element.on('keydown', function (e) {
|
||||||
|
console.log('keydown', e.keyCode)
|
||||||
|
if ( _this.preventInput ) {
|
||||||
|
console.log('preventInput', _this.preventInput)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// strap the return key being pressed
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
|
||||||
|
// disbale multi line
|
||||||
|
if ( !_this.options.multiline ) {
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// limit check
|
||||||
|
if ( !_this.maxLengthOk( true ) ) {
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$element.on('keyup', function (e) {
|
||||||
|
console.log('KU')
|
||||||
|
if ( _this.options.mode === 'textonly' ) {
|
||||||
|
console.log('REMOVE TAGS')
|
||||||
|
if ( !_this.options.multiline ) {
|
||||||
|
App.Utils.htmlRemoveTags(_this.$element)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
App.Utils.htmlRemoveRichtext(_this.$element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
App.Utils.htmlClanup(_this.$element)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// just paste text
|
||||||
|
this.$element.on('paste', function (e) {
|
||||||
|
console.log('paste')
|
||||||
|
if ( _this.options.mode === 'textonly' ) {
|
||||||
|
console.log('REMOVE TAGS')
|
||||||
|
if ( !_this.options.multiline ) {
|
||||||
|
App.Utils.htmlRemoveTags(_this.$element)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
App.Utils.htmlRemoveRichtext(_this.$element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
App.Utils.htmlClanup(_this.$element)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
if ( this.options.mode === 'textonly' ) {
|
||||||
|
e.preventDefault()
|
||||||
|
var text
|
||||||
|
if (window.clipboardData) { // IE
|
||||||
|
text = window.clipboardData.getData('Text')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text = (e.originalEvent || e).clipboardData.getData('text/plain')
|
||||||
|
}
|
||||||
|
var overlimit = false
|
||||||
|
if (text) {
|
||||||
|
|
||||||
|
// replace new lines
|
||||||
|
if ( !_this.options.multiline ) {
|
||||||
|
text = text.replace(/\n/g, '')
|
||||||
|
text = text.replace(/\r/g, '')
|
||||||
|
text = text.replace(/\t/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit length, limit paste string
|
||||||
|
if ( _this.options.maxlength ) {
|
||||||
|
var pasteLength = text.length
|
||||||
|
var currentLength = _this.$element.text().length
|
||||||
|
var overSize = ( currentLength + pasteLength ) - _this.options.maxlength
|
||||||
|
if ( overSize > 0 ) {
|
||||||
|
text = text.substr( 0, pasteLength - overSize )
|
||||||
|
overlimit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert new text
|
||||||
|
if (document.selection) { // IE
|
||||||
|
var range = document.selection.createRange()
|
||||||
|
range.pasteHTML(text)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.execCommand('inserttext', false, text)
|
||||||
|
}
|
||||||
|
_this.maxLengthOk( overlimit )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// disable rich text b/u/i
|
||||||
|
if ( this.options.mode === 'textonly' ) {
|
||||||
|
this.$element.on('keydown', function (e) {
|
||||||
|
if ( _this.richTextKey(e) ) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// link textarea
|
|
||||||
else if ( this.options.multiline && this.options.mode != 'richtext' ) {
|
|
||||||
editorMode = Medium.partialMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// rich text
|
}
|
||||||
else {
|
|
||||||
editorMode = Medium.richMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// max length validation
|
// check if rich text key is pressed
|
||||||
var validation = function(element) {
|
Plugin.prototype.richTextKey = function(e) {
|
||||||
|
// e.altKey
|
||||||
|
// e.ctrlKey
|
||||||
|
// e.metaKey
|
||||||
|
// on mac block e.metaKey + i/b/u
|
||||||
|
if ( !e.altKey && e.metaKey && this.options.richTextFormatKey[ e.keyCode ] ) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// on win block e.ctrlKey + i/b/u
|
||||||
|
if ( !e.altKey && e.ctrlKey && this.options.richTextFormatKey[ e.keyCode ] ) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// max length check
|
||||||
|
Plugin.prototype.maxLengthOk = function(typeAhead) {
|
||||||
|
var length = this.$element.text().length
|
||||||
|
if (typeAhead) {
|
||||||
|
length = length + 1
|
||||||
|
}
|
||||||
|
if ( length > this.options.maxlength ) {
|
||||||
|
|
||||||
// try to set error on framework form
|
// try to set error on framework form
|
||||||
var parent = $(element).parent().parent()
|
var parent = this.$element.parent().parent()
|
||||||
if ( parent.hasClass('controls') ) {
|
if ( parent.hasClass('controls') ) {
|
||||||
parent.addClass('has-error')
|
parent.addClass('has-error')
|
||||||
setTimeout($.proxy(function(){
|
setTimeout($.proxy(function(){
|
||||||
|
@ -59,31 +216,15 @@
|
||||||
|
|
||||||
// set validation on element
|
// set validation on element
|
||||||
else {
|
else {
|
||||||
$(element).addClass('invalid')
|
this.$element.addClass('invalid')
|
||||||
setTimeout($.proxy(function(){
|
setTimeout($.proxy(function(){
|
||||||
$(element).removeClass('invalid')
|
this.$element.removeClass('invalid')
|
||||||
}, this), 1000)
|
}, this), 1000)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new Medium({
|
return true
|
||||||
element: element,
|
|
||||||
modifier: 'auto',
|
|
||||||
placeholder: this.options.placeholder || '',
|
|
||||||
autofocus: false,
|
|
||||||
autoHR: false,
|
|
||||||
mode: editorMode,
|
|
||||||
maxLength: this.options.maxlength || -1,
|
|
||||||
maxLengthReached: validation,
|
|
||||||
tags: {
|
|
||||||
'break': 'br',
|
|
||||||
'horizontalRule': 'hr',
|
|
||||||
'paragraph': 'div',
|
|
||||||
'outerLevel': ['pre', 'blockquote', 'figure'],
|
|
||||||
'innerLevel': ['a', 'b', 'u', 'i', 'img', 'strong']
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get value
|
// get value
|
||||||
|
|
File diff suppressed because it is too large
Load diff
3755
app/assets/javascripts/app/lib/base/rangy-core.js
Executable file
3755
app/assets/javascripts/app/lib/base/rangy-core.js
Executable file
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,7 @@
|
||||||
<li><a href="#layout_ref/user_profile">User Profile</a></li>
|
<li><a href="#layout_ref/user_profile">User Profile</a></li>
|
||||||
<li><a href="#layout_ref/organization_profile">Organization Profile</a></li>
|
<li><a href="#layout_ref/organization_profile">Organization Profile</a></li>
|
||||||
<li><a href="#layout_ref/setup">Setup Wizard</a></li>
|
<li><a href="#layout_ref/setup">Setup Wizard</a></li>
|
||||||
|
<li><a href="#layout_ref/richtext">Richtext</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
26
app/assets/javascripts/app/views/layout_ref/richtext.jst.eco
Normal file
26
app/assets/javascripts/app/views/layout_ref/richtext.jst.eco
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<div class="main flex">
|
||||||
|
|
||||||
|
<h1>Richtext<h1>
|
||||||
|
|
||||||
|
<h2>Singleline / Textonly</h2>
|
||||||
|
|
||||||
|
<div class="text-bubble">
|
||||||
|
<div class="bubble-arrow"></div>
|
||||||
|
<div class="js-text-oneline" contenteditable="true" data-name="body"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Multiline / Textonly</h2>
|
||||||
|
|
||||||
|
<div class="text-bubble">
|
||||||
|
<div class="bubble-arrow"></div>
|
||||||
|
<div class="js-text-multiline" contenteditable="true" data-name="body"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Multiline / Richtext</h2>
|
||||||
|
|
||||||
|
<div class="text-bubble">
|
||||||
|
<div class="bubble-arrow"></div>
|
||||||
|
<div class="js-text-richtext" contenteditable="true" data-name="body"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -36,6 +36,9 @@ small {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.u-unclickable {
|
.u-unclickable {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
Loading…
Reference in a new issue