dante2-editor 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/.babelrc +20 -0
  3. data/.eslint +13 -0
  4. data/.gitignore +15 -0
  5. data/README.md +5 -1
  6. data/dante2.gemspec +2 -4
  7. data/{app → demo}/assets/index.html +2 -2
  8. data/{app → demo}/assets/license.html +1 -1
  9. data/{app → demo}/assets/options.html +1 -5
  10. data/{app → demo}/assets/store.json +0 -0
  11. data/demo/data/poc.js +9 -0
  12. data/demo/demo.js +9 -0
  13. data/demo/initialize.js +7 -0
  14. data/{app → demo}/styles/layout/layout.scss +0 -0
  15. data/{app → demo}/styles/layout/normalize.css +0 -0
  16. data/{app → demo}/styles/layout/scaffold.scss +0 -0
  17. data/{app → demo}/styles/layout/tooltips.scss +0 -0
  18. data/dist/Dante2.js +2995 -0
  19. data/dist/Dante2.min.js +3 -0
  20. data/dist/Dante2.min.js.map +1 -0
  21. data/dist/DanteStyles.css +2 -0
  22. data/dist/DanteStyles.css.map +1 -0
  23. data/dist/DanteStyles.js +102 -0
  24. data/dist/DanteStyles.min.js +2 -0
  25. data/dist/DanteStyles.min.js.map +1 -0
  26. data/dist/dante-vendors.js +48316 -0
  27. data/dist/dante-vendors.min.js +29 -0
  28. data/dist/dante-vendors.min.js.map +1 -0
  29. data/{app/styles/fonts/dante → dist/fonts}/dante.eot +0 -0
  30. data/{app/styles/fonts/dante → dist/fonts}/dante.svg +0 -0
  31. data/{app/styles/fonts/dante → dist/fonts}/dante.ttf +0 -0
  32. data/{app/styles/fonts/dante → dist/fonts}/dante.woff +0 -0
  33. data/{app/styles/fonts/dante → dist/fonts}/fontello.eot +0 -0
  34. data/{app/styles/fonts/dante → dist/fonts}/fontello.svg +0 -0
  35. data/{app/styles/fonts/dante → dist/fonts}/fontello.ttf +0 -0
  36. data/{app/styles/fonts/dante → dist/fonts}/fontello.woff +0 -0
  37. data/docs/app.css +430 -1
  38. data/docs/app.css.map +1 -1
  39. data/docs/app.js +62 -2
  40. data/docs/app.js.map +1 -1
  41. data/docs/dante-vendors.js +49513 -22
  42. data/docs/dante-vendors.js.map +1 -1
  43. data/docs/dante.css +1602 -1
  44. data/docs/dante.css.map +1 -1
  45. data/docs/dante.js +3310 -3
  46. data/docs/dante.js.map +1 -1
  47. data/docs/doc.html +3 -7
  48. data/docs/index.html +4 -4
  49. data/docs/license.html +3 -3
  50. data/docs/options.html +53 -0
  51. data/package.json +47 -17
  52. data/rb_lib/dante2-editor/rails.rb +15 -0
  53. data/{lib → rb_lib}/dante2-editor/version.rb +1 -1
  54. data/{lib → rb_lib}/dante2-editor.rb +0 -0
  55. data/src/components/blocks/embed.js +126 -0
  56. data/src/components/blocks/image.js +377 -0
  57. data/src/components/blocks/placeholder.js +68 -0
  58. data/src/components/blocks/video.js +80 -0
  59. data/src/components/dante.js +291 -0
  60. data/src/components/dante_editor.js +928 -0
  61. data/src/components/debug.js +104 -0
  62. data/src/components/decorators/link.js +64 -0
  63. data/src/components/popovers/addButton.js +318 -0
  64. data/src/components/popovers/image.js +188 -0
  65. data/src/components/popovers/link.js +107 -0
  66. data/src/components/popovers/toolTip.js +402 -0
  67. data/{app/utils/logger.coffee → src/components/wrapper.js} +0 -0
  68. data/{app → src}/images/dante/media-loading-placeholder.png +0 -0
  69. data/{app → src}/images/site/dante-demo.png +0 -0
  70. data/{app → src}/images/site/dante-editor-logo.png +0 -0
  71. data/{app → src}/images/site/github-logo.png +0 -0
  72. data/src/index.js +9 -0
  73. data/{app → src}/model/index.js +0 -0
  74. data/src/style.js +3 -0
  75. data/{app → src}/styles/dante/_animations.scss +0 -0
  76. data/{app → src}/styles/dante/_caption.scss +0 -0
  77. data/{app → src}/styles/dante/_debug.scss +0 -0
  78. data/{app → src}/styles/dante/_fonts.scss +0 -0
  79. data/{app → src}/styles/dante/_graf.scss +0 -0
  80. data/{app → src}/styles/dante/_icons.scss +0 -0
  81. data/{app → src}/styles/dante/_media.scss +0 -0
  82. data/{app → src}/styles/dante/_menu.scss +0 -0
  83. data/{app → src}/styles/dante/_needsorder.scss +0 -0
  84. data/{app → src}/styles/dante/_popover.scss +0 -0
  85. data/{app → src}/styles/dante/_post.scss +0 -0
  86. data/{app → src}/styles/dante/_scaffold.scss +0 -0
  87. data/{app → src}/styles/dante/_tooltip.scss +0 -0
  88. data/{app → src}/styles/dante/_utilities.scss +0 -0
  89. data/{app → src}/styles/dante/_variables.scss +0 -0
  90. data/{app → src}/styles/dante/blame.scss +0 -0
  91. data/{app → src}/styles/dante.scss +0 -0
  92. data/{app → src}/styles/draft.css +0 -0
  93. data/src/styles/fonts/dante/dante.eot +0 -0
  94. data/src/styles/fonts/dante/dante.svg +18 -0
  95. data/src/styles/fonts/dante/dante.ttf +0 -0
  96. data/src/styles/fonts/dante/dante.woff +0 -0
  97. data/src/styles/fonts/dante/fontello.eot +0 -0
  98. data/src/styles/fonts/dante/fontello.svg +36 -0
  99. data/src/styles/fonts/dante/fontello.ttf +0 -0
  100. data/src/styles/fonts/dante/fontello.woff +0 -0
  101. data/src/utils/find_entities.js +17 -0
  102. data/src/utils/html2content.js +127 -0
  103. data/src/utils/save_content.js +80 -0
  104. data/{app → src}/utils/selection.js +0 -0
  105. data/tools/.eslintrc +9 -0
  106. data/tools/amd/README.md +7 -0
  107. data/tools/amd/bower.json +19 -0
  108. data/tools/amd/build.js +36 -0
  109. data/tools/build-cli.js +46 -0
  110. data/tools/build.js +30 -0
  111. data/tools/buildBabel.js +32 -0
  112. data/tools/constants.js +10 -0
  113. data/tools/dist/build.js +13 -0
  114. data/tools/docs/build.js +12 -0
  115. data/tools/es/build.js +27 -0
  116. data/tools/exec.js +50 -0
  117. data/tools/fs-utils.js +35 -0
  118. data/tools/lib/build.js +14 -0
  119. data/tools/promisify.js +14 -0
  120. data/webpack/base.config.js +89 -0
  121. data/webpack/docs.config.js +147 -0
  122. data/webpack/webpack.config.js +78 -0
  123. data/webpack.config-doc.js +5 -0
  124. data/webpack.config.js +3 -147
  125. data/yarn.lock +973 -718
  126. metadata +108 -64
  127. data/app/components/App.cjsx +0 -1083
  128. data/app/components/blocks/embed.cjsx +0 -109
  129. data/app/components/blocks/image.cjsx +0 -316
  130. data/app/components/blocks/placeholder.cjsx +0 -60
  131. data/app/components/blocks/video.cjsx +0 -75
  132. data/app/components/debug.cjsx +0 -96
  133. data/app/components/decorators/link.cjsx +0 -61
  134. data/app/components/popovers/addButton.cjsx +0 -247
  135. data/app/components/popovers/image.cjsx +0 -160
  136. data/app/components/popovers/link.cjsx +0 -87
  137. data/app/components/popovers/toolTip.cjsx +0 -326
  138. data/app/data/poc.js +0 -15
  139. data/app/demo.js +0 -7
  140. data/app/initialize.js +0 -4
  141. data/app/utils/find_entities.coffee +0 -20
  142. data/app/utils/html2content.coffee +0 -120
  143. data/app/utils/save_content.coffee +0 -63
  144. data/lib/dante2-editor/rails.rb +0 -16
