dante2-editor 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +33 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +42 -0
- data/README.md +107 -0
- data/app/assets/index.html +55 -0
- data/app/assets/license.html +52 -0
- data/app/assets/options.html +57 -0
- data/app/assets/store.json +1 -0
- data/app/components/App.cjsx +1083 -0
- data/app/components/blocks/embed.cjsx +109 -0
- data/app/components/blocks/image.cjsx +316 -0
- data/app/components/blocks/placeholder.cjsx +60 -0
- data/app/components/blocks/video.cjsx +75 -0
- data/app/components/debug.cjsx +96 -0
- data/app/components/decorators/link.cjsx +61 -0
- data/app/components/popovers/addButton.cjsx +247 -0
- data/app/components/popovers/image.cjsx +160 -0
- data/app/components/popovers/link.cjsx +87 -0
- data/app/components/popovers/toolTip.cjsx +326 -0
- data/app/data/poc.js +15 -0
- data/app/demo.js +7 -0
- data/app/images/dante/media-loading-placeholder.png +0 -0
- data/app/images/site/dante-demo.png +0 -0
- data/app/images/site/dante-editor-logo.png +0 -0
- data/app/images/site/github-logo.png +0 -0
- data/app/initialize.js +4 -0
- data/app/model/index.js +194 -0
- data/app/styles/dante.scss +22 -0
- data/app/styles/dante/_animations.scss +54 -0
- data/app/styles/dante/_caption.scss +61 -0
- data/app/styles/dante/_debug.scss +124 -0
- data/app/styles/dante/_fonts.scss +17 -0
- data/app/styles/dante/_graf.scss +242 -0
- data/app/styles/dante/_icons.scss +62 -0
- data/app/styles/dante/_media.scss +39 -0
- data/app/styles/dante/_menu.scss +201 -0
- data/app/styles/dante/_needsorder.scss +209 -0
- data/app/styles/dante/_popover.scss +212 -0
- data/app/styles/dante/_post.scss +67 -0
- data/app/styles/dante/_scaffold.scss +24 -0
- data/app/styles/dante/_tooltip.scss +130 -0
- data/app/styles/dante/_utilities.scss +59 -0
- data/app/styles/dante/_variables.scss +96 -0
- data/app/styles/dante/blame.scss +246 -0
- data/app/styles/draft.css +297 -0
- data/app/styles/fonts/dante/dante.eot +0 -0
- data/app/styles/fonts/dante/dante.svg +18 -0
- data/app/styles/fonts/dante/dante.ttf +0 -0
- data/app/styles/fonts/dante/dante.woff +0 -0
- data/app/styles/fonts/dante/fontello.eot +0 -0
- data/app/styles/fonts/dante/fontello.svg +36 -0
- data/app/styles/fonts/dante/fontello.ttf +0 -0
- data/app/styles/fonts/dante/fontello.woff +0 -0
- data/app/styles/layout/layout.scss +64 -0
- data/app/styles/layout/normalize.css +375 -0
- data/app/styles/layout/scaffold.scss +8 -0
- data/app/styles/layout/tooltips.scss +216 -0
- data/app/utils/find_entities.coffee +20 -0
- data/app/utils/html2content.coffee +120 -0
- data/app/utils/logger.coffee +0 -0
- data/app/utils/save_content.coffee +63 -0
- data/app/utils/selection.js +53 -0
- data/config.ru +64 -0
- data/dante2.gemspec +19 -0
- data/docs/app.css +2 -0
- data/docs/app.css.map +1 -0
- data/docs/app.js +3 -0
- data/docs/app.js.map +1 -0
- data/docs/dante-vendors.js +28 -0
- data/docs/dante-vendors.js.map +1 -0
- data/docs/dante.css +2 -0
- data/docs/dante.css.map +1 -0
- data/docs/dante.js +4 -0
- data/docs/dante.js.map +1 -0
- data/docs/doc.html +57 -0
- data/docs/fonts/dante.eot +0 -0
- data/docs/fonts/dante.svg +18 -0
- data/docs/fonts/dante.ttf +0 -0
- data/docs/fonts/dante.woff +0 -0
- data/docs/fonts/fontello.eot +0 -0
- data/docs/fonts/fontello.svg +36 -0
- data/docs/fonts/fontello.ttf +0 -0
- data/docs/fonts/fontello.woff +0 -0
- data/docs/images/dante-editor-logo.png +0 -0
- data/docs/images/github-logo.png +0 -0
- data/docs/index.html +55 -0
- data/docs/license.html +52 -0
- data/lib/dante2-editor.rb +5 -0
- data/lib/dante2-editor/rails.rb +16 -0
- data/lib/dante2-editor/version.rb +5 -0
- data/package.json +61 -0
- data/rakefile +1 -0
- data/webpack.config.js +148 -0
- data/yarn.lock +4704 -0
- metadata +139 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
React = require('react')
|
3
|
+
ReactDOM = require('react-dom')
|
4
|
+
|
5
|
+
{
|
6
|
+
Entity,
|
7
|
+
RichUtils
|
8
|
+
AtomicBlockUtils
|
9
|
+
EditorBlock
|
10
|
+
} = require('draft-js')
|
11
|
+
|
12
|
+
{ updateDataOfBlock } = require('../../model/index.js')
|
13
|
+
|
14
|
+
axios = require("axios")
|
15
|
+
|
16
|
+
class VideoBlock extends React.Component
|
17
|
+
constructor: (props) ->
|
18
|
+
super props
|
19
|
+
#api_key = "86c28a410a104c8bb58848733c82f840"
|
20
|
+
|
21
|
+
@state =
|
22
|
+
embed_data: @defaultData()
|
23
|
+
|
24
|
+
defaultData: ->
|
25
|
+
existing_data = @.props.block.getData().toJS()
|
26
|
+
existing_data.embed_data || {}
|
27
|
+
|
28
|
+
# will update block state
|
29
|
+
updateData: =>
|
30
|
+
blockProps = @.props.blockProps
|
31
|
+
block = @.props.block
|
32
|
+
getEditorState = @props.blockProps.getEditorState
|
33
|
+
setEditorState = @props.blockProps.setEditorState
|
34
|
+
data = block.getData();
|
35
|
+
newData = data.merge(@state)
|
36
|
+
setEditorState(updateDataOfBlock(getEditorState(), block, newData))
|
37
|
+
|
38
|
+
dataForUpdate: =>
|
39
|
+
@.props.blockProps.data.toJS()
|
40
|
+
|
41
|
+
componentDidMount: ->
|
42
|
+
|
43
|
+
return unless @.props.blockProps.data
|
44
|
+
# ensure data isnt already loaded
|
45
|
+
return unless @dataForUpdate().endpoint or @dataForUpdate().provisory_text
|
46
|
+
|
47
|
+
axios
|
48
|
+
method: 'get'
|
49
|
+
url: "#{@.dataForUpdate().endpoint}#{@.dataForUpdate().provisory_text}&scheme=https"
|
50
|
+
.then (result)=>
|
51
|
+
@setState
|
52
|
+
embed_data: result.data #JSON.parse(data.responseText)
|
53
|
+
, @updateData
|
54
|
+
.catch (error)=>
|
55
|
+
console.log("TODO: error")
|
56
|
+
|
57
|
+
classForImage: ->
|
58
|
+
if @state.embed_data.thumbnail_url then "" else "mixtapeImage--empty u-ignoreBlock"
|
59
|
+
|
60
|
+
render: ->
|
61
|
+
return(
|
62
|
+
<figure
|
63
|
+
className='graf--figure graf--iframe graf--first' tabIndex='0'>
|
64
|
+
<div className='iframeContainer' dangerouslySetInnerHTML={__html: @state.embed_data.html}>
|
65
|
+
</div>
|
66
|
+
<figcaption
|
67
|
+
className='imageCaption'>
|
68
|
+
<EditorBlock {...@props}
|
69
|
+
className="imageCaption"
|
70
|
+
/>
|
71
|
+
</figcaption>
|
72
|
+
</figure>
|
73
|
+
)
|
74
|
+
|
75
|
+
module.exports = VideoBlock
|
@@ -0,0 +1,96 @@
|
|
1
|
+
React = require('react')
|
2
|
+
|
3
|
+
class Debug extends React.Component
|
4
|
+
|
5
|
+
constructor: (options={})->
|
6
|
+
@state =
|
7
|
+
output: ""
|
8
|
+
display: "none"
|
9
|
+
|
10
|
+
handleToggleReadOnly: (e)=>
|
11
|
+
e.preventDefault()
|
12
|
+
@props.editor.toggleEditable()
|
13
|
+
return false
|
14
|
+
|
15
|
+
handleTestEmitAndDecode: (e)=>
|
16
|
+
e.preventDefault()
|
17
|
+
@.testEmitAndDecode()
|
18
|
+
|
19
|
+
handleTestEmitTEXT: (e)=>
|
20
|
+
e.preventDefault()
|
21
|
+
@.testEmitTEXT()
|
22
|
+
|
23
|
+
testEmitAndDecode: (e)=>
|
24
|
+
raw_as_json = @props.editor.emitSerializedOutput()
|
25
|
+
@props.editor.setState
|
26
|
+
editorState: @props.editor.decodeEditorContent(raw_as_json)
|
27
|
+
, @logState(JSON.stringify(raw_as_json))
|
28
|
+
false
|
29
|
+
|
30
|
+
testEmitTEXT: ()=>
|
31
|
+
text = @props.editor.getTextFromEditor()
|
32
|
+
@logState(text)
|
33
|
+
|
34
|
+
logState: (raw)=>
|
35
|
+
@setState
|
36
|
+
output: raw
|
37
|
+
, @open
|
38
|
+
|
39
|
+
toggleDisplay: (e)=>
|
40
|
+
e.preventDefault()
|
41
|
+
d = if @state.display is "block" then "none" else @state.display
|
42
|
+
@setState
|
43
|
+
display: d
|
44
|
+
|
45
|
+
open: =>
|
46
|
+
@setState
|
47
|
+
display: "block"
|
48
|
+
|
49
|
+
render: =>
|
50
|
+
return (
|
51
|
+
|
52
|
+
<div>
|
53
|
+
<div className="debugControls">
|
54
|
+
<ul>
|
55
|
+
<li>LOCKS: {@props.editor.state.locks}</li>
|
56
|
+
<li>
|
57
|
+
<a href="#" onClick={@handleToggleReadOnly}>
|
58
|
+
EDITABLE: {if @props.editor.state.read_only then 'NO' else 'YES'}
|
59
|
+
</a>
|
60
|
+
</li>
|
61
|
+
<li>
|
62
|
+
<a href="#" onClick={@handleTestEmitTEXT}>
|
63
|
+
EDITOR TEXT
|
64
|
+
</a>
|
65
|
+
</li>
|
66
|
+
|
67
|
+
<li>
|
68
|
+
<a href="#" onClick={@handleTestEmitAndDecode}>
|
69
|
+
EDITOR STATE
|
70
|
+
</a>
|
71
|
+
</li>
|
72
|
+
|
73
|
+
</ul>
|
74
|
+
</div>
|
75
|
+
|
76
|
+
<div className="debugZone" style={display: @state.display}>
|
77
|
+
<a href="#"
|
78
|
+
className="dante-debug-close close"
|
79
|
+
onClick={@toggleDisplay}
|
80
|
+
/>
|
81
|
+
|
82
|
+
<div className="debugOutput">
|
83
|
+
<h2>EDITOR OUTPUT</h2>
|
84
|
+
{
|
85
|
+
if @state.output.length > 0
|
86
|
+
<pre>
|
87
|
+
{@state.output}
|
88
|
+
</pre>
|
89
|
+
}
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
)
|
94
|
+
|
95
|
+
|
96
|
+
module.exports = Debug
|
@@ -0,0 +1,61 @@
|
|
1
|
+
React = require('react')
|
2
|
+
{Entity} = require('draft-js')
|
3
|
+
|
4
|
+
###
|
5
|
+
Link = (props) ->
|
6
|
+
data = Entity.get(props.entityKey).getData();
|
7
|
+
console.log props
|
8
|
+
#onMouseOver={@props.showPopLinkOver}>
|
9
|
+
#onMouseOut={@props.hidePopLinkOver}>
|
10
|
+
return (
|
11
|
+
<a href={data.url} className="markup--anchor">
|
12
|
+
{props.children}
|
13
|
+
</a>
|
14
|
+
);
|
15
|
+
###
|
16
|
+
|
17
|
+
class Link extends React.Component
|
18
|
+
|
19
|
+
constructor: (props) ->
|
20
|
+
super props
|
21
|
+
@isHover = false
|
22
|
+
|
23
|
+
_validateLink: ()=>
|
24
|
+
pattern = new RegExp('^(https?:\/\/)?'+ # protocol
|
25
|
+
'((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|'+ # domain name
|
26
|
+
'((\d{1,3}\.){3}\d{1,3}))'+ # OR ip (v4) address
|
27
|
+
'(\:\d+)?(\/[-a-z\d%_.~+]*)*'+ # port and path
|
28
|
+
'(\?[;&a-z\d%_.~+=-]*)?'+ # query string
|
29
|
+
'(\#[-a-z\d_]*)?$','i') # fragment locater
|
30
|
+
if !pattern.test(str)
|
31
|
+
alert("Please enter a valid URL.")
|
32
|
+
return false
|
33
|
+
else
|
34
|
+
return true
|
35
|
+
|
36
|
+
_checkProtocol: ()=>
|
37
|
+
console.log "xcvd"
|
38
|
+
|
39
|
+
|
40
|
+
_showPopLinkOver: (e)=>
|
41
|
+
return unless @data.showPopLinkOver
|
42
|
+
@data.showPopLinkOver(@refs.link)
|
43
|
+
|
44
|
+
_hidePopLinkOver: (e)=>
|
45
|
+
return unless @data.hidePopLinkOver
|
46
|
+
@data.hidePopLinkOver()
|
47
|
+
|
48
|
+
render: ->
|
49
|
+
@data = Entity.get(@props.entityKey).getData();
|
50
|
+
|
51
|
+
return (
|
52
|
+
<a ref="link"
|
53
|
+
href={@data.url}
|
54
|
+
className="markup--anchor"
|
55
|
+
onMouseOver={@_showPopLinkOver}
|
56
|
+
onMouseOut={@_hidePopLinkOver}>
|
57
|
+
{@props.children}
|
58
|
+
</a>
|
59
|
+
);
|
60
|
+
|
61
|
+
module.exports = Link
|
@@ -0,0 +1,247 @@
|
|
1
|
+
React = require('react')
|
2
|
+
ReactDOM = require('react-dom')
|
3
|
+
|
4
|
+
{
|
5
|
+
Entity
|
6
|
+
RichUtils
|
7
|
+
AtomicBlockUtils
|
8
|
+
EditorState
|
9
|
+
} = require('draft-js')
|
10
|
+
|
11
|
+
{
|
12
|
+
addNewBlock
|
13
|
+
resetBlockWithType
|
14
|
+
updateDataOfBlock
|
15
|
+
getCurrentBlock
|
16
|
+
getNode
|
17
|
+
} = require('../../model/index.js')
|
18
|
+
|
19
|
+
{
|
20
|
+
getSelectionRect
|
21
|
+
getSelection
|
22
|
+
} = require("../../utils/selection.js")
|
23
|
+
|
24
|
+
class DanteInlineTooltip extends React.Component
|
25
|
+
|
26
|
+
constructor: (props) ->
|
27
|
+
super props
|
28
|
+
|
29
|
+
@state =
|
30
|
+
position: {top: 0, left:0}
|
31
|
+
show: false
|
32
|
+
scaled: false
|
33
|
+
|
34
|
+
display: (b)=>
|
35
|
+
if b then @show() else @hide()
|
36
|
+
|
37
|
+
show: =>
|
38
|
+
@setState
|
39
|
+
show: true
|
40
|
+
|
41
|
+
hide: =>
|
42
|
+
@setState
|
43
|
+
show: false
|
44
|
+
|
45
|
+
setPosition: (coords)->
|
46
|
+
@setState
|
47
|
+
position: coords
|
48
|
+
|
49
|
+
_toggleScaled: (ev)=>
|
50
|
+
if @state.scaled then @collapse() else @scale()
|
51
|
+
|
52
|
+
scale: =>
|
53
|
+
@setState
|
54
|
+
scaled: true
|
55
|
+
|
56
|
+
collapse: =>
|
57
|
+
@setState
|
58
|
+
scaled: false
|
59
|
+
|
60
|
+
componentWillReceiveProps: (newProps)=>
|
61
|
+
@collapse()
|
62
|
+
|
63
|
+
activeClass: ->
|
64
|
+
#if @props.show then "is-active" else ""
|
65
|
+
if @isActive() then "is-active" else ""
|
66
|
+
|
67
|
+
isActive: ->
|
68
|
+
@state.show
|
69
|
+
|
70
|
+
scaledClass: ->
|
71
|
+
if @state.scaled then "is-scaled" else ""
|
72
|
+
|
73
|
+
scaledWidth: ->
|
74
|
+
if @state.scaled then "124" else "0"
|
75
|
+
|
76
|
+
clickOnFileUpload: =>
|
77
|
+
@.refs.fileInput.click()
|
78
|
+
@collapse()
|
79
|
+
@hide()
|
80
|
+
|
81
|
+
handlePlaceholder: (input)=>
|
82
|
+
opts =
|
83
|
+
type: input.widget_options.insert_block
|
84
|
+
placeholder: input.options.placeholder
|
85
|
+
endpoint: input.options.endpoint
|
86
|
+
|
87
|
+
@props.onChange(resetBlockWithType(@props.editorState, 'placeholder', opts));
|
88
|
+
|
89
|
+
insertImage: (file) =>
|
90
|
+
opts =
|
91
|
+
url: URL.createObjectURL(file)
|
92
|
+
file: file
|
93
|
+
|
94
|
+
@props.onChange(addNewBlock(@props.editorState, 'image', opts));
|
95
|
+
|
96
|
+
handleFileInput: (e)=>
|
97
|
+
fileList = e.target.files
|
98
|
+
# TODO: support multiple file uploads
|
99
|
+
###
|
100
|
+
Object.keys(fileList).forEach (o)=>
|
101
|
+
@.insertImage(fileList[0])
|
102
|
+
###
|
103
|
+
@.insertImage(fileList[0])
|
104
|
+
|
105
|
+
widgets: =>
|
106
|
+
@.props.editor.widgets
|
107
|
+
|
108
|
+
clickHandler: (e, type)=>
|
109
|
+
request_block = @widgets().find (o)->
|
110
|
+
o.icon is type
|
111
|
+
|
112
|
+
switch request_block.widget_options.insertion
|
113
|
+
when "upload"
|
114
|
+
@clickOnFileUpload(e, request_block)
|
115
|
+
when "placeholder"
|
116
|
+
@handlePlaceholder(request_block)
|
117
|
+
else
|
118
|
+
console.log "WRONG TYPE FOR #{request_block.widget_options.insertion}"
|
119
|
+
|
120
|
+
getItems: ->
|
121
|
+
@widgets().filter (o)=>
|
122
|
+
o.widget_options.displayOnInlineTooltip
|
123
|
+
|
124
|
+
isDescendant: (parent, child)->
|
125
|
+
node = child.parentNode
|
126
|
+
while node != null
|
127
|
+
if (node is parent)
|
128
|
+
return true
|
129
|
+
node = node.parentNode
|
130
|
+
return false
|
131
|
+
|
132
|
+
relocate: ()=>
|
133
|
+
editorState = @props.editorState
|
134
|
+
|
135
|
+
if editorState.getSelection().isCollapsed()
|
136
|
+
|
137
|
+
currentBlock = getCurrentBlock(editorState)
|
138
|
+
blockType = currentBlock.getType()
|
139
|
+
|
140
|
+
contentState = editorState.getCurrentContent()
|
141
|
+
selectionState = editorState.getSelection()
|
142
|
+
|
143
|
+
block = contentState.getBlockForKey(selectionState.anchorKey);
|
144
|
+
|
145
|
+
nativeSelection = getSelection(window);
|
146
|
+
if !nativeSelection.rangeCount
|
147
|
+
return;
|
148
|
+
|
149
|
+
node = getNode()
|
150
|
+
|
151
|
+
selectionBoundary = getSelectionRect(nativeSelection);
|
152
|
+
coords = selectionBoundary #utils.getSelectionDimensions(node)
|
153
|
+
|
154
|
+
parent = ReactDOM.findDOMNode(@props.editor);
|
155
|
+
parentBoundary = parent.getBoundingClientRect();
|
156
|
+
|
157
|
+
# hide if selected node is not in editor
|
158
|
+
# debugger
|
159
|
+
#console.log @isDescendant(parent, nativeSelection.anchorNode)
|
160
|
+
|
161
|
+
if !@isDescendant(parent, nativeSelection.anchorNode)
|
162
|
+
@hide()
|
163
|
+
return
|
164
|
+
|
165
|
+
# checkeamos si esta vacio
|
166
|
+
@display block.getText().length is 0 and blockType is "unstyled"
|
167
|
+
@setPosition
|
168
|
+
top: coords.top + window.scrollY
|
169
|
+
left: coords.left + window.scrollX - 60
|
170
|
+
|
171
|
+
###
|
172
|
+
@refs.image_popover.display(blockType is "image")
|
173
|
+
|
174
|
+
if blockType is "image"
|
175
|
+
selectionBoundary = node.anchorNode.parentNode.parentNode.parentNode.getBoundingClientRect()
|
176
|
+
#el = document.querySelector("#dante_image_popover")
|
177
|
+
el = @refs.image_popover.refs.image_popover
|
178
|
+
padd = el.offsetWidth / 2
|
179
|
+
@refs.image_popover.setPosition
|
180
|
+
top: selectionBoundary.top - parentBoundary.top + 60
|
181
|
+
left: selectionBoundary.left + (selectionBoundary.width / 2) - padd
|
182
|
+
|
183
|
+
|
184
|
+
#@setState
|
185
|
+
# image_popover_position:
|
186
|
+
# top: selectionBoundary.top - parentBoundary.top + 60
|
187
|
+
# left: selectionBoundary.left + (selectionBoundary.width / 2) - padd
|
188
|
+
#
|
189
|
+
###
|
190
|
+
|
191
|
+
else
|
192
|
+
@hide()
|
193
|
+
|
194
|
+
render: ->
|
195
|
+
return (
|
196
|
+
|
197
|
+
<div className="inlineTooltip #{@activeClass()} #{@scaledClass()}"
|
198
|
+
style={@state.position}>
|
199
|
+
|
200
|
+
<button className="inlineTooltip-button control"
|
201
|
+
title="Close Menu"
|
202
|
+
data-action="inline-menu"
|
203
|
+
onClick={@_toggleScaled}
|
204
|
+
>
|
205
|
+
<span className="tooltip-icon dante-icon-plus"></span>
|
206
|
+
</button>
|
207
|
+
|
208
|
+
<div className="inlineTooltip-menu" style={{width: "#{@scaledWidth()}px"}}>
|
209
|
+
|
210
|
+
{
|
211
|
+
@getItems().map (item, i)=>
|
212
|
+
<InlineTooltipItem
|
213
|
+
item={item}
|
214
|
+
key={i}
|
215
|
+
clickHandler={@clickHandler}
|
216
|
+
/>
|
217
|
+
}
|
218
|
+
|
219
|
+
<input type="file"
|
220
|
+
style={{display: 'none'}}
|
221
|
+
ref="fileInput"
|
222
|
+
multiple="multiple"
|
223
|
+
onChange={@.handleFileInput}
|
224
|
+
/>
|
225
|
+
|
226
|
+
</div>
|
227
|
+
|
228
|
+
</div>
|
229
|
+
|
230
|
+
)
|
231
|
+
|
232
|
+
class InlineTooltipItem extends React.Component
|
233
|
+
|
234
|
+
clickHandler: (e)=>
|
235
|
+
e.preventDefault()
|
236
|
+
@props.clickHandler(e, @props.item.icon)
|
237
|
+
|
238
|
+
render: ->
|
239
|
+
return (
|
240
|
+
<button className="inlineTooltip-button scale"
|
241
|
+
title={@props.title} onMouseDown={@clickHandler}>
|
242
|
+
<span className="tooltip-icon dante-icon-#{@props.item.icon}">
|
243
|
+
</span>
|
244
|
+
</button>
|
245
|
+
)
|
246
|
+
|
247
|
+
module.exports = DanteInlineTooltip
|