character_editor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9de43f73c9a9be5a52713b0e182eae7a3d77a5d4
4
+ data.tar.gz: 48d6f5ee42ea70ac33468e504d50a96ae9b2f86e
5
+ SHA512:
6
+ metadata.gz: 733d8129f5be1b895c043aaea78ba6cf692153f7c3c6c4d0e6fa00d55b8a33c50a3574cc5f16cf882f6c361f673d3acb9319d25c1f143ead6899c6f270b66f5b
7
+ data.tar.gz: 6a13445ba089a922eb5df7e77cb74d94117963ca3e6be89bf50a2832a9bb0d20012b80612d845f9d4062338a64f477891ed07422025a7621bdb1e515af8f1b8c
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in character.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,14 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ character_medium_editor (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ character_medium_editor!
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # [Character](http://github.com/slate-studio/character) Editor
2
+
3
+ Based on [MediumEditor](https://github.com/daviferreira/medium-editor) — Medium.com WYSIWYG editor clone.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems/package_task'
2
+
3
+ spec = Gem::Specification.load(Dir['*.gemspec'].first)
4
+ gem = Gem::PackageTask.new(spec)
5
+ gem.define()
@@ -0,0 +1,193 @@
1
+ ###!
2
+ * Character Editor
3
+ * Author: Alexander Kravets @ slatestudio.com
4
+ * Licensed under the MIT license
5
+
6
+ Usage:
7
+ $('#editor').editor({placeholder: 'Title' })
8
+ inst = $('#editor').data('editor')
9
+ inst.serialize()
10
+
11
+ ###
12
+
13
+ #= require_self
14
+ #= require character_editor/_selection
15
+ #= require character_editor/_toolbar
16
+
17
+ # Object - an object representing a concept that you want
18
+ # to model (e.g. a car)
19
+ @CharacterEditor =
20
+ options:
21
+ allowMultiParagraphSelection: true
22
+ anchorInputPlaceholder: 'Paste or type a link'
23
+ buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote']
24
+ delay: 0
25
+ diffLeft: 0
26
+ diffTop: -10
27
+ disableReturn: false
28
+ disableToolbar: false
29
+ forcePlainText: true
30
+ placeholder: 'Type your text...'
31
+ targetBlank: false
32
+ firstHeader: 'h3'
33
+ secondHeader: 'h4'
34
+ tabSpaces: ' '
35
+ viewSelector: 'body'
36
+ parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre']
37
+
38
+ _dataOptions: ->
39
+ result = {}
40
+ dataOptions = @$elem.attr('data-options')
41
+ if dataOptions
42
+ opts = dataOptions.split(';')
43
+
44
+ isNumber = (n) -> return !isNaN(parseFloat(n)) && isFinite(n)
45
+ removeQuotes = (str) -> str.replace(/^['\\/"]+|(;\s?})+|['\\/"]+$/g, '')
46
+ trim = (val) -> if typeof val == 'string' then removeQuotes($.trim(val)) else val
47
+
48
+ $.each opts, (i, opt) ->
49
+ p = opt.split(':')
50
+
51
+ if /true/i.test(p[1])
52
+ p[1] = true
53
+ else if /false/i.test(p[1])
54
+ p[1] = false
55
+ else if isNumber(p[1])
56
+ p[1] = parseFloat(p[1])
57
+
58
+ if p.length == 2 and p[0].length > 0
59
+ result[ trim(p[0]) ] = trim(p[1])
60
+
61
+ return result
62
+
63
+ init: (options, elem) ->
64
+ @elem = elem
65
+ @$elem = $(elem)
66
+
67
+ @options = $.extend({}, @options, @_dataOptions(), options)
68
+
69
+ @_build()
70
+
71
+ @_bind()
72
+
73
+ @
74
+
75
+ _build: ->
76
+ @$elem.attr('contenteditable', true)
77
+ @$elem.attr('data-editor-element', true)
78
+
79
+ @_setPlaceholder()
80
+
81
+ if not @options.disableToolbar
82
+ @_addToolbar()
83
+
84
+ _addToolbar: -> # create only one toolbar for now
85
+ @toolbar = window.characterEditorToolbar
86
+
87
+ if not @toolbar
88
+ $toolbarElement = $("<div id='character_editor_toolbar' class='character-editor-toolbar' />")
89
+ $(@options.viewSelector).append($toolbarElement)
90
+
91
+ @toolbar = Object.create(CharacterEditor.Toolbar).init(@options, $toolbarElement)
92
+
93
+ window.characterEditorToolbar = @toolbar
94
+
95
+ _setPlaceholder: ->
96
+ @$elem.attr('data-placeholder', @options.placeholder)
97
+
98
+ activatePlaceholder = (el) ->
99
+ if el.textContent.replace(/^\s+|\s+$/g, '') == ''
100
+ $(el).addClass('character-editor-placeholder')
101
+
102
+ activatePlaceholder(@elem)
103
+
104
+ @$elem.on 'blur keypress', (e) ->
105
+ $(@).removeClass('character-editor-placeholder')
106
+ if e.type != 'keypress'
107
+ activatePlaceholder(@)
108
+
109
+ _bind: ->
110
+ @_bindNewParagraph()
111
+ @_bindReturn()
112
+ @_bindTab()
113
+ @_bindPaste()
114
+
115
+ _bindNewParagraph: ->
116
+ @$elem.on 'keyup', (e) =>
117
+ node = getSelectionStart()
118
+
119
+ if node and node.getAttribute('data-editor-element') and node.children.length == 0 and !@options.disableReturn
120
+ document.execCommand('formatBlock', false, 'p')
121
+
122
+ if e.which == 13 and !e.shiftKey
123
+ node = getSelectionStart()
124
+ tagName = node.tagName.toLowerCase()
125
+
126
+ if !@options.disableReturn and tagName != 'li' and !isListItemChild(node)
127
+ document.execCommand('formatBlock', false, 'p')
128
+ if tagName == 'a'
129
+ document.execCommand('unlink', false, null)
130
+
131
+ _bindReturn: ->
132
+ @$elem.on 'keypress', (e) =>
133
+ if e.which == 13 and @options.disableReturn
134
+ e.preventDefault()
135
+
136
+ _bindTab: ->
137
+ @$elem.on 'keydown', (e) =>
138
+ if e.which == 9
139
+ tag = getSelectionStart().tagName.toLowerCase()
140
+ if tag == "pre"
141
+ e.preventDefault()
142
+ document.execCommand('insertHtml', null, @options.tabSpaces)
143
+
144
+ _bindPaste: ->
145
+ return if !@options.forcePlainText
146
+
147
+ @$elem.on 'paste', (e) =>
148
+ html = ''
149
+
150
+ $(@).removeClass('character-editor-placeholder')
151
+
152
+ if e.clipboardData and e.clipboardData.getData
153
+ e.preventDefault()
154
+
155
+ if !@options.disableReturn
156
+ paragraphs = e.clipboardData.getData('text/plain').split(/[\r\n]/g)
157
+ $.each paragraphs, (i, p) -> if p != '' then html += "<p>#{p}</p>"
158
+
159
+ document.execCommand('insertHTML', false, html)
160
+ else
161
+ document.execCommand('insertHTML', false, e.clipboardData.getData('text/plain'))
162
+
163
+ serialize: ->
164
+ @$elem.html().trim()
165
+
166
+ destroy: ->
167
+ @$elem.removeAttr('contenteditable')
168
+ @$elem.removeAttr('data-editor-element')
169
+ @$elem.removeAttr('data-placeholder')
170
+ @$elem.removeClass('character-editor-placeholder')
171
+
172
+ @$elem.off 'blur keypress keyup keydown paste'
173
+
174
+ if @toolbar
175
+ @toolbar.destroy()
176
+ delete @toolbar
177
+ delete window.characterEditorToolbar
178
+
179
+ # Object.create support test, and fallback for browsers without it
180
+ if typeof Object.create isnt "function"
181
+ Object.create = (o) ->
182
+ F = ->
183
+ F:: = o
184
+ new F()
185
+
186
+ # Create a plugin based on a defined object
187
+ $.plugin = (name, object) ->
188
+ $.fn[name] = (options) ->
189
+ @each ->
190
+ $.data @, name, Object.create(object).init(options, @) unless $.data(@, name)
191
+ return
192
+
193
+ $.plugin('editor', CharacterEditor)
@@ -0,0 +1,51 @@
1
+ # http://stackoverflow.com/questions/4176923/html-of-selected-text
2
+ # by Tim Down
3
+ window.getSelectionHtml = ->
4
+ html = ''
5
+
6
+ if window.getSelection != undefined
7
+ sel = window.getSelection()
8
+
9
+ if sel.rangeCount
10
+ container = document.createElement('div')
11
+
12
+ for i in [0..sel.rangeCount-1]
13
+ container.appendChild(sel.getRangeAt(i).cloneContents())
14
+
15
+ html = container.innerHTML
16
+
17
+ else if document.selection != undefined
18
+
19
+ if document.selection.type == 'Text'
20
+ html = document.selection.createRange().htmlText
21
+
22
+ return html
23
+
24
+ # http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
25
+ # by You
26
+ window.getSelectionStart = ->
27
+ node = document.getSelection().anchorNode
28
+ startNode = if node and node.nodeType == 3 then node.parentNode else node
29
+ return startNode
30
+
31
+ window.isListItemChild = (node) -> $(node).parents('li').length > 0
32
+
33
+ # http://stackoverflow.com/questions/5605401/insert-link-in-contenteditable-element
34
+ # by Tim Down
35
+ window.saveSelection = ->
36
+ sel = window.getSelection()
37
+
38
+ if sel.getRangeAt and sel.rangeCount
39
+ ranges = []
40
+ for i in [0..sel.rangeCount-1]
41
+ ranges.push(sel.getRangeAt(i))
42
+ return ranges
43
+
44
+ return false
45
+
46
+ window.restoreSelection = (savedSel) ->
47
+ sel = window.getSelection()
48
+ if savedSel
49
+ sel.removeAllRanges()
50
+ for i in [0..savedSel.length-1]
51
+ sel.addRange(savedSel[i])
@@ -0,0 +1,353 @@
1
+
2
+ @CharacterEditor.Toolbar =
3
+ init: (options, elem) ->
4
+ @options = $.extend({}, @options, options)
5
+
6
+ @elem = elem
7
+ @$elem = $(elem)
8
+
9
+ # this helps to not hide toolbar on selection (while toolbar button click)
10
+ @keepToolbarVisible = false
11
+
12
+ @_build()
13
+
14
+ @_bind()
15
+
16
+ @
17
+
18
+ options:
19
+ buttonLabels:
20
+ bold: '<i class="fa fa-bold"></i>'
21
+ italic : '<i class="fa fa-italic"></i>'
22
+ underline: '<i class="fa fa-underline"></i>'
23
+ strikethrough: '<i class="fa fa-strikethrough"></i>'
24
+ superscript: '<i class="fa fa-superscript"></i>'
25
+ subscript: '<i class="fa fa-subscript"></i>'
26
+ anchor: '<i class="fa fa-link"></i>'
27
+ image: '<i class="fa fa-picture-o"></i>'
28
+ quote: '<i class="fa fa-quote-right"></i>'
29
+ orderedlist: '<i class="fa fa-list-ol"></i>'
30
+ unorderedlist: '<i class="fa fa-list-ul"></i>'
31
+ pre: '<i class="fa fa-code fa-lg"></i>'
32
+ header1: '<b>H1</b>'
33
+ header2: '<b>H1</b>'
34
+
35
+ _buttonTemplate: (key) ->
36
+ l = @options.buttonLabels
37
+ classPrefix = 'character-editor-action'
38
+ templates =
39
+ bold: "<li><button class='#{classPrefix} #{classPrefix}-bold'
40
+ data-action='bold' data-element='b'>#{ l.bold }</button></li>"
41
+
42
+ italic: "<li><button class='#{classPrefix} #{classPrefix}-italic'
43
+ data-action='italic' data-element='i'>#{ l.italic }</button></li>"
44
+
45
+ underline: "<li><button class='#{classPrefix} #{classPrefix}-underline'
46
+ data-action='underline' data-element='u'>#{ l.underline }</button></li>"
47
+
48
+ strikethrough: "<li><button class='#{classPrefix} #{classPrefix}-strikethrough'
49
+ data-action='strikethrough' data-element='strike'>#{ l.strikethrough }</button></li>"
50
+
51
+ superscript: "<li><button class='#{classPrefix} #{classPrefix}-superscript'
52
+ data-action='superscript' data-element='sup'>#{ l.superscript }</button></li>"
53
+
54
+ subscript: "<li><button class='#{classPrefix} #{classPrefix}-subscript'
55
+ data-action='subscript' data-element='sub'>#{ l.subscript }</button></li>"
56
+
57
+ anchor: "<li><button class='#{classPrefix} #{classPrefix}-anchor'
58
+ data-action='anchor' data-element='a'>#{ l.anchor }</button></li>"
59
+
60
+ image: "<li><button class='#{classPrefix} #{classPrefix}-image'
61
+ data-action='image' data-element='img'>#{ l.image }</button></li>"
62
+
63
+ quote: "<li><button class='#{classPrefix} #{classPrefix}-quote'
64
+ data-action='append-blockquote' data-element='blockquote'>#{ l.quote }</button></li>"
65
+
66
+ orderedlist: "<li><button class='#{classPrefix} #{classPrefix}-orderedlist'
67
+ data-action='insertorderedlist' data-element='ol'>#{ l.orderedlist }</button></li>"
68
+
69
+ unorderedlist: "<li><button class='#{classPrefix} #{classPrefix}-unorderedlist'
70
+ data-action='insertunorderedlist' data-element='ul'>#{ l.unorderedlist }</button></li>"
71
+
72
+ pre: "<li><button class='#{classPrefix} #{classPrefix}-pre'
73
+ data-action='append-pre' data-element='pre'>#{ l.pre }</button></li>"
74
+
75
+ header1: "<li><button class='#{classPrefix} #{classPrefix}-header1'
76
+ data-action='append-#{ @options.firstHeader }' data-element='#{ @options.firstHeader }'>#{ l.header1 }</button></li>"
77
+
78
+ header2: "<li><button class='#{classPrefix} #{classPrefix}-header2'
79
+ data-action='append-#{ @options.secondHeader }' data-element='#{ @options.secondHeader }'>#{ l.header2 }</button></li>"
80
+ return templates[key]
81
+
82
+ _build: ->
83
+ html = """<ul id='character_editor_toolbar_buttons'>"""
84
+
85
+ $.each @options.buttons, (i, key) => html += @_buttonTemplate(key)
86
+
87
+ html += """</ul>
88
+ <div class='character-editor-toolbar-form-anchor' id='character_editor_toolbar_form_anchor'>
89
+ <input type='text' value='' placeholder='#{ @options.anchorInputPlaceholder }'><a href='#'>&times;</a>
90
+ </div>"""
91
+ @$elem.html(html)
92
+
93
+ @$toolbarButtons = @$elem.find('#character_editor_toolbar_buttons')
94
+ @$anchorForm = @$elem.find('#character_editor_toolbar_form_anchor')
95
+ @$anchorInput = @$anchorForm.children('input')
96
+
97
+ buttonWidth = @$toolbarButtons.find('button').first().width()
98
+ @$anchorInput.css('width', (@options.buttons.length - 1) * buttonWidth + @options.buttons.length - 1)
99
+
100
+ @$toolbarButtons.show()
101
+ @$anchorForm.hide()
102
+
103
+ _bind: ->
104
+ @_bindWindowResize()
105
+ @_bindButtons()
106
+ @_bindAnchorForm()
107
+ @_bindSelect()
108
+
109
+ _hide: ->
110
+ @keepToolbarVisible = false
111
+ @$elem.removeClass('character-editor-toolbar-active')
112
+
113
+ _show: ->
114
+ timer = ''
115
+
116
+
117
+ @$anchorForm.hide()
118
+ @$toolbarButtons.show()
119
+ @keepToolbarVisible = false
120
+
121
+ clearTimeout(timer)
122
+
123
+ timer = setTimeout ( =>
124
+ if !@$elem.hasClass('character-editor-toolbar-active')
125
+ @$elem.addClass('character-editor-toolbar-active')
126
+ ), 100
127
+
128
+ _setPosition: ->
129
+ $contentView = $(@options.viewSelector)
130
+
131
+ contentLeftOffset = $contentView.offset().left
132
+ contentTopOffset = $contentView.offset().top
133
+ contentScrollTop = $contentView.scrollTop()
134
+ contentInnerWidth = $contentView.innerWidth()
135
+
136
+ buttonHeight = 30
137
+ selection = window.getSelection()
138
+ range = selection.getRangeAt(0)
139
+ boundary = range.getBoundingClientRect()
140
+
141
+ offsetWidth = @$elem.get(0).offsetWidth
142
+ offsetHeight = @$elem.get(0).offsetHeight
143
+
144
+ defaultLeft = (@options.diffLeft) - (offsetWidth / 2)
145
+ middleBoundary = (boundary.left + boundary.right) / 2 - contentLeftOffset
146
+ halfOffsetWidth = offsetWidth / 2
147
+
148
+
149
+ if boundary.top < buttonHeight
150
+ @$elem.addClass('character-toolbar-arrow-over')
151
+ @$elem.removeClass('character-toolbar-arrow-under')
152
+ @$elem.css 'top', buttonHeight + boundary.bottom - @options.diffTop + window.pageYOffset - offsetHeight + 'px'
153
+ else
154
+ @$elem.addClass('character-toolbar-arrow-under')
155
+ @$elem.removeClass('character-toolbar-arrow-over')
156
+ @$elem.css 'top', boundary.top + @options.diffTop - contentTopOffset + contentScrollTop - offsetHeight + 'px'
157
+
158
+ edgeOffset = 5
159
+
160
+ if middleBoundary < halfOffsetWidth
161
+ @$elem.css 'left', defaultLeft + halfOffsetWidth + edgeOffset + 'px'
162
+ else if (contentInnerWidth - middleBoundary) < halfOffsetWidth
163
+ @$elem.css 'left', contentInnerWidth + defaultLeft - halfOffsetWidth - edgeOffset + 'px'
164
+ else
165
+ @$elem.css 'left', defaultLeft + middleBoundary + 'px'
166
+
167
+ # TODO: update arrow layout to make it possible to move it on edge cases
168
+ #
169
+ #
170
+ #
171
+ #
172
+ #
173
+ #
174
+ #
175
+
176
+ _bindSelect: ->
177
+ if !@options.disableToolbar
178
+ timer = ''
179
+ toolbar = @
180
+
181
+ $(@options.viewSelector).on 'mouseup keyup blur', (e) =>
182
+ console.log $(e.target).parents('#character_editor_toolbar')
183
+
184
+ clearTimeout(timer)
185
+ timer = setTimeout ( -> console.log 'select' ; toolbar._checkSelection() ), @options.delay
186
+
187
+ _bindWindowResize: ->
188
+ timer = ''
189
+ $(window).on 'resize', =>
190
+ clearTimeout(timer)
191
+ timer = setTimeout ( => if @$elem.hasClass('character-editor-toolbar-active') then @_setPosition() ), 100
192
+
193
+ _getActiveEditor: ->
194
+ $editorElement = $(@selection.getRangeAt(0).commonAncestorContainer).parents('[data-editor-element]')
195
+ if $editorElement then $editorElement.data('editor') else false
196
+
197
+ _checkSelection: ->
198
+ if not @keepToolbarVisible
199
+ @selection = window.getSelection()
200
+
201
+ if @selection.toString().trim() == ''
202
+ return @_hide()
203
+
204
+ activeEditor = @_getActiveEditor()
205
+ if !activeEditor or activeEditor.options.disableToolbar
206
+ return @_hide()
207
+
208
+ selectionHtml = getSelectionHtml()
209
+ selectionHtml = selectionHtml.replace(/<[\S]+><\/[\S]+>/gim, '')
210
+
211
+ hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g)
212
+
213
+ if !@options.allowMultiParagraphSelection and hasMultiParagraphs
214
+ return @_hide()
215
+
216
+ @_setButtonStates()
217
+ @_setPosition()
218
+ @_show()
219
+
220
+ _setButtonStates: ->
221
+ $buttons = @$elem.find('button')
222
+ $buttons.removeClass('character-editor-button-active')
223
+
224
+ parentNode = @selection.anchorNode
225
+
226
+ if !parentNode.tagName
227
+ parentNode = @selection.anchorNode.parentNode
228
+
229
+ while parentNode.tagName != undefined and @options.parentElements.indexOf(parentNode.tagName) == -1
230
+ tag = parentNode.tagName.toLowerCase()
231
+ @_activateButton(tag)
232
+ parentNode = parentNode.parentNode
233
+
234
+ _activateButton: (tag) ->
235
+ $el = @$elem.find('[data-element="' + tag + '"]').first()
236
+ if $el.length and !$el.hasClass('character-editor-button-active')
237
+ $el.addClass('character-editor-button-active')
238
+
239
+ _getSelectionData: (el) ->
240
+ if el and el.tagName
241
+ tagName = el.tagName.toLowerCase()
242
+
243
+ while el and @options.parentElements.indexOf(tagName) == -1
244
+ el = el.parentNode
245
+ if el and el.tagName
246
+ tagName = el.tagName.toLowerCase()
247
+
248
+ return { el: el, tagName: tagName }
249
+
250
+ _execFormatBlock: (el) ->
251
+ selectionData = @_getSelectionData(@selection.anchorNode)
252
+
253
+ # FF handles blockquote differently on formatBlock allowing nesting, we need to use outdent
254
+ # https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
255
+ if el == 'blockquote' and selectionData.el and selectionData.el.parentNode.tagName.toLowerCase() == 'blockquote'
256
+ return document.execCommand('outdent', false, null)
257
+
258
+ if selectionData.tagName == el
259
+ el = 'p'
260
+
261
+ return document.execCommand('formatBlock', false, el)
262
+
263
+ _execAction: (action, e) ->
264
+ if action.indexOf('append-') > -1
265
+ @_execFormatBlock(action.replace('append-', ''))
266
+ @_setPosition()
267
+ @_setButtonStates()
268
+
269
+ else if action is 'anchor'
270
+ @_triggerAnchorAction(e)
271
+
272
+ else if action is 'image'
273
+ document.execCommand('insertImage', false, window.getSelection())
274
+
275
+ else
276
+ document.execCommand(action, false, null)
277
+ @_setPosition()
278
+
279
+ _triggerAnchorAction: (e) ->
280
+ if @selection.anchorNode.parentNode.tagName.toLowerCase() == 'a'
281
+ document.execCommand('unlink', false, null)
282
+ else
283
+ if @$anchorForm.is(':visible') then @_show() else @_showAnchorForm()
284
+
285
+ _showAnchorForm: ->
286
+ @keepToolbarVisible = true
287
+
288
+ @savedSelection = window.saveSelection()
289
+
290
+ @$anchorForm.show()
291
+ @$toolbarButtons.hide()
292
+
293
+ @$anchorInput.focus()
294
+ @$anchorInput.val('')
295
+
296
+ _bindButtons: ->
297
+ toolbar = @
298
+ $buttons = @$elem.find('button')
299
+
300
+ triggerAction = (e) ->
301
+ e.preventDefault()
302
+ e.stopPropagation()
303
+
304
+ $button = $(e.currentTarget)
305
+ $button.toggleClass('character-editor-button-active')
306
+
307
+ action = $button.attr('data-action')
308
+ toolbar._execAction(action, e)
309
+
310
+ $buttons.on 'click', triggerAction
311
+
312
+ _setTargetBlank: ->
313
+ el = window.getSelectionStart()
314
+
315
+ if el.tagName.toLowerCase() == 'a'
316
+ el.target = '_blank'
317
+ else
318
+ $(el).find('a').each (i, el) -> $(el).attr('target', '_blank')
319
+
320
+ _createLink: (input) ->
321
+ window.restoreSelection(@savedSelection)
322
+ document.execCommand('createLink', false, @$anchorInput.val())
323
+
324
+ if @options.targetBlank
325
+ @_setTargetBlank()
326
+
327
+ @_show()
328
+ @$anchorInput.val('')
329
+
330
+ _bindAnchorForm: ->
331
+ @$anchorForm.on 'click', (e) ->
332
+ e.stopPropagation()
333
+
334
+ @$anchorInput.on 'keyup', (e) =>
335
+ if e.keyCode == 13
336
+ e.preventDefault()
337
+ @_createLink()
338
+
339
+ @$anchorInput.on 'blur', (e) =>
340
+ @keepToolbarVisible = false
341
+
342
+ @$anchorForm.find('a').on 'click', (e) =>
343
+ e.preventDefault()
344
+ window.restoreSelection(@savedSelection)
345
+
346
+ destroy: ->
347
+ $(@options.viewSelector).off 'mouseup keyup blur'
348
+ $(window).off 'resize'
349
+ @$anchorForm.off 'click'
350
+ @$anchorForm.find('a').off 'click'
351
+ @$anchorInput.on 'keyup blur'
352
+ @$elem.find('button').off 'click'
353
+ @$elem.remove()
@@ -0,0 +1,126 @@
1
+ $bgcolor: #57ad68;
2
+ $border_color: #fff;
3
+ $button_size: 40px;
4
+ $link_color: #fff;
5
+
6
+ .character-editor {
7
+ &:focus { outline: 0;
8
+ }
9
+ }
10
+
11
+ .character-editor-placeholder { position: relative;
12
+ &:after { position: absolute;
13
+ content: attr(data-placeholder);
14
+ top: 0;
15
+ left: 0;
16
+ font-style: italic;
17
+ opacity: .5;
18
+ }
19
+ }
20
+
21
+ .character-editor-toolbar { position: absolute;
22
+ top: 0;
23
+ left: 0;
24
+ visibility: hidden;
25
+ z-index: 2000;
26
+ background-color: $bgcolor;
27
+ font-family: HelveticaNeue, Helvetica, Arial, sans-serif;
28
+ font-size: 16px;
29
+ //@include transition(top .075s ease-out,left .075s ease-out);
30
+ ul { margin: 0;
31
+ padding: 0;
32
+ }
33
+
34
+ li { float: left;
35
+ list-style: none;
36
+ margin: 0;
37
+ padding: 0;
38
+ &:last-child button { border-right: none;
39
+ }
40
+
41
+ button { display: block;
42
+ padding: 0px;
43
+ margin: 0;
44
+ height: $button_size;
45
+ min-width: $button_size;
46
+ background-color: transparent;
47
+ border: none;
48
+ border-right: 1px solid lighten($bgcolor, 20);
49
+ color: $link_color;
50
+ cursor: pointer;
51
+ font-size: 14px;
52
+ text-decoration: none;
53
+ //@include box-sizing(border-box);
54
+ //@include transition(background-color .2s ease-in, color .2s ease-in);
55
+ &:hover { background-color: darken($bgcolor, 20);
56
+ color: #fff;
57
+ }
58
+ &:focus { outline: none;
59
+ }
60
+ }
61
+ }
62
+
63
+ .character-editor-button-active { background-color: darken($bgcolor, 30);
64
+ color: #fff;
65
+ }
66
+ }
67
+
68
+ .character-editor-toolbar-active { visibility: visible;
69
+ //-webkit-animation: pop-upwards 160ms forwards linear;
70
+ //-moz-animation: pop-upwards 160ms forwards linear;
71
+ //-ms-animation: pop-upwards 160ms forwards linear;
72
+ //-o-animation: pop-upwards 160ms forwards linear;
73
+ //animation: pop-upwards 160ms forwards linear;
74
+ //@include transition(top .075s ease-out,left .075s ease-out);
75
+ }
76
+
77
+ .character-editor-toolbar-form-anchor { background: $bgcolor;
78
+ color: #fff;
79
+ input[type='text'], a { font-family: HelveticaNeue, Helvetica, Arial, sans-serif;
80
+ }
81
+ input[type='text'] { display: inline;
82
+ padding: 6px;
83
+ height: $button_size;
84
+ background-color: $bgcolor!important;
85
+ color: $link_color;
86
+ border: none;
87
+ font-size: 14px;
88
+ margin: 0;
89
+ //@include box-sizing(border-box);
90
+ &:focus { outline: 0;
91
+ border: none;
92
+ box-shadow: none;
93
+ }
94
+ }
95
+ a { color: $link_color;
96
+ font-weight: bolder;
97
+ font-size: 24px;
98
+ text-decoration: none;
99
+ padding: 0 13px;
100
+ &:hover { color: $link_color;
101
+ opacity: .8;
102
+ }
103
+ }
104
+ }
105
+
106
+ %character-toolbar-arrow { content: "";
107
+ display: block;
108
+ position: absolute;
109
+ left: 50%;
110
+ margin-left: -5px;
111
+ width: 0;
112
+ height: 0;
113
+ border-style: solid;
114
+ }
115
+
116
+ .character-toolbar-arrow-under:after { @extend %character-toolbar-arrow;
117
+ top: $button_size;
118
+ border-width: 5px 5px 0 5px;
119
+ border-color: $bgcolor transparent transparent transparent;
120
+ }
121
+
122
+ .character-toolbar-arrow-over:before { @extend %character-toolbar-arrow;
123
+ top: -5px;
124
+ border-width: 0 5px 5px 5px;
125
+ border-color: transparent transparent $bgcolor transparent;
126
+ }
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/character_editor/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'character_editor'
6
+ gem.version = Character::Editor::VERSION
7
+ gem.summary = 'Character WYSIWYG editor'
8
+ gem.license = 'MIT'
9
+
10
+ gem.authors = ['Alexander Kravets']
11
+ gem.email = 'alex@slatestudio.com'
12
+ gem.homepage = 'https://github.com/slate-studio/character_editor'
13
+
14
+ gem.require_paths = ['lib']
15
+ gem.files = `git ls-files`.split($\)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Supress the warning about no rubyforge project
20
+ gem.rubyforge_project = 'nowarning'
21
+ end
@@ -0,0 +1,2 @@
1
+ require 'character_editor/version'
2
+ require 'character_editor/engine'
@@ -0,0 +1,14 @@
1
+ module Character
2
+ module Editor
3
+ class Engine < ::Rails::Engine
4
+ config.before_configuration do
5
+ end
6
+ end
7
+
8
+ class << self
9
+ def configure(&block)
10
+ block.call(self)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Character
2
+ module Editor
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: character_editor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexander Kravets
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: alex@slatestudio.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".gitignore"
20
+ - Gemfile
21
+ - Gemfile.lock
22
+ - README.md
23
+ - Rakefile
24
+ - app/assets/javascripts/character_editor.coffee
25
+ - app/assets/javascripts/character_editor/_selection.coffee
26
+ - app/assets/javascripts/character_editor/_toolbar.coffee
27
+ - app/assets/stylesheets/character_editor.scss
28
+ - character_editor.gemspec
29
+ - lib/character_editor.rb
30
+ - lib/character_editor/engine.rb
31
+ - lib/character_editor/version.rb
32
+ homepage: https://github.com/slate-studio/character_editor
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project: nowarning
52
+ rubygems_version: 2.2.1
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Character WYSIWYG editor
56
+ test_files: []