character_editor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []