character_editor 0.0.1 → 0.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9de43f73c9a9be5a52713b0e182eae7a3d77a5d4
4
- data.tar.gz: 48d6f5ee42ea70ac33468e504d50a96ae9b2f86e
3
+ metadata.gz: e9ead82356a9f7446f9a612643dca791b865b2aa
4
+ data.tar.gz: f69a8651fdeed67516b651cd7d6f22bda7531c7d
5
5
  SHA512:
6
- metadata.gz: 733d8129f5be1b895c043aaea78ba6cf692153f7c3c6c4d0e6fa00d55b8a33c50a3574cc5f16cf882f6c361f673d3acb9319d25c1f143ead6899c6f270b66f5b
7
- data.tar.gz: 6a13445ba089a922eb5df7e77cb74d94117963ca3e6be89bf50a2832a9bb0d20012b80612d845f9d4062338a64f477891ed07422025a7621bdb1e515af8f1b8c
6
+ metadata.gz: 30d2bb4d3b1039e35a4a722c4bb1b11af61fc8356d80417be9a3475c6d262c948939aa94c03d93b83786b81500eed30baadefb5506653376215e8300dccbb8e1
7
+ data.tar.gz: ef4c68f4aedb47bea73fab5cae920edf1a5d4cde3fc223f1854643ea4f6eef470fc5dc0889bd7dab5b007b8fdffc83ef3ca1345e8f307a860d3f3659cfdc6ca1
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  .DS_Store
2
+ pkg
data/README.md CHANGED
@@ -1,3 +1,13 @@
1
1
  # [Character](http://github.com/slate-studio/character) Editor
2
2
 
3
- Based on [MediumEditor](https://github.com/daviferreira/medium-editor) — Medium.com WYSIWYG editor clone.
3
+ Based on [MediumEditor](https://github.com/daviferreira/medium-editor) — Medium.com WYSIWYG editor clone.
4
+
5
+ ## TODO:
6
+ - fix copy/paste in the editor (test Safari/Chrome/FF, might be related to span issue)
7
+ - pre tag is not removed when unset
8
+ - insert images (integrate with Character assets plugin)
9
+
10
+ ### Span Issue: happens only in Chrome
11
+
12
+ * http://www.neotericdesign.com/blog/2013/3/working-around-chrome-s-contenteditable-span-bug
13
+ * http://stackoverflow.com/questions/15015019/prevent-chrome-from-wrapping-contents-of-joined-p-with-a-span
@@ -12,7 +12,7 @@
12
12
 
13
13
  #= require_self
14
14
  #= require character_editor/_selection
15
- #= require character_editor/_toolbar
15
+ #= require character_editor/toolbar/_toolbar
16
16
 
17
17
  # Object - an object representing a concept that you want
18
18
  # to model (e.g. a car)
@@ -20,7 +20,7 @@
20
20
  options:
21
21
  allowMultiParagraphSelection: true
22
22
  anchorInputPlaceholder: 'Paste or type a link'
23
- buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote']
23
+ buttons: 'bold italic underline anchor header1 header2 quote'
24
24
  delay: 0
25
25
  diffLeft: 0
26
26
  diffTop: -10
@@ -29,8 +29,8 @@
29
29
  forcePlainText: true
30
30
  placeholder: 'Type your text...'
31
31
  targetBlank: false
32
- firstHeader: 'h3'
33
- secondHeader: 'h4'
32
+ firstHeader: 'h2'
33
+ secondHeader: 'h3'
34
34
  tabSpaces: ' '
35
35
  viewSelector: 'body'
36
36
  parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre']
@@ -81,15 +81,10 @@
81
81
  if not @options.disableToolbar
82
82
  @_addToolbar()
83
83
 
84
- _addToolbar: -> # create only one toolbar for now
84
+ _addToolbar: ->
85
85
  @toolbar = window.characterEditorToolbar
86
-
87
86
  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
-
87
+ @toolbar = Object.create(CharacterEditor.Toolbar).init(@options)
93
88
  window.characterEditorToolbar = @toolbar
94
89
 
95
90
  _setPlaceholder: ->
@@ -0,0 +1,40 @@
1
+
2
+ @CharacterEditor.Toolbar._setPosition = ->
3
+ $contentView = $(@options.viewSelector)
4
+ $arrow = @$elem.children('.character-editor-toolbar-arrow')
5
+ $toolbarWidth = @$elem.width()
6
+ $toolbarLeft = @$elem.position().left
7
+
8
+ contentLeftOffset = $contentView.offset().left
9
+ contentTopOffset = $contentView.offset().top
10
+ contentScrollTop = $contentView.scrollTop()
11
+ contentInnerWidth = $contentView.innerWidth()
12
+
13
+ selection = window.getSelection()
14
+ range = selection.getRangeAt(0)
15
+ boundary = range.getBoundingClientRect()
16
+
17
+ offsetWidth = @$elem.get(0).offsetWidth
18
+ offsetHeight = @$elem.get(0).offsetHeight
19
+
20
+ defaultLeft = (@options.diffLeft) - (offsetWidth / 2)
21
+ middleBoundary = (boundary.left + boundary.right) / 2 - contentLeftOffset
22
+ halfOffsetWidth = offsetWidth / 2
23
+
24
+ @$elem.css 'top', boundary.top + @options.diffTop - contentTopOffset + contentScrollTop - offsetHeight + 'px'
25
+
26
+ edgeOffset = 5
27
+
28
+ # TODO: some issues still happen here, on double click (right edge) and right edge text selection
29
+
30
+ if middleBoundary < halfOffsetWidth
31
+ @$elem.css { left: "#{edgeOffset}px", right: 'auto' }
32
+ $arrow.css { 'margin-left': -($toolbarWidth/2 - middleBoundary + 10) } # arrow to left
33
+
34
+ else if (contentInnerWidth - middleBoundary) <= halfOffsetWidth
35
+ @$elem.css { left: 'auto', right: "#{edgeOffset}px" }
36
+ $arrow.css { 'margin-left': (middleBoundary - $toolbarLeft - $toolbarWidth/2 - 5) } # arrow to right
37
+
38
+ else
39
+ @$elem.css { left: defaultLeft + middleBoundary + 'px', right: 'auto' }
40
+ $arrow.css { 'margin-left': '' } # center arrow
@@ -0,0 +1,72 @@
1
+
2
+ @CharacterEditor.Toolbar._toolbarTemplate = (options) -> """
3
+ <div id='character_editor_toolbar' class='character-editor-toolbar'>
4
+ <ul id='character_editor_toolbar_buttons'></ul>
5
+ <div class='character-editor-toolbar-form-anchor' id='character_editor_toolbar_form_anchor'>
6
+ <input type='text' value='' placeholder='#{ options.anchorInputPlaceholder }'><a href='#'>&times;</a>
7
+ </div>
8
+ <div class='character-editor-toolbar-arrow'></div>
9
+ </div>"""
10
+
11
+ @CharacterEditor.Toolbar._generateButtonTemplates = (options) ->
12
+ classPrefix = 'character-editor-action'
13
+
14
+ l =
15
+ bold: '<i class="fa fa-bold"></i>'
16
+ italic : '<i class="fa fa-italic"></i>'
17
+ underline: '<i class="fa fa-underline"></i>'
18
+ strikethrough: '<i class="fa fa-strikethrough"></i>'
19
+ superscript: '<i class="fa fa-superscript"></i>'
20
+ subscript: '<i class="fa fa-subscript"></i>'
21
+ anchor: '<i class="fa fa-link"></i>'
22
+ image: '<i class="fa fa-picture-o"></i>'
23
+ quote: '<i class="fa fa-quote-right"></i>'
24
+ orderedlist: '<i class="fa fa-list-ol"></i>'
25
+ unorderedlist: '<i class="fa fa-list-ul"></i>'
26
+ pre: '<i class="fa fa-code fa-lg"></i>'
27
+ header1: "<b>#{ options.firstHeader.toUpperCase() }</b>"
28
+ header2: "<b>#{ options.secondHeader.toUpperCase() }</b>"
29
+
30
+ templates =
31
+ bold: "<li><button class='#{classPrefix} #{classPrefix}-bold'
32
+ data-action='bold' data-element='b'>#{ l.bold }</button></li>"
33
+
34
+ italic: "<li><button class='#{classPrefix} #{classPrefix}-italic'
35
+ data-action='italic' data-element='i'>#{ l.italic }</button></li>"
36
+
37
+ underline: "<li><button class='#{classPrefix} #{classPrefix}-underline'
38
+ data-action='underline' data-element='u'>#{ l.underline }</button></li>"
39
+
40
+ strikethrough: "<li><button class='#{classPrefix} #{classPrefix}-strikethrough'
41
+ data-action='strikethrough' data-element='strike'>#{ l.strikethrough }</button></li>"
42
+
43
+ superscript: "<li><button class='#{classPrefix} #{classPrefix}-superscript'
44
+ data-action='superscript' data-element='sup'>#{ l.superscript }</button></li>"
45
+
46
+ subscript: "<li><button class='#{classPrefix} #{classPrefix}-subscript'
47
+ data-action='subscript' data-element='sub'>#{ l.subscript }</button></li>"
48
+
49
+ anchor: "<li><button class='#{classPrefix} #{classPrefix}-anchor'
50
+ data-action='anchor' data-element='a'>#{ l.anchor }</button></li>"
51
+
52
+ image: "<li><button class='#{classPrefix} #{classPrefix}-image'
53
+ data-action='image' data-element='img'>#{ l.image }</button></li>"
54
+
55
+ quote: "<li><button class='#{classPrefix} #{classPrefix}-quote'
56
+ data-action='append-blockquote' data-element='blockquote'>#{ l.quote }</button></li>"
57
+
58
+ orderedlist: "<li><button class='#{classPrefix} #{classPrefix}-orderedlist'
59
+ data-action='insertorderedlist' data-element='ol'>#{ l.orderedlist }</button></li>"
60
+
61
+ unorderedlist: "<li><button class='#{classPrefix} #{classPrefix}-unorderedlist'
62
+ data-action='insertunorderedlist' data-element='ul'>#{ l.unorderedlist }</button></li>"
63
+
64
+ pre: "<li><button class='#{classPrefix} #{classPrefix}-pre'
65
+ data-action='append-pre' data-element='pre'>#{ l.pre }</button></li>"
66
+
67
+ header1: "<li><button class='#{classPrefix} #{classPrefix}-header1'
68
+ data-action='append-#{ options.firstHeader }' data-element='#{ options.firstHeader }'>#{ l.header1 }</button></li>"
69
+
70
+ header2: "<li><button class='#{classPrefix} #{classPrefix}-header2'
71
+ data-action='append-#{ options.secondHeader }' data-element='#{ options.secondHeader }'>#{ l.header2 }</button></li>"
72
+ return templates
@@ -0,0 +1,241 @@
1
+ #= require_self
2
+ #= require ./_position
3
+ #= require ./_templates
4
+
5
+ @CharacterEditor.Toolbar =
6
+ init: (options, elem) ->
7
+ @options = $.extend({}, @options, options)
8
+
9
+ @_buttonTemplates = @_generateButtonTemplates(@options)
10
+
11
+ @$elem = $(@_toolbarTemplate(@options))
12
+ $(@options.viewSelector).append(@$elem)
13
+
14
+ # this helps to not hide toolbar on selection (while toolbar button click)
15
+ @keepToolbarVisible = false
16
+
17
+ @_build()
18
+
19
+ @_bind()
20
+
21
+ @
22
+
23
+ options: {}
24
+
25
+ _build: ->
26
+ @$toolbarButtons = @$elem.find('#character_editor_toolbar_buttons')
27
+ @$anchorForm = @$elem.find('#character_editor_toolbar_form_anchor')
28
+ @$anchorInput = @$anchorForm.children('input')
29
+
30
+ @$toolbarButtons.show()
31
+ @$anchorForm.hide()
32
+
33
+ _bind: ->
34
+ @_bindWindowResize()
35
+ @_bindButtons()
36
+ @_bindAnchorForm()
37
+ @_bindSelect()
38
+
39
+ _hide: ->
40
+ @keepToolbarVisible = false
41
+ @$elem.removeClass('character-editor-toolbar-active')
42
+ @$toolbarButtons.removeClass()
43
+
44
+ _show: ->
45
+ timer = ''
46
+
47
+ @$anchorForm.hide()
48
+
49
+ # TODO: this can be optimized to do not change DOM
50
+ html = ''
51
+ $.each @options.buttons.split(' '), (i, key) => html += @_buttonTemplates[key]
52
+ @$toolbarButtons.html(html)
53
+
54
+ @$toolbarButtons.show()
55
+
56
+ @$anchorInput.css('width', @$elem.width() - 40 - 12) # TODO: remove build in themes values
57
+
58
+ @keepToolbarVisible = false
59
+
60
+ clearTimeout(timer)
61
+
62
+ timer = setTimeout ( =>
63
+ if !@$elem.hasClass('character-editor-toolbar-active')
64
+ @$elem.addClass('character-editor-toolbar-active')
65
+ ), 100
66
+
67
+ _bindSelect: ->
68
+ if !@options.disableToolbar
69
+ timer = ''
70
+ toolbar = @
71
+
72
+ $(@options.viewSelector).on 'mouseup keyup blur', (e) =>
73
+ clearTimeout(timer)
74
+ timer = setTimeout (-> toolbar._checkSelection()), @options.delay
75
+
76
+ _bindWindowResize: ->
77
+ timer = ''
78
+ $(window).on 'resize', =>
79
+ clearTimeout(timer)
80
+ timer = setTimeout ( => if @$elem.hasClass('character-editor-toolbar-active') then @_setPosition() ), 100
81
+
82
+ _getActiveEditor: ->
83
+ $editorElement = $(@selection.getRangeAt(0).commonAncestorContainer).parents('[data-editor-element]')
84
+ if $editorElement then $editorElement.data('editor') else false
85
+
86
+ _checkSelection: ->
87
+ if not @keepToolbarVisible
88
+ @selection = window.getSelection()
89
+
90
+ if @selection.toString().trim() == ''
91
+ return @_hide()
92
+
93
+ activeEditor = @_getActiveEditor()
94
+ if !activeEditor or activeEditor.options.disableToolbar
95
+ return @_hide()
96
+
97
+ selectionHtml = getSelectionHtml()
98
+ selectionHtml = selectionHtml.replace(/<[\S]+><\/[\S]+>/gim, '')
99
+
100
+ hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g)
101
+
102
+ if !@options.allowMultiParagraphSelection and hasMultiParagraphs
103
+ return @_hide()
104
+
105
+ @_setButtonStates()
106
+ @_show()
107
+ @_setPosition()
108
+
109
+ _setButtonStates: ->
110
+ $buttons = @$elem.find('button')
111
+ $buttons.removeClass('character-editor-button-active')
112
+
113
+ parentNode = @selection.anchorNode
114
+
115
+ if !parentNode.tagName
116
+ parentNode = @selection.anchorNode.parentNode
117
+
118
+ while parentNode.tagName != undefined and @options.parentElements.indexOf(parentNode.tagName) == -1
119
+ tag = parentNode.tagName.toLowerCase()
120
+ @_activateButton(tag)
121
+ parentNode = parentNode.parentNode
122
+
123
+ _activateButton: (tag) ->
124
+ $el = @$elem.find('[data-element="' + tag + '"]').first()
125
+ if $el.length and !$el.hasClass('character-editor-button-active')
126
+ $el.addClass('character-editor-button-active')
127
+
128
+ _getSelectionData: (el) ->
129
+ if el and el.tagName
130
+ tagName = el.tagName.toLowerCase()
131
+
132
+ while el and @options.parentElements.indexOf(tagName) == -1
133
+ el = el.parentNode
134
+ if el and el.tagName
135
+ tagName = el.tagName.toLowerCase()
136
+
137
+ return { el: el, tagName: tagName }
138
+
139
+ _execFormatBlock: (el) ->
140
+ selectionData = @_getSelectionData(@selection.anchorNode)
141
+
142
+ # FF handles blockquote differently on formatBlock allowing nesting, we need to use outdent
143
+ # https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
144
+ if el == 'blockquote' and selectionData.el and selectionData.el.parentNode.tagName.toLowerCase() == 'blockquote'
145
+ return document.execCommand('outdent', false, null)
146
+
147
+ if selectionData.tagName == el
148
+ el = 'p'
149
+
150
+ return document.execCommand('formatBlock', false, el)
151
+
152
+ _execAction: (action, e) ->
153
+ if action.indexOf('append-') > -1
154
+ @_execFormatBlock(action.replace('append-', ''))
155
+ @_setPosition()
156
+ @_setButtonStates()
157
+
158
+ else if action is 'anchor'
159
+ @_triggerAnchorAction(e)
160
+
161
+ else if action is 'image'
162
+ document.execCommand('insertImage', false, window.getSelection())
163
+
164
+ else
165
+ document.execCommand(action, false, null)
166
+ @_setPosition()
167
+
168
+ _triggerAnchorAction: (e) ->
169
+ if @selection.anchorNode.parentNode.tagName.toLowerCase() == 'a'
170
+ document.execCommand('unlink', false, null)
171
+ else
172
+ if @$anchorForm.is(':visible') then @_show() else @_showAnchorForm()
173
+
174
+ _showAnchorForm: ->
175
+ @keepToolbarVisible = true
176
+
177
+ @savedSelection = window.saveSelection()
178
+
179
+ @$anchorForm.show()
180
+ @$toolbarButtons.hide()
181
+
182
+ @$anchorInput.focus()
183
+ @$anchorInput.val('')
184
+
185
+ _bindButtons: ->
186
+ toolbar = @
187
+
188
+ triggerAction = (e) ->
189
+ e.preventDefault()
190
+ e.stopPropagation()
191
+
192
+ $button = $(e.currentTarget)
193
+ $button.toggleClass('character-editor-button-active')
194
+
195
+ action = $button.attr('data-action')
196
+ toolbar._execAction(action, e)
197
+
198
+ $(document).on 'click', '#character_editor_toolbar_buttons button', triggerAction
199
+
200
+ _setTargetBlank: ->
201
+ el = window.getSelectionStart()
202
+
203
+ if el.tagName.toLowerCase() == 'a'
204
+ el.target = '_blank'
205
+ else
206
+ $(el).find('a').each (i, el) -> $(el).attr('target', '_blank')
207
+
208
+ _createLink: (input) ->
209
+ window.restoreSelection(@savedSelection)
210
+ document.execCommand('createLink', false, @$anchorInput.val())
211
+
212
+ if @options.targetBlank
213
+ @_setTargetBlank()
214
+
215
+ @_show()
216
+ @$anchorInput.val('')
217
+
218
+ _bindAnchorForm: ->
219
+ @$anchorForm.on 'click', (e) ->
220
+ e.stopPropagation()
221
+
222
+ @$anchorInput.on 'keyup', (e) =>
223
+ if e.keyCode == 13
224
+ e.preventDefault()
225
+ @_createLink()
226
+
227
+ @$anchorInput.on 'blur', (e) =>
228
+ @keepToolbarVisible = false
229
+
230
+ @$anchorForm.find('a').on 'click', (e) =>
231
+ e.preventDefault()
232
+ window.restoreSelection(@savedSelection)
233
+
234
+ destroy: ->
235
+ $(@options.viewSelector).off 'mouseup keyup blur'
236
+ $(window).off 'resize'
237
+ @$anchorForm.off 'click'
238
+ @$anchorForm.find('a').off 'click'
239
+ @$anchorInput.on 'keyup blur'
240
+ $(document).off 'click', '#character_editor_toolbar_buttons button'
241
+ @$elem.remove()
@@ -1,7 +1,11 @@
1
- $bgcolor: #57ad68;
2
- $border_color: #fff;
3
- $button_size: 40px;
4
- $link_color: #fff;
1
+ //---------------------------------------------------------
2
+ // CHARACTER EDITOR
3
+ //---------------------------------------------------------
4
+
5
+ $bgcolor: $secondary_color;
6
+ $border_color: darken($secondary_color, 5%);
7
+ $link_color: #fff;
8
+ $button_size: 40px;
5
9
 
6
10
  .character-editor {
7
11
  &:focus { outline: 0;
@@ -26,7 +30,6 @@ $link_color: #fff;
26
30
  background-color: $bgcolor;
27
31
  font-family: HelveticaNeue, Helvetica, Arial, sans-serif;
28
32
  font-size: 16px;
29
- //@include transition(top .075s ease-out,left .075s ease-out);
30
33
  ul { margin: 0;
31
34
  padding: 0;
32
35
  }
@@ -45,14 +48,12 @@ $link_color: #fff;
45
48
  min-width: $button_size;
46
49
  background-color: transparent;
47
50
  border: none;
48
- border-right: 1px solid lighten($bgcolor, 20);
51
+ border-right: 1px solid $border_color;
49
52
  color: $link_color;
50
53
  cursor: pointer;
51
54
  font-size: 14px;
52
55
  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
+ &:hover { background-color: $border_color;
56
57
  color: #fff;
57
58
  }
58
59
  &:focus { outline: none;
@@ -60,18 +61,12 @@ $link_color: #fff;
60
61
  }
61
62
  }
62
63
 
63
- .character-editor-button-active { background-color: darken($bgcolor, 30);
64
+ .character-editor-button-active { background-color: $border_color;
64
65
  color: #fff;
65
66
  }
66
67
  }
67
68
 
68
69
  .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
70
  }
76
71
 
77
72
  .character-editor-toolbar-form-anchor { background: $bgcolor;
@@ -80,47 +75,40 @@ $link_color: #fff;
80
75
  }
81
76
  input[type='text'] { display: inline;
82
77
  padding: 6px;
83
- height: $button_size;
78
+ height: $button_size - 12px;
84
79
  background-color: $bgcolor!important;
85
80
  color: $link_color;
86
81
  border: none;
87
82
  font-size: 14px;
88
83
  margin: 0;
89
- //@include box-sizing(border-box);
90
84
  &:focus { outline: 0;
91
85
  border: none;
92
86
  box-shadow: none;
93
87
  }
88
+ @include input-placeholder { color: $border_color;
89
+ }
94
90
  }
95
91
  a { color: $link_color;
96
92
  font-weight: bolder;
97
93
  font-size: 24px;
98
94
  text-decoration: none;
99
95
  padding: 0 13px;
96
+ position: relative;
97
+ top: 1px;
100
98
  &:hover { color: $link_color;
101
99
  opacity: .8;
102
100
  }
103
101
  }
104
102
  }