@@ -0,0 +1,928 @@
1
+
2
+ import React from 'react'
3
+ import ReactDOM from 'react-dom'
4
+ import Immutable from 'immutable'
5
+ import { Map, fromJS } from 'immutable'
6
+ import {
7
+ convertToRaw,
8
+ convertFromRaw,
9
+ CompositeDecorator,
10
+ //getVisibleSelectionRect,
11
+ getDefaultKeyBinding,
12
+ //getSelectionOffsetKeyForNode,
13
+ //KeyBindingUtil,
14
+ ContentState,
15
+ Editor,
16
+ EditorState,
17
+ Entity,
18
+ RichUtils,
19
+ DefaultDraftBlockRenderMap,
20
+ SelectionState,
21
+ Modifier,
22
+ //BlockMapBuilder,
23
+ //getSafeBodyFromHTML
24
+ } from 'draft-js'
25
+
26
+ //import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor'
27
+
28
+ import {
29
+ convertToHTML,
30
+ //, convertFromHTML
31
+ } from 'draft-convert'
32
+
33
+ //import isSoftNewlineEvent from 'draft-js/lib/isSoftNewlineEvent'
34
+
35
+ import {
36
+ addNewBlock,
37
+ resetBlockWithType,
38
+ updateDataOfBlock,
39
+ //updateTextOfBlock,
40
+ getCurrentBlock,
41
+ addNewBlockAt
42
+ } from '../model/index.js'
43
+
44
+ //import DanteImagePopover from './popovers/image'
45
+ //import DanteAnchorPopover from './popovers/link'
46
+
47
+ //import { getSelectionRect, getSelection } from "../utils/selection.js"
48
+ //import DanteInlineTooltip from './popovers/addButton'
49
+ //import DanteTooltip from './popovers/toolTip'
50
+ import Link from './decorators/link'
51
+
52
+ import Debug from './debug'
53
+ import findEntities from '../utils/find_entities'
54
+
55
+ /*import ImageBlock from './blocks/image'
56
+ import EmbedBlock from './blocks/embed'
57
+ import VideoBlock from './blocks/video'
58
+ import PlaceholderBlock from './blocks/placeholder'*/
59
+
60
+ import SaveBehavior from '../utils/save_content'
61
+ import customHTML2Content from '../utils/html2content'
62
+
63
+
64
+ class DanteEditor extends React.Component {
65
+ constructor(props) {
66
+ super(props)
67
+
68
+ this.initializeState = this.initializeState.bind(this)
69
+ this.refreshSelection = this.refreshSelection.bind(this)
70
+ this.forceRender = this.forceRender.bind(this)
71
+ this.onChange = this.onChange.bind(this)
72
+ this.dispatchChangesToSave = this.dispatchChangesToSave.bind(this)
73
+ this.setPreContent = this.setPreContent.bind(this)
74
+ this.focus = this.focus.bind(this)
75
+ this.getEditorState = this.getEditorState.bind(this)
76
+ this.emitSerializedOutput = this.emitSerializedOutput.bind(this)
77
+ this.decodeEditorContent = this.decodeEditorContent.bind(this)
78
+ this.getTextFromEditor = this.getTextFromEditor.bind(this)
79
+ this.getLocks = this.getLocks.bind(this)
80
+ this.addLock = this.addLock.bind(this)
81
+ this.removeLock = this.removeLock.bind(this)
82
+ this.renderableBlocks = this.renderableBlocks.bind(this)
83
+ this.defaultWrappers = this.defaultWrappers.bind(this)
84
+ this.blockRenderer = this.blockRenderer.bind(this)
85
+ this.handleBlockRenderer = this.handleBlockRenderer.bind(this)
86
+ this.blockStyleFn = this.blockStyleFn.bind(this)
87
+ this.getDataBlock = this.getDataBlock.bind(this)
88
+ this.styleForBlock = this.styleForBlock.bind(this)
89
+ this.handlePasteText = this.handlePasteText.bind(this)
90
+ this.handleTXTPaste = this.handleTXTPaste.bind(this)
91
+ this.handleHTMLPaste = this.handleHTMLPaste.bind(this)
92
+ this.handlePasteImage = this.handlePasteImage.bind(this)
93
+ this.handleDroppedFiles = this.handleDroppedFiles.bind(this)
94
+ this.handleUpArrow = this.handleUpArrow.bind(this)
95
+ this.handleDownArrow = this.handleDownArrow.bind(this)
96
+ this.handleReturn = this.handleReturn.bind(this)
97
+ this.handleBeforeInput = this.handleBeforeInput.bind(this)
98
+ this.handleKeyCommand = this.handleKeyCommand.bind(this)
99
+ this.findCommandKey = this.findCommandKey.bind(this)
100
+ this.KeyBindingFn = this.KeyBindingFn.bind(this)
101
+ this.updateBlockData = this.updateBlockData.bind(this)
102
+ this.setDirection = this.setDirection.bind(this)
103
+ this.toggleEditable = this.toggleEditable.bind(this)
104
+ this.closePopOvers = this.closePopOvers.bind(this)
105
+ this.relocateTooltips = this.relocateTooltips.bind(this)
106
+ this.tooltipsWithProp = this.tooltipsWithProp.bind(this)
107
+ this.tooltipHasSelectionElement = this.tooltipHasSelectionElement.bind(this)
108
+ this.handleShowPopLinkOver = this.handleShowPopLinkOver.bind(this)
109
+ this.handleHidePopLinkOver = this.handleHidePopLinkOver.bind(this)
110
+ this.showPopLinkOver = this.showPopLinkOver.bind(this)
111
+ this.hidePopLinkOver = this.hidePopLinkOver.bind(this)
112
+ this.render = this.render.bind(this)
113
+ this.decorator = new CompositeDecorator([{
114
+ strategy: findEntities.bind(null, 'LINK', this),
115
+ component: Link
116
+ }])
117
+
118
+ this.blockRenderMap = Map({
119
+ "image": {
120
+ element: 'figure'
121
+ },
122
+ "video": {
123
+ element: 'figure'
124
+ },
125
+ "embed": {
126
+ element: 'div'
127
+ },
128
+ 'unstyled': {
129
+ wrapper: null,
130
+ element: 'div'
131
+ },
132
+ 'paragraph': {
133
+ wrapper: null,
134
+ element: 'div'
135
+ },
136
+ 'placeholder': {
137
+ wrapper: null,
138
+ element: 'div'
139
+ }
140
+
141
+ })
142
+
143
+ this.extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(this.blockRenderMap)
144
+
145
+ this.state = {
146
+ editorState: this.initializeState(),
147
+ read_only: this.props.config.read_only,
148
+ blockRenderMap: this.extendedBlockRenderMap,
149
+ locks: 0,
150
+ debug: this.props.config.debug
151
+ }
152
+
153
+ this.widgets = this.props.config.widgets
154
+ this.tooltips = this.props.config.tooltips
155
+
156
+ this.key_commands = this.props.config.key_commands
157
+
158
+ this.continuousBlocks = this.props.config.continuousBlocks
159
+
160
+ this.block_types = this.props.config.block_types
161
+
162
+ this.default_wrappers = this.props.config.default_wrappers
163
+
164
+ this.character_convert_mapping = this.props.config.character_convert_mapping
165
+
166
+ this.save = new SaveBehavior({
167
+ getLocks: this.getLocks,
168
+ config: {
169
+ xhr: this.props.config.xhr,
170
+ data_storage: this.props.config.data_storage
171
+ },
172
+ editorState: this.state.editorState,
173
+ editorContent: this.emitSerializedOutput()
174
+ })
175
+ }
176
+
177
+ initializeState() {
178
+ if (this.props.content) {
179
+ //and @.props.content.trim() isnt ""
180
+ return this.decodeEditorContent(this.props.content)
181
+ } else {
182
+ return EditorState.createEmpty(this.decorator)
183
+ }
184
+ }
185
+
186
+ refreshSelection(newEditorState) {
187
+ const { editorState } = this.state
188
+ // Setting cursor position after inserting to content
189
+ const s = this.state.editorState.getSelection()
190
+ const c = editorState.getCurrentContent()
191
+ const focusOffset = s.getFocusOffset()
192
+ const anchorKey = s.getAnchorKey()
193
+
194
+ let selectionState = SelectionState.createEmpty(s.getAnchorKey())
195
+
196
+ // console.log anchorKey, focusOffset
197
+ selectionState = selectionState.merge({
198
+ anchorOffset: focusOffset,
199
+ focusKey: anchorKey,
200
+ focusOffset
201
+ })
202
+
203
+ let newState = EditorState.forceSelection(newEditorState, selectionState)
204
+
205
+ return this.onChange(newState)
206
+ }
207
+
208
+ forceRender(editorState) {
209
+ const selection = this.state.editorState.getSelection()
210
+ const content = editorState.getCurrentContent()
211
+ const newEditorState = EditorState.createWithContent(content, this.decorator)
212
+
213
+ return this.refreshSelection(newEditorState)
214
+ }
215
+
216
+ onChange(editorState) {
217
+ this.setPreContent()
218
+ this.setState({ editorState })
219
+
220
+ const currentBlock = getCurrentBlock(this.state.editorState)
221
+ const blockType = currentBlock.getType()
222
+
223
+ if (!editorState.getSelection().isCollapsed()) {
224
+
225
+ const tooltip = this.tooltipsWithProp('displayOnSelection')[0]
226
+ if (!this.tooltipHasSelectionElement(tooltip, blockType)) {
227
+ return
228
+ }
229
+
230
+ this.handleTooltipDisplayOn('displayOnSelection')
231
+ } else {
232
+ this.handleTooltipDisplayOn('displayOnSelection', false)
233
+ }
234
+
235
+ setTimeout(() => {
236
+ return this.relocateTooltips()
237
+ }, 0)
238
+
239
+ return this.dispatchChangesToSave()
240
+ }
241
+
242
+ dispatchChangesToSave() {
243
+ clearTimeout(this.saveTimeout)
244
+ return this.saveTimeout = setTimeout(() => {
245
+ return this.save.store(this.emitSerializedOutput())
246
+ }, 100)
247
+ }
248
+
249
+ setPreContent() {
250
+ const content = this.emitSerializedOutput()
251
+ return this.save.editorContent = content
252
+ }
253
+
254
+ focus() {}
255
+ //@props.refs.richEditor.focus()
256
+
257
+ getEditorState() {
258
+ return this.state.editorState
259
+ }
260
+
261
+ emitSerializedOutput() {
262
+ const raw = convertToRaw(this.state.editorState.getCurrentContent())
263
+
264
+ return raw
265
+ }
266
+
267
+ decodeEditorContent(raw_as_json) {
268
+ const new_content = convertFromRaw(raw_as_json)
269
+ let editorState
270
+
271
+ return editorState = EditorState.createWithContent(new_content, this.decorator)
272
+ }
273
+
274
+ //# title utils
275
+ getTextFromEditor() {
276
+ const c = this.state.editorState.getCurrentContent()
277
+ const out = c.getBlocksAsArray().map(o => {
278
+ return o.getText()
279
+ }).join("\n")
280
+
281
+ return out
282
+ }
283
+
284
+ emitHTML2() {
285
+ let html
286
+
287
+ return html = convertToHTML({
288
+ entityToHTML: (entity, originalText) => {
289
+ if (entity.type === 'LINK') {
290
+ return `<a href=\"${ entity.data.url }\">${ originalText }</a>`
291
+ } else {
292
+ return originalText
293
+ }
294
+ }
295
+
296
+ })(this.state.editorState.getCurrentContent())
297
+ }
298
+
299
+ getLocks() {
300
+ return this.state.locks
301
+ }
302
+
303
+ addLock() {
304
+ return this.setState({
305
+ locks: this.state.locks += 1 })
306
+ }
307
+
308
+ removeLock() {
309
+ return this.setState({
310
+ locks: this.state.locks -= 1 })
311
+ }
312
+
313
+ renderableBlocks() {
314
+ return this.widgets.filter(o => o.renderable).map(o => o.type)
315
+ }
316
+
317
+ defaultWrappers(blockType) {
318
+ return this.default_wrappers.filter(o => {
319
+ return o.block === blockType
320
+ }).map(o => o.className)
321
+ }
322
+
323
+ blockRenderer(block) {
324
+
325
+ switch (block.getType()) {
326
+
327
+ case "atomic":
328
+
329
+ const entity = block.getEntityAt(0)
330
+ const entity_type = Entity.get(entity).getType()
331
+
332
+ break
333
+ }
334
+
335
+ if (this.renderableBlocks().includes(block.getType())) {
336
+ return this.handleBlockRenderer(block)
337
+ }
338
+
339
+ return null
340
+ }
341
+
342
+ handleBlockRenderer(block) {
343
+ const dataBlock = this.getDataBlock(block)
344
+ if (!dataBlock) {
345
+ return null
346
+ }
347
+
348
+ const read_only = this.state.read_only ? false : null
349
+ const editable = read_only !== null ? read_only : dataBlock.editable
350
+ return {
351
+ component: eval(dataBlock.block),
352
+ editable,
353
+ props: {
354
+ data: block.getData(),
355
+ getEditorState: this.getEditorState,
356
+ setEditorState: this.onChange,
357
+ addLock: this.addLock,
358
+ removeLock: this.removeLock,
359
+ getLocks: this.getLocks,
360
+ config: dataBlock.options
361
+ }
362
+ }
363
+
364
+ return null
365
+ }
366
+
367
+ blockStyleFn(block) {
368
+ const currentBlock = getCurrentBlock(this.state.editorState)
369
+ const is_selected = currentBlock.getKey() === block.getKey() ? "is-selected" : ""
370
+
371
+ if (this.renderableBlocks().includes(block.getType())) {
372
+ return this.styleForBlock(block, currentBlock, is_selected)
373
+ }
374
+
375
+ const defaultBlockClass = this.defaultWrappers(block.getType())
376
+ if (defaultBlockClass.length > 0) {
377
+ return `graf ${ defaultBlockClass[0] } ${ is_selected }`
378
+ } else {
379
+ return `graf nana ${ is_selected }`
380
+ }
381
+ }
382
+
383
+ getDataBlock(block) {
384
+ return this.widgets.find(o => {
385
+ return o.type === block.getType()
386
+ })
387
+ }
388
+
389
+ styleForBlock(block, currentBlock, is_selected) {
390
+ const dataBlock = this.getDataBlock(block)
391
+
392
+ if (!dataBlock) {
393
+ return null
394
+ }
395
+
396
+ const selectedFn = dataBlock.selectedFn ? dataBlock.selectedFn(block) : null
397
+ const selected_class = is_selected ? dataBlock.selected_class : ''
398
+
399
+ return `${ dataBlock.wrapper_class } ${ selected_class } ${ selectedFn }`
400
+ }
401
+
402
+ handleTooltipDisplayOn(prop, display) {
403
+ if (display == null) {
404
+ display = true
405
+ }
406
+ return setTimeout(() => {
407
+ const items = this.tooltipsWithProp(prop)
408
+ return items.map(o => {
409
+ this.refs[o.ref].display(display)
410
+ return this.refs[o.ref].relocate()
411
+ })
412
+ }, 20)
413
+ }
414
+
415
+ handlePasteText(text, html) {
416
+
417
+ // https://github.com/facebook/draft-js/issues/685
418
+ /*
419
+ html = "<p>chao</p>
420
+ <avv>aaa</avv>
421
+ <p>oli</p>
422
+ <img src='x'/>"
423
+ */
424
+
425
+ // if not html then fallback to default handler
426
+
427
+ if (!html) {
428
+ return this.handleTXTPaste(text, html)
429
+ }
430
+ if (html) {
431
+ return this.handleHTMLPaste(text, html)
432
+ }
433
+ }
434
+
435
+ handleTXTPaste(text, html) {
436
+ const currentBlock = getCurrentBlock(this.state.editorState)
437
+
438
+ let { editorState } = this.state
439
+
440
+ switch (currentBlock.getType()) {
441
+ case "image":case "video":case "placeholder":
442
+ const newContent = Modifier.replaceText(editorState.getCurrentContent(), new SelectionState({
443
+ anchorKey: currentBlock.getKey(),
444
+ anchorOffset: 0,
445
+ focusKey: currentBlock.getKey(),
446
+ focusOffset: 2
447
+ }), text)
448
+
449
+ editorState = EditorState.push(editorState, newContent, 'replace-text')
450
+
451
+ this.onChange(editorState)
452
+
453
+ return true
454
+ default:
455
+ return false
456
+ }
457
+ }
458
+
459
+ handleHTMLPaste(text, html) {
460
+
461
+ const currentBlock = getCurrentBlock(this.state.editorState)
462
+
463
+ // TODO: make this configurable
464
+ switch (currentBlock.getType()) {
465
+ case "image":case "video":case "placeholder":
466
+ return this.handleTXTPaste(text, html)
467
+ break
468
+ }
469
+
470
+ const newContentState = customHTML2Content(html, this.extendedBlockRenderMap)
471
+
472
+ const selection = this.state.editorState.getSelection()
473
+ const endKey = selection.getEndKey()
474
+
475
+ const content = this.state.editorState.getCurrentContent()
476
+ const blocksBefore = content.blockMap.toSeq().takeUntil(v => v.key === endKey)
477
+ const blocksAfter = content.blockMap.toSeq().skipUntil(v => v.key === endKey).rest()
478
+
479
+ const newBlockKey = newContentState.blockMap.first().getKey()
480
+
481
+ const newBlockMap = blocksBefore.concat(newContentState.blockMap, blocksAfter).toOrderedMap()
482
+
483
+ const newContent = content.merge({
484
+ blockMap: newBlockMap,
485
+ selectionBefore: selection,
486
+ selectionAfter: selection.merge({
487
+ anchorKey: newBlockKey,
488
+ anchorOffset: 0,
489
+ focusKey: newBlockKey,
490
+ focusOffset: 0,
491
+ isBackward: false
492
+ })
493
+ })
494
+
495
+ const pushedContentState = EditorState.push(this.state.editorState, newContent, 'insert-fragment')
496
+
497
+ this.onChange(pushedContentState)
498
+
499
+ return true
500
+ }
501
+
502
+ handlePasteImage(files) {
503
+ //TODO: check file types
504
+ return files.map(file => {
505
+ let opts = {
506
+ url: URL.createObjectURL(file),
507
+ file
508
+ }
509
+
510
+ return this.onChange(addNewBlock(this.state.editorState, 'image', opts))
511
+ })
512
+ }
513
+
514
+ handleDroppedFiles(state, files) {
515
+ return files.map(file => {
516
+ let opts = {
517
+ url: URL.createObjectURL(file),
518
+ file
519
+ }
520
+
521
+ return this.onChange(addNewBlock(this.state.editorState, 'image', opts))
522
+ })
523
+ }
524
+
525
+ handleUpArrow(e) {
526
+ return setTimeout(() => {
527
+ return this.forceRender(this.state.editorState)
528
+ }, 10)
529
+ }
530
+
531
+ handleDownArrow(e) {
532
+ return setTimeout(() => {
533
+ return this.forceRender(this.state.editorState)
534
+ }, 10)
535
+ }
536
+
537
+ handleReturn(e) {
538
+ if (this.props.handleReturn) {
539
+ if (this.props.handleReturn()) {
540
+ return true
541
+ }
542
+ }
543
+
544
+ let { editorState } = this.state
545
+
546
+ if (!e.altKey && !e.metaKey && !e.ctrlKey) {
547
+ const currentBlock = getCurrentBlock(editorState)
548
+ const blockType = currentBlock.getType()
549
+ const selection = editorState.getSelection()
550
+
551
+ const config_block = this.getDataBlock(currentBlock)
552
+
553
+ if (currentBlock.getText().length === 0) {
554
+
555
+ if (config_block && config_block.handleEnterWithoutText) {
556
+ config_block.handleEnterWithText(this, currentBlock)
557
+ this.closePopOvers()
558
+ return true
559
+ }
560
+
561
+ //TODO turn this in configurable
562
+ switch (blockType) {
563
+ case "header-one":
564
+ this.onChange(resetBlockWithType(editorState, "unstyled"))
565
+ return true
566
+ break
567
+ default:
568
+ return false
569
+ }
570
+ }
571
+
572
+ if (currentBlock.getText().length > 0) {
573
+
574
+ if (blockType === "unstyled") {
575
+ // hack hackety hack
576
+ // https://github.com/facebook/draft-js/issues/304
577
+ const newContent = Modifier.splitBlock(
578
+ this.state.editorState.getCurrentContent(),
579
+ this.state.editorState.getSelection())
580
+
581
+ const newEditorState = EditorState.push(this.state.editorState,
582
+ newContent, 'insert-characters')
583
+ this.onChange(newEditorState)
584
+
585
+ setTimeout(() => {
586
+ //TODO: check is element is in viewport
587
+ const a = document.getElementsByClassName("is-selected")
588
+ const pos = a[0].getBoundingClientRect()
589
+ return window.scrollTo(0, pos.top + window.scrollY - 100)
590
+ }, 0)
591
+
592
+ return true
593
+ }
594
+
595
+ if (config_block && config_block.handleEnterWithText) {
596
+ config_block.handleEnterWithText(this, currentBlock)
597
+ this.closePopOvers()
598
+ return true
599
+ }
600
+
601
+ if (currentBlock.getLength() === selection.getStartOffset()) {
602
+ if (this.continuousBlocks.indexOf(blockType) < 0) {
603
+ this.onChange(addNewBlockAt(editorState, currentBlock.getKey()))
604
+ return true
605
+ }
606
+ }
607
+
608
+ return false
609
+ }
610
+
611
+ // selection.isCollapsed() and # should we check collapsed here?
612
+ if (currentBlock.getLength() === selection.getStartOffset()) {
613
+ //or (config_block && config_block.breakOnContinuous))
614
+ // it will match the unstyled for custom blocks
615
+ if (this.continuousBlocks.indexOf(blockType) < 0) {
616
+ this.onChange(addNewBlockAt(editorState, currentBlock.getKey()))
617
+ return true
618
+ }
619
+ return false
620
+ }
621
+
622
+ return false
623
+ }
624
+ }
625
+
626
+ //return false
627
+
628
+ // TODO: make this configurable
629
+ handleBeforeInput(chars) {
630
+ const currentBlock = getCurrentBlock(this.state.editorState)
631
+ const blockType = currentBlock.getType()
632
+ const selection = this.state.editorState.getSelection()
633
+
634
+ let { editorState } = this.state
635
+
636
+ // close popovers
637
+ if (currentBlock.getText().length !== 0) {
638
+ //@closeInlineButton()
639
+ this.closePopOvers()
640
+ }
641
+
642
+ // handle space on link
643
+ const endOffset = selection.getEndOffset()
644
+ const endKey = currentBlock.getEntityAt(endOffset - 1)
645
+ const endEntityType = endKey && Entity.get(endKey).getType()
646
+ const afterEndKey = currentBlock.getEntityAt(endOffset)
647
+ const afterEndEntityType = afterEndKey && Entity.get(afterEndKey).getType()
648
+
649
+ // will insert blank space when link found
650
+ if (chars === ' ' && endEntityType === 'LINK' && afterEndEntityType !== 'LINK') {
651
+ const newContentState = Modifier.insertText(editorState.getCurrentContent(), selection, ' ')
652
+ const newEditorState = EditorState.push(editorState, newContentState, 'insert-characters')
653
+ this.onChange(newEditorState)
654
+ return true
655
+ }
656
+
657
+ // block transform
658
+ if (blockType.indexOf('atomic') === 0) {
659
+ return false
660
+ }
661
+
662
+ const blockLength = currentBlock.getLength()
663
+ if (selection.getAnchorOffset() > 1 || blockLength > 1) {
664
+ return false
665
+ }
666
+
667
+ const blockTo = this.character_convert_mapping[currentBlock.getText() + chars]
668
+
669
+ console.log(`BLOCK TO SHOW: ${ blockTo }`)
670
+
671
+ if (!blockTo) {
672
+ return false
673
+ }
674
+
675
+ this.onChange(resetBlockWithType(editorState, blockTo))
676
+
677
+ return true
678
+ }
679
+
680
+ // TODO: make this configurable
681
+ handleKeyCommand(command) {
682
+ const { editorState } = this.state
683
+ let currentBlockType, newBlockType
684
+
685
+ if (this.props.handleKeyCommand && this.props.handleKeyCommand(command)) {
686
+ return true
687
+ }
688
+
689
+ if (command === 'add-new-block') {
690
+ this.onChange(addNewBlock(editorState, 'blockquote'))
691
+ return true
692
+ }
693
+
694
+ const block = getCurrentBlock(editorState)
695
+
696
+ if (command.indexOf('toggle_inline:') === 0) {
697
+ newBlockType = command.split(':')[1]
698
+ currentBlockType = block.getType()
699
+ this.onChange(RichUtils.toggleInlineStyle(editorState, newBlockType))
700
+ return true
701
+ }
702
+
703
+ if (command.indexOf('toggle_block:') === 0) {
704
+ newBlockType = command.split(':')[1]
705
+ currentBlockType = block.getType()
706
+
707
+ this.onChange(RichUtils.toggleBlockType(editorState, newBlockType))
708
+ return true
709
+ }
710
+
711
+ const newState = RichUtils.handleKeyCommand(this.state.editorState, command)
712
+ if (newState) {
713
+ this.onChange(newState)
714
+ return true
715
+ }
716
+
717
+ return false
718
+ }
719
+
720
+ findCommandKey(opt, command) {
721
+ // console.log "COMMAND find: #{opt} #{command}"
722
+ return this.key_commands[opt].find(o => o.key === command)
723
+ }
724
+
725
+ KeyBindingFn(e) {
726
+
727
+ //⌘ + B / Ctrl + B Bold
728
+ //⌘ + I / Ctrl + I Italic
729
+ //⌘ + K / Ctrl + K Turn into link
730
+ //⌘ + Alt + 1 / Ctrl + Alt + 1 Header
731
+ //⌘ + Alt + 2 / Ctrl + Alt + 2 Sub-Header
732
+ //⌘ + Alt + 5 / Ctrl + Alt + 5 Quote (Press once for a block quote, again for a pull quote and a third time to turn off quote)
733
+
734
+ let cmd
735
+ if (e.altKey) {
736
+ if (e.shiftKey) {
737
+ cmd = this.findCommandKey("alt-shift", e.which)
738
+ if (cmd) {
739
+ return cmd.cmd
740
+ }
741
+
742
+ return getDefaultKeyBinding(e)
743
+ }
744
+
745
+ if (e.ctrlKey || e.metaKey) {
746
+ cmd = this.findCommandKey("alt-cmd", e.which)
747
+ if (cmd) {
748
+ return cmd.cmd
749
+ }
750
+ return getDefaultKeyBinding(e)
751
+ }
752
+ } else if (e.ctrlKey || e.metaKey) {
753
+ cmd = this.findCommandKey("cmd", e.which)
754
+ if (cmd) {
755
+ return cmd.cmd
756
+ }
757
+ return getDefaultKeyBinding(e)
758
+ }
759
+
760
+ return getDefaultKeyBinding(e)
761
+ }
762
+
763
+ // will update block state todo: movo to utils
764
+ updateBlockData(block, options) {
765
+ const data = block.getData()
766
+ const newData = data.merge(options)
767
+ const newState = updateDataOfBlock(this.state.editorState, block, newData)
768
+ // this fixes enter from image caption
769
+ return this.forceRender(newState)
770
+ }
771
+
772
+ setDirection(direction_type) {
773
+ const contentState = this.state.editorState.getCurrentContent()
774
+ const selectionState = this.state.editorState.getSelection()
775
+ const block = contentState.getBlockForKey(selectionState.anchorKey)
776
+
777
+ return this.updateBlockData(block, { direction: direction_type })
778
+ }
779
+
780
+ //# read only utils
781
+ toggleEditable() {
782
+ this.closePopOvers()
783
+
784
+ return this.setState({ read_only: !this.state.read_only }, this.testEmitAndDecode)
785
+ }
786
+
787
+ closePopOvers() {
788
+ return this.tooltips.map(o => {
789
+ return this.refs[o.ref].hide()
790
+ })
791
+ }
792
+
793
+ relocateTooltips() {
794
+ return this.tooltips.map(o => {
795
+ return this.refs[o.ref].relocate()
796
+ })
797
+ }
798
+
799
+ tooltipsWithProp(prop) {
800
+ return this.tooltips.filter(o => {
801
+ return o[prop]
802
+ })
803
+ }
804
+
805
+ tooltipHasSelectionElement(tooltip, element) {
806
+ return tooltip.selectionElements.includes(element)
807
+ }
808
+
809
+ //################################
810
+ // TODO: this methods belongs to popovers/link
811
+ //################################
812
+
813
+ handleShowPopLinkOver(e) {
814
+ return this.showPopLinkOver()
815
+ }
816
+
817
+ handleHidePopLinkOver(e) {
818
+ return this.hidePopLinkOver()
819
+ }
820
+
821
+ showPopLinkOver(el) {
822
+ // handles popover display
823
+ // using anchor or from popover
824
+
825
+ const parent_el = ReactDOM.findDOMNode(this)
826
+
827
+ // set url first in order to calculate popover width
828
+ let coords
829
+ this.refs.anchor_popover.setState({ url: el ? el.href : this.refs.anchor_popover.state.url })
830
+
831
+ if (el) {
832
+ coords = this.refs.anchor_popover.relocate(el)
833
+ }
834
+
835
+ if (coords) {
836
+ this.refs.anchor_popover.setPosition(coords)
837
+ }
838
+
839
+ this.refs.anchor_popover.setState({ show: true })
840
+
841
+ this.isHover = true
842
+ return this.cancelHide()
843
+ }
844
+
845
+ hidePopLinkOver() {
846
+ return this.hideTimeout = setTimeout(() => {
847
+ return this.refs.anchor_popover.hide()
848
+ }, 300)
849
+ }
850
+
851
+ cancelHide() {
852
+ // console.log "Cancel Hide"
853
+ return clearTimeout(this.hideTimeout)
854
+ }
855
+
856
+ //##############################
857
+
858
+ render() {
859
+ return (
860
+ <div id="content" suppressContentEditableWarning={ true }>
861
+ <article className="postArticle">
862
+ <div className="postContent">
863
+ <div className="notesSource">
864
+ <div id="editor" className="postField postField--body">
865
+ <section className="section--first section--last">
866
+ <div className="section-divider layoutSingleColumn">
867
+ <hr className="section-divider" />
868
+ </div>
869
+ <div className="section-content">
870
+ <div ref="richEditor" className="section-inner layoutSingleColumn"
871
+ onClick={ this.focus }>
872
+ <Editor
873
+ blockRendererFn={ this.blockRenderer }
874
+ editorState={ this.state.editorState }
875
+ onChange={ this.onChange }
876
+ onUpArrow={ this.handleUpArrow }
877
+ onDownArrow={ this.handleDownArrow }
878
+ handleReturn={ this.handleReturn }
879
+ blockRenderMap={ this.state.blockRenderMap }
880
+ blockStyleFn={ this.blockStyleFn }
881
+ handlePastedText={ this.handlePasteText }
882
+ handlePastedFiles={ this.handlePasteImage }
883
+ handleDroppedFiles={ this.handleDroppedFiles }
884
+ handleKeyCommand={ this.handleKeyCommand }
885
+ keyBindingFn={ this.KeyBindingFn }
886
+ handleBeforeInput={ this.handleBeforeInput }
887
+ readOnly={ this.state.read_only }
888
+ placeholder={ this.props.config.body_placeholder }
889
+ ref="editor"
890
+ />
891
+ </div>
892
+ </div>
893
+ </section>
894
+ </div>
895
+ </div>
896
+ </div>
897
+ </article>
898
+ {
899
+ this.tooltips.map( (o, i) => {
900
+ return (
901
+ <o.component
902
+ ref={ o.ref }
903
+ key={ i }
904
+ editor={ this }
905
+ editorState={ this.state.editorState }
906
+ onChange={ this.onChange }
907
+ configTooltip={ o }
908
+ widget_options={ o.widget_options }
909
+ showPopLinkOver={ this.showPopLinkOver }
910
+ hidePopLinkOver={ this.hidePopLinkOver }
911
+ handleOnMouseOver={ this.handleShowPopLinkOver }
912
+ handleOnMouseOut={ this.handleHidePopLinkOver }
913
+ />
914
+ )
915
+ })
916
+ }
917
+ {
918
+ this.state.debug
919
+ ? <Debug locks={ this.state.locks } editor={ this } />
920
+ : undefined
921
+ }
922
+ </div>
923
+
924
+ )
925
+ }
926
+ }
927
+
928
+ module.exports = DanteEditor