dante-editor 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +4 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +146 -0
- data/Procfile +1 -0
- data/README.md +129 -0
- data/TODO.md +45 -0
- data/bower.json +10 -0
- data/config.rb +82 -0
- data/config.ru +31 -0
- data/dante-editor.gemspec +19 -0
- data/lib/dante-editor.rb +3 -0
- data/lib/dante-editor/version.rb +5 -0
- data/license.md +22 -0
- data/rakefile +2 -0
- data/source/assets/fonts/fontello.eot +0 -0
- data/source/assets/fonts/fontello.svg +36 -0
- data/source/assets/fonts/fontello.ttf +0 -0
- data/source/assets/fonts/fontello.woff +0 -0
- data/source/assets/images/background.png +0 -0
- data/source/assets/images/icon-logo.png +0 -0
- data/source/assets/images/icon.png +0 -0
- data/source/assets/images/media-loading-placeholder.png +0 -0
- data/source/assets/images/middleman.png +0 -0
- data/source/assets/javascripts/all.js +3 -0
- data/source/assets/javascripts/dante.js +7 -0
- data/source/assets/javascripts/dante/dante.js.coffee.erb +7 -0
- data/source/assets/javascripts/dante/editor.js.coffee +917 -0
- data/source/assets/javascripts/dante/menu.js.coffee +202 -0
- data/source/assets/javascripts/dante/tooltip.js.coffee +302 -0
- data/source/assets/javascripts/dante/utils.js.coffee +235 -0
- data/source/assets/javascripts/dante/view.js.coffee +56 -0
- data/source/assets/javascripts/deps.js +4 -0
- data/source/assets/javascripts/spec.js +2 -0
- data/source/assets/javascripts/specs/cleaner.js.coffee +8 -0
- data/source/assets/javascripts/specs/dante_view.js.coffee +74 -0
- data/source/assets/javascripts/specs/editor.js.coffee +57 -0
- data/source/assets/stylesheets/all.css.scss +4 -0
- data/source/assets/stylesheets/dante.css.scss +3 -0
- data/source/assets/stylesheets/dante/base.css.scss +57 -0
- data/source/assets/stylesheets/dante/editor.css.scss +662 -0
- data/source/assets/stylesheets/dante/fonts.css.scss +106 -0
- data/source/assets/stylesheets/normalize.css +375 -0
- data/source/embeds.html.erb +29 -0
- data/source/index.html.erb +28 -0
- data/source/layouts/layout.erb +21 -0
- data/source/layouts/spec.html.erb +24 -0
- data/source/partials/_example_1.erb +30 -0
- data/source/partials/_example_2.erb +33 -0
- data/source/partials/_example_3.erb +17 -0
- data/source/partials/_readme.markdown +78 -0
- data/source/partials/test/_example_1.erb +18 -0
- data/source/readme.html.erb +28 -0
- data/source/tests/dante_view.html.erb +11 -0
- data/source/tests/index.html.erb +32 -0
- data/tmp/.gitkeep +0 -0
- metadata +99 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
String.prototype.killWhiteSpace = ()->
|
2
|
+
this.replace(/\s/g, '')
|
3
|
+
|
4
|
+
String.prototype.reduceWhiteSpace = ()->
|
5
|
+
this.replace(/\s+/g, ' ')
|
6
|
+
|
7
|
+
utils = {}
|
8
|
+
window.Dante.utils = utils
|
9
|
+
|
10
|
+
utils.log = (message, force) ->
|
11
|
+
if (window.debugMode || force)
|
12
|
+
#console.log('%cDANTE DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
|
13
|
+
#console.log('%cDANTE DEBUGGER: %c', 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
|
14
|
+
console.log( message );
|
15
|
+
|
16
|
+
utils.getBase64Image = (img) ->
|
17
|
+
canvas = document.createElement("canvas")
|
18
|
+
canvas.width = img.width
|
19
|
+
canvas.height = img.height
|
20
|
+
ctx = canvas.getContext("2d")
|
21
|
+
ctx.drawImage img, 0, 0
|
22
|
+
dataURL = canvas.toDataURL("image/png")
|
23
|
+
|
24
|
+
# escape data:image prefix
|
25
|
+
# dataURL.replace /^data:image\/(png|jpg);base64,/, ""
|
26
|
+
|
27
|
+
# or just return dataURL
|
28
|
+
return dataURL
|
29
|
+
|
30
|
+
utils.generateUniqueName = ()->
|
31
|
+
Math.random().toString(36).slice(8)
|
32
|
+
|
33
|
+
#http://stackoverflow.com/questions/5605401/insert-link-in-contenteditable-element
|
34
|
+
utils.saveSelection = ()->
|
35
|
+
if window.getSelection
|
36
|
+
sel = window.getSelection()
|
37
|
+
if sel.getRangeAt and sel.rangeCount
|
38
|
+
ranges = []
|
39
|
+
i = 0
|
40
|
+
len = sel.rangeCount
|
41
|
+
|
42
|
+
while i < len
|
43
|
+
ranges.push sel.getRangeAt(i)
|
44
|
+
++i
|
45
|
+
return ranges
|
46
|
+
else return document.selection.createRange() if document.selection and document.selection.createRange
|
47
|
+
null
|
48
|
+
|
49
|
+
utils.restoreSelection = (savedSel) ->
|
50
|
+
if savedSel
|
51
|
+
if window.getSelection
|
52
|
+
sel = window.getSelection()
|
53
|
+
sel.removeAllRanges()
|
54
|
+
i = 0
|
55
|
+
len = savedSel.length
|
56
|
+
|
57
|
+
while i < len
|
58
|
+
sel.addRange savedSel[i]
|
59
|
+
++i
|
60
|
+
else savedSel.select() if document.selection and savedSel.select
|
61
|
+
return
|
62
|
+
|
63
|
+
utils.getNode = ()->
|
64
|
+
range = undefined
|
65
|
+
sel = undefined
|
66
|
+
container = undefined
|
67
|
+
if document.selection and document.selection.createRange
|
68
|
+
|
69
|
+
# IE case
|
70
|
+
range = document.selection.createRange()
|
71
|
+
range.parentElement()
|
72
|
+
else if window.getSelection
|
73
|
+
sel = window.getSelection()
|
74
|
+
if sel.getRangeAt
|
75
|
+
range = sel.getRangeAt(0) if sel.rangeCount > 0
|
76
|
+
else
|
77
|
+
|
78
|
+
# Old WebKit selection object has no getRangeAt, so
|
79
|
+
# create a range from other selection properties
|
80
|
+
range = document.createRange()
|
81
|
+
range.setStart sel.anchorNode, sel.anchorOffset
|
82
|
+
range.setEnd sel.focusNode, sel.focusOffset
|
83
|
+
|
84
|
+
# Handle the case when the selection was selected backwards (from the end to the start in the document)
|
85
|
+
if range.collapsed isnt sel.isCollapsed
|
86
|
+
range.setStart sel.focusNode, sel.focusOffset
|
87
|
+
range.setEnd sel.anchorNode, sel.anchorOffset
|
88
|
+
if range
|
89
|
+
container = range.commonAncestorContainer
|
90
|
+
|
91
|
+
# Check if the container is a text node and return its parent if so
|
92
|
+
(if container.nodeType is 3 then container.parentNode else container)
|
93
|
+
|
94
|
+
#http://stackoverflow.com/questions/12603397/calculate-width-height-of-the-selected-text-javascript
|
95
|
+
utils.getSelectionDimensions = ->
|
96
|
+
sel = document.selection
|
97
|
+
range = undefined
|
98
|
+
width = 0
|
99
|
+
height = 0
|
100
|
+
left = 0
|
101
|
+
top = 0
|
102
|
+
if sel
|
103
|
+
unless sel.type is "Control"
|
104
|
+
range = sel.createRange()
|
105
|
+
width = range.boundingWidth
|
106
|
+
height = range.boundingHeight
|
107
|
+
else if window.getSelection
|
108
|
+
sel = window.getSelection()
|
109
|
+
if sel.rangeCount
|
110
|
+
range = sel.getRangeAt(0).cloneRange()
|
111
|
+
if range.getBoundingClientRect
|
112
|
+
rect = range.getBoundingClientRect()
|
113
|
+
width = rect.right - rect.left
|
114
|
+
height = rect.bottom - rect.top
|
115
|
+
|
116
|
+
width: width
|
117
|
+
height: height
|
118
|
+
top: rect.top
|
119
|
+
left: rect.left
|
120
|
+
|
121
|
+
#http://stackoverflow.com/questions/3972014/get-caret-position-in-contenteditable-div
|
122
|
+
utils.getCaretPosition = (editableDiv) ->
|
123
|
+
caretPos = 0
|
124
|
+
containerEl = null
|
125
|
+
sel = undefined
|
126
|
+
range = undefined
|
127
|
+
if window.getSelection
|
128
|
+
sel = window.getSelection()
|
129
|
+
if sel.rangeCount
|
130
|
+
range = sel.getRangeAt(0)
|
131
|
+
caretPos = range.endOffset if range.commonAncestorContainer.parentNode is editableDiv
|
132
|
+
else if document.selection and document.selection.createRange
|
133
|
+
range = document.selection.createRange()
|
134
|
+
if range.parentElement() is editableDiv
|
135
|
+
tempEl = document.createElement("span")
|
136
|
+
editableDiv.insertBefore tempEl, editableDiv.firstChild
|
137
|
+
tempRange = range.duplicate()
|
138
|
+
tempRange.moveToElementText tempEl
|
139
|
+
tempRange.setEndPoint "EndToEnd", range
|
140
|
+
caretPos = tempRange.text.length
|
141
|
+
caretPos
|
142
|
+
|
143
|
+
#http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
|
144
|
+
utils.isElementInViewport = (el) ->
|
145
|
+
#special bonus for those using jQuery
|
146
|
+
el = el[0] if typeof jQuery is "function" and el instanceof jQuery
|
147
|
+
rect = el.getBoundingClientRect()
|
148
|
+
#or $(window).height()
|
149
|
+
rect.top >= 0 and rect.left >= 0 and rect.bottom <= (window.innerHeight or document.documentElement.clientHeight) and rect.right <= (window.innerWidth or document.documentElement.clientWidth) #or $(window).width()
|
150
|
+
|
151
|
+
#http://brianmhunt.github.io/articles/taming-contenteditable/
|
152
|
+
|
153
|
+
LINE_HEIGHT = 20
|
154
|
+
|
155
|
+
is_caret_at_start_of_node = (node, range) ->
|
156
|
+
# See: http://stackoverflow.com/questions/7451468
|
157
|
+
pre_range = document.createRange()
|
158
|
+
pre_range.selectNodeContents(node)
|
159
|
+
pre_range.setEnd(range.startContainer, range.startOffset)
|
160
|
+
return pre_range.toString().trim().length == 0
|
161
|
+
|
162
|
+
is_caret_at_end_of_node = (node, range) ->
|
163
|
+
post_range = document.createRange()
|
164
|
+
post_range.selectNodeContents(node)
|
165
|
+
post_range.setStart(range.endContainer, range.endOffset)
|
166
|
+
return post_range.toString().trim().length == 0
|
167
|
+
|
168
|
+
$.fn.editableIsCaret = ->
|
169
|
+
return window.getSelection().type == 'Caret'
|
170
|
+
# alt test:
|
171
|
+
# return sel.rangeCount == 1 and sel.getRangeAt(0).collapsed
|
172
|
+
|
173
|
+
$.fn.editableRange = ->
|
174
|
+
# Return the range for the selection
|
175
|
+
sel = window.getSelection()
|
176
|
+
return unless sel.rangeCount > 0
|
177
|
+
return sel.getRangeAt(0)
|
178
|
+
|
179
|
+
$.fn.editableCaretRange = ->
|
180
|
+
return unless @editableIsCaret()
|
181
|
+
return @editableRange()
|
182
|
+
|
183
|
+
$.fn.editableSetRange = (range) ->
|
184
|
+
sel = window.getSelection()
|
185
|
+
sel.removeAllRanges() if sel.rangeCount > 0
|
186
|
+
sel.addRange(range)
|
187
|
+
|
188
|
+
$.fn.editableFocus = (at_start=true) ->
|
189
|
+
return unless @attr('contenteditable')
|
190
|
+
sel = window.getSelection()
|
191
|
+
sel.removeAllRanges() if sel.rangeCount > 0
|
192
|
+
range = document.createRange()
|
193
|
+
range.selectNodeContents(@[0])
|
194
|
+
range.collapse(at_start)
|
195
|
+
sel.addRange(range)
|
196
|
+
|
197
|
+
$.fn.editableCaretAtStart = ->
|
198
|
+
range = @editableRange()
|
199
|
+
return false unless range
|
200
|
+
return is_caret_at_start_of_node(@[0], range)
|
201
|
+
|
202
|
+
$.fn.editableCaretAtEnd = ->
|
203
|
+
range = @editableRange()
|
204
|
+
return false unless range
|
205
|
+
return is_caret_at_end_of_node(@[0], range)
|
206
|
+
|
207
|
+
$.fn.editableCaretOnFirstLine = ->
|
208
|
+
range = @editableRange()
|
209
|
+
return false unless range
|
210
|
+
# At the start of a node, the getClientRects() is [], so we have to
|
211
|
+
# use the getBoundingClientRect (which seems to work).
|
212
|
+
if is_caret_at_start_of_node(@[0], range)
|
213
|
+
return true
|
214
|
+
else if is_caret_at_end_of_node(@[0], range)
|
215
|
+
ctop = @[0].getBoundingClientRect().bottom - LINE_HEIGHT
|
216
|
+
else
|
217
|
+
ctop = range.getClientRects()[0].top
|
218
|
+
etop = @[0].getBoundingClientRect().top
|
219
|
+
return ctop < etop + LINE_HEIGHT
|
220
|
+
|
221
|
+
$.fn.editableCaretOnLastLine = ->
|
222
|
+
range = @editableRange()
|
223
|
+
return false unless range
|
224
|
+
if is_caret_at_end_of_node(@[0], range)
|
225
|
+
return true
|
226
|
+
else if is_caret_at_start_of_node(@[0], range)
|
227
|
+
# We are on the first line.
|
228
|
+
cbtm = @[0].getBoundingClientRect().top + LINE_HEIGHT
|
229
|
+
else
|
230
|
+
cbtm = range.getClientRects()[0].bottom
|
231
|
+
ebtm = @[0].getBoundingClientRect().bottom
|
232
|
+
return cbtm > ebtm - LINE_HEIGHT
|
233
|
+
|
234
|
+
$.fn.exists = ->
|
235
|
+
@.length > 0
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#a very light backbone.view like version
|
2
|
+
|
3
|
+
class Dante.View
|
4
|
+
|
5
|
+
constructor: (opts = {})->
|
6
|
+
@el = opts.el if opts.el
|
7
|
+
@._ensureElement()
|
8
|
+
@initialize.apply(@, arguments)
|
9
|
+
@._ensureEvents()
|
10
|
+
|
11
|
+
initialize: (opts={})->
|
12
|
+
|
13
|
+
events: ->
|
14
|
+
|
15
|
+
render: ()->
|
16
|
+
return @
|
17
|
+
|
18
|
+
remove: ()->
|
19
|
+
@._removeElement()
|
20
|
+
@.stopListening()
|
21
|
+
return @
|
22
|
+
|
23
|
+
_removeElement: ()->
|
24
|
+
@.$el.remove();
|
25
|
+
|
26
|
+
setElement: (element)->
|
27
|
+
#@.undelegateEvents()
|
28
|
+
@._setElement(element)
|
29
|
+
#@.delegateEvents()
|
30
|
+
return @
|
31
|
+
|
32
|
+
setEvent: (opts)->
|
33
|
+
if !_.isEmpty(opts)
|
34
|
+
_.each opts, (f, key)=>
|
35
|
+
key_arr = key.split(" ")
|
36
|
+
|
37
|
+
if _.isFunction(f)
|
38
|
+
func = f
|
39
|
+
else if _.isString(f)
|
40
|
+
func = @[f]
|
41
|
+
else
|
42
|
+
throw "error event needs a function or string"
|
43
|
+
|
44
|
+
element = if key_arr.length > 1 then key_arr.splice(1 , 3).join(" ") else null
|
45
|
+
|
46
|
+
$( @el ).on( key_arr[0], element, _.bind(func, this) )
|
47
|
+
|
48
|
+
_ensureElement: ()->
|
49
|
+
@.setElement(_.result(@, 'el'))
|
50
|
+
|
51
|
+
_ensureEvents: ()->
|
52
|
+
@.setEvent(_.result(@, 'events'))
|
53
|
+
|
54
|
+
_setElement: (el)->
|
55
|
+
@.$el = if el instanceof $ then el else $(el)
|
56
|
+
@.el = @.$el[0]
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
class TestView extends Dante.View
|
3
|
+
|
4
|
+
class TestViewWithEl extends Dante.View
|
5
|
+
el: "#view"
|
6
|
+
initialize: ()->
|
7
|
+
@model = {foo: "bar"}
|
8
|
+
super
|
9
|
+
|
10
|
+
class TestViewWithEvents extends Dante.View
|
11
|
+
el: "#view"
|
12
|
+
initialize: ->
|
13
|
+
@one = false
|
14
|
+
@two = false
|
15
|
+
@some_var = 1
|
16
|
+
|
17
|
+
events:
|
18
|
+
"click" : (ev)->
|
19
|
+
console.log("CLICKED ANYWHERE")
|
20
|
+
@anywhere = true
|
21
|
+
return false
|
22
|
+
|
23
|
+
"click .one" : (ev)->
|
24
|
+
@one = true
|
25
|
+
console.log("CLICKED .one YEAH, #{@one}")
|
26
|
+
return false
|
27
|
+
|
28
|
+
"click .two" : "handleClickTwo"
|
29
|
+
|
30
|
+
handleClickTwo: (ev)->
|
31
|
+
@two = true
|
32
|
+
console.log("CLICKED .two YEAH, #{@two}")
|
33
|
+
console.log(ev.currentTarget)
|
34
|
+
|
35
|
+
return false
|
36
|
+
|
37
|
+
class TestViewOpts extends Dante.View
|
38
|
+
el: "#view"
|
39
|
+
initialize: (opts={})->
|
40
|
+
|
41
|
+
@model = opts.model
|
42
|
+
|
43
|
+
window.view = new TestView
|
44
|
+
|
45
|
+
window.view2 = new TestView(el: "#view")
|
46
|
+
window.view3 = new TestViewWithEl
|
47
|
+
|
48
|
+
window.event_view = new TestViewWithEvents
|
49
|
+
window.opts_view = new TestViewOpts(model: 1234)
|
50
|
+
|
51
|
+
QUnit.test "should initialize view without el", ( assert )->
|
52
|
+
assert.ok( _.isObject(view), "Passed!" )
|
53
|
+
assert.ok( _.isUndefined(view.el), "Passed!" )
|
54
|
+
|
55
|
+
QUnit.test "should initialize view with el", ( assert )->
|
56
|
+
assert.ok( _.isElement(view2.el), "Passed!" )
|
57
|
+
assert.ok( _.isElement(view3.el), "Passed!" )
|
58
|
+
|
59
|
+
QUnit.test "should initialize view with @model", ( assert )->
|
60
|
+
assert.ok( !_.isEmpty(view3.model), "Passed!" )
|
61
|
+
|
62
|
+
QUnit.test "should set variable on click", ( assert )->
|
63
|
+
event_view.$el.find("a.one").trigger("click")
|
64
|
+
event_view.$el.find("a.two").trigger("click")
|
65
|
+
assert.ok( event_view.one, "Passed!" )
|
66
|
+
assert.ok( event_view.two, "Passed!" )
|
67
|
+
|
68
|
+
QUnit.test "should set vars on initialize", ( assert )->
|
69
|
+
assert.ok( event_view.some_var is 1, "Passed!" )
|
70
|
+
|
71
|
+
QUnit.test "should set windows.vars on initialize", ( assert )->
|
72
|
+
assert.ok( opts_view.model is 1234 , "Passed!" )
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
window.editor = new Dante.Editor
|
4
|
+
upload_url: "/images.json"
|
5
|
+
el: "#editor1"
|
6
|
+
|
7
|
+
window.editor.start()
|
8
|
+
|
9
|
+
window.editor2 = new Dante.Editor
|
10
|
+
el: "#editor2"
|
11
|
+
|
12
|
+
window.editor2.start()
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
QUnit.test "should initialize editor", ( assert )->
|
17
|
+
assert.ok( _.isObject(window.editor), "Passed!" )
|
18
|
+
|
19
|
+
QUnit.test "should init editor defaults", ( assert )->
|
20
|
+
assert.ok( !_.isEmpty(window.editor.title_placeholder), "Passed!" )
|
21
|
+
assert.ok( !_.isEmpty(window.editor.body_placeholder), "Passed!" )
|
22
|
+
assert.ok( !_.isEmpty(window.editor.embed_placeholder), "Passed!" )
|
23
|
+
assert.ok( !_.isEmpty(window.editor.extract_placeholder), "Passed!" )
|
24
|
+
|
25
|
+
assert.ok( !_.isEmpty(window.editor.upload_url), "Passed!" )
|
26
|
+
assert.ok( !_.isEmpty(window.editor.oembed_url), "Passed!" )
|
27
|
+
assert.ok( !_.isEmpty(window.editor.extract_url), "Passed!" )
|
28
|
+
|
29
|
+
QUnit.test "should init editor", ( assert )->
|
30
|
+
assert.ok( !_.isEmpty(window.editor), "Passed!" )
|
31
|
+
assert.ok( _.isObject(window.editor.tooltip_view), "Passed!" )
|
32
|
+
assert.ok( _.isObject(window.editor.editor_menu), "Passed!" )
|
33
|
+
|
34
|
+
QUnit.test "should build tooltip & menu", ( assert )->
|
35
|
+
assert.ok( !_.isEmpty( $(".inlineTooltip2") ), "Passed!" )
|
36
|
+
assert.ok( !_.isEmpty( $("#editor-menu") ), "Passed!" )
|
37
|
+
|
38
|
+
QUnit.test "should display placeholders when empty content", (assert)->
|
39
|
+
assert.ok $(editor.el).find("span.defaultValue").length is 2 , "Passed!"
|
40
|
+
|
41
|
+
##CLEANER
|
42
|
+
|
43
|
+
QUnit.test "should clean spans", (assert)->
|
44
|
+
assert.ok !$(editor2.el).find("a:first span").exists(), "Passed!"
|
45
|
+
assert.ok !$(editor2.el).find(".section-inner div.class").exists(), "Passed!"
|
46
|
+
assert.ok !$(editor2.el).find(".section-inner span:not(.defaultValue)").exists(), "Passed!"
|
47
|
+
assert.ok !$(editor2.el).find(".section-inner p span").exists() , "Passed!"
|
48
|
+
|
49
|
+
QUnit.test "should detect existing images", (assert)->
|
50
|
+
fig = $(editor2.el).find(".section-inner figure")
|
51
|
+
|
52
|
+
assert.ok $(fig).exists() , "generate figure.graf--figure"
|
53
|
+
assert.ok $(fig).find("img").exists() , "figure have image"
|
54
|
+
assert.ok !_.isEmpty($(fig).find("img").attr('src')) , "and image src"
|
55
|
+
|
56
|
+
assert.ok $(fig).find("figcaption").exists() , "and caption"
|
57
|
+
assert.ok $(fig).find("figcaption span.defaultValue").exists() , "and caption span"
|