105
103
 
106
- %character-toolbar-arrow { content: "";
107
- display: block;
104
+ .character-editor-toolbar-arrow { display: block;
108
105
  position: absolute;
109
106
  left: 50%;
107
+ top: $button_size;
110
108
  margin-left: -5px;
111
- width: 0;
112
- height: 0;
109
+ width: 0px;
110
+ height: 0px;
113
111
  border-style: solid;
114
- }
115
-
116
- .character-toolbar-arrow-under:after { @extend %character-toolbar-arrow;
117
- top: $button_size;
118
112
  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;
113
+ border-color: $bgcolor transparent transparent transparent;
126
114
  }
@@ -1,5 +1,5 @@
1
1
  module Character
2
2
  module Editor
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.9'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: character_editor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Kravets
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-07 00:00:00.000000000 Z
11
+ date: 2014-02-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: alex@slatestudio.com
@@ -23,7 +23,9 @@ files:
23
23
  - Rakefile
24
24
  - app/assets/javascripts/character_editor.coffee
25
25
  - app/assets/javascripts/character_editor/_selection.coffee
26
- - app/assets/javascripts/character_editor/_toolbar.coffee
26
+ - app/assets/javascripts/character_editor/toolbar/_position.coffee
27
+ - app/assets/javascripts/character_editor/toolbar/_templates.coffee
28
+ - app/assets/javascripts/character_editor/toolbar/_toolbar.coffee
27
29
  - app/assets/stylesheets/character_editor.scss
28
30
  - character_editor.gemspec
29
31
  - lib/character_editor.rb
@@ -1,353 +0,0 @@
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()