character_editor 0.0.1 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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()