dante-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 +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"
|