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
|
@ -24,8 +24,8 @@ class Content extends App.ControllerContent
|
|||
|
||||
for avatar in @$('.user.avatar')
|
||||
avatar = $(avatar)
|
||||
size = switch
|
||||
when avatar.hasClass('size-80') then 80
|
||||
size = switch
|
||||
when avatar.hasClass('size-80') then 80
|
||||
when avatar.hasClass('size-50') then 50
|
||||
else 40
|
||||
@createUniqueAvatar avatar, size, avatar.data('firstname'), avatar.data('lastname'), avatar.data('userid')
|
||||
|
@ -641,8 +641,76 @@ class ReferenceSetupWizard extends App.ControllerWizard
|
|||
@agentEmail.add(@agentFirstName).add(@agentLastName).val('')
|
||||
@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
|
||||
})
|
||||
|
||||
App.Config.set( 'LayoutRef', { prio: 1700, parent: '#current_user', name: 'Layout Reference', target: '#layout_ref', role: [ 'Admin' ] }, 'NavBarRight' )
|
||||
@$('.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' )
|
|
@ -1488,16 +1488,17 @@ class ArticleView extends App.Controller
|
|||
|
||||
# add quoted text if needed
|
||||
selectedText = App.ClipBoard.getSelected()
|
||||
|
||||
if selectedText
|
||||
body = @ui.el.find('[data-name="body"]').html() || ''
|
||||
|
||||
# quote text
|
||||
selectedText = App.Utils.textCleanup( selectedText )
|
||||
selectedText = App.Utils.quote( selectedText )
|
||||
#selectedText = App.Utils.quote( selectedText )
|
||||
|
||||
# convert to html
|
||||
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
|
||||
|
||||
|
|
|
@ -77,3 +77,52 @@ class App.Utils
|
|||
'> ' + match
|
||||
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 = {
|
||||
mode: 'richtext',
|
||||
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 ) {
|
||||
|
@ -28,26 +54,157 @@
|
|||
this.options.placeholder = this.$element.data('placeholder')
|
||||
}
|
||||
|
||||
// link input
|
||||
if ( !this.options.multiline ) {
|
||||
editorMode = Medium.inlineMode
|
||||
this.preventInput = false
|
||||
|
||||
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
|
||||
var validation = function(element) {
|
||||
// check if rich text key is pressed
|
||||
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
|
||||
var parent = $(element).parent().parent()
|
||||
var parent = this.$element.parent().parent()
|
||||
if ( parent.hasClass('controls') ) {
|
||||
parent.addClass('has-error')
|
||||
setTimeout($.proxy(function(){
|
||||
|
@ -59,31 +216,15 @@
|
|||
|
||||
// set validation on element
|
||||
else {
|
||||
$(element).addClass('invalid')
|
||||
this.$element.addClass('invalid')
|
||||
setTimeout($.proxy(function(){
|
||||
$(element).removeClass('invalid')
|
||||
this.$element.removeClass('invalid')
|
||||
}, this), 1000)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
new Medium({
|
||||
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']
|
||||
},
|
||||
});
|
||||
return true
|
||||
}
|
||||
|
||||
// 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/organization_profile">Organization Profile</a></li>
|
||||
<li><a href="#layout_ref/setup">Setup Wizard</a></li>
|
||||
<li><a href="#layout_ref/richtext">Richtext</a></li>
|
||||
</ul>
|
||||
|
||||
</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;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.u-unclickable {
|
||||
pointer-events: none;
|
||||
|
|
Loading…
Reference in a new issue