dante2-editor 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/Gemfile +25 -0
  4. data/Gemfile.lock +42 -0
  5. data/README.md +107 -0
  6. data/app/assets/index.html +55 -0
  7. data/app/assets/license.html +52 -0
  8. data/app/assets/options.html +57 -0
  9. data/app/assets/store.json +1 -0
  10. data/app/components/App.cjsx +1083 -0
  11. data/app/components/blocks/embed.cjsx +109 -0
  12. data/app/components/blocks/image.cjsx +316 -0
  13. data/app/components/blocks/placeholder.cjsx +60 -0
  14. data/app/components/blocks/video.cjsx +75 -0
  15. data/app/components/debug.cjsx +96 -0
  16. data/app/components/decorators/link.cjsx +61 -0
  17. data/app/components/popovers/addButton.cjsx +247 -0
  18. data/app/components/popovers/image.cjsx +160 -0
  19. data/app/components/popovers/link.cjsx +87 -0
  20. data/app/components/popovers/toolTip.cjsx +326 -0
  21. data/app/data/poc.js +15 -0
  22. data/app/demo.js +7 -0
  23. data/app/images/dante/media-loading-placeholder.png +0 -0
  24. data/app/images/site/dante-demo.png +0 -0
  25. data/app/images/site/dante-editor-logo.png +0 -0
  26. data/app/images/site/github-logo.png +0 -0
  27. data/app/initialize.js +4 -0
  28. data/app/model/index.js +194 -0
  29. data/app/styles/dante.scss +22 -0
  30. data/app/styles/dante/_animations.scss +54 -0
  31. data/app/styles/dante/_caption.scss +61 -0
  32. data/app/styles/dante/_debug.scss +124 -0
  33. data/app/styles/dante/_fonts.scss +17 -0
  34. data/app/styles/dante/_graf.scss +242 -0
  35. data/app/styles/dante/_icons.scss +62 -0
  36. data/app/styles/dante/_media.scss +39 -0
  37. data/app/styles/dante/_menu.scss +201 -0
  38. data/app/styles/dante/_needsorder.scss +209 -0
  39. data/app/styles/dante/_popover.scss +212 -0
  40. data/app/styles/dante/_post.scss +67 -0
  41. data/app/styles/dante/_scaffold.scss +24 -0
  42. data/app/styles/dante/_tooltip.scss +130 -0
  43. data/app/styles/dante/_utilities.scss +59 -0
  44. data/app/styles/dante/_variables.scss +96 -0
  45. data/app/styles/dante/blame.scss +246 -0
  46. data/app/styles/draft.css +297 -0
  47. data/app/styles/fonts/dante/dante.eot +0 -0
  48. data/app/styles/fonts/dante/dante.svg +18 -0
  49. data/app/styles/fonts/dante/dante.ttf +0 -0
  50. data/app/styles/fonts/dante/dante.woff +0 -0
  51. data/app/styles/fonts/dante/fontello.eot +0 -0
  52. data/app/styles/fonts/dante/fontello.svg +36 -0
  53. data/app/styles/fonts/dante/fontello.ttf +0 -0
  54. data/app/styles/fonts/dante/fontello.woff +0 -0
  55. data/app/styles/layout/layout.scss +64 -0
  56. data/app/styles/layout/normalize.css +375 -0
  57. data/app/styles/layout/scaffold.scss +8 -0
  58. data/app/styles/layout/tooltips.scss +216 -0
  59. data/app/utils/find_entities.coffee +20 -0
  60. data/app/utils/html2content.coffee +120 -0
  61. data/app/utils/logger.coffee +0 -0
  62. data/app/utils/save_content.coffee +63 -0
  63. data/app/utils/selection.js +53 -0
  64. data/config.ru +64 -0
  65. data/dante2.gemspec +19 -0
  66. data/docs/app.css +2 -0
  67. data/docs/app.css.map +1 -0
  68. data/docs/app.js +3 -0
  69. data/docs/app.js.map +1 -0
  70. data/docs/dante-vendors.js +28 -0
  71. data/docs/dante-vendors.js.map +1 -0
  72. data/docs/dante.css +2 -0
  73. data/docs/dante.css.map +1 -0
  74. data/docs/dante.js +4 -0
  75. data/docs/dante.js.map +1 -0
  76. data/docs/doc.html +57 -0
  77. data/docs/fonts/dante.eot +0 -0
  78. data/docs/fonts/dante.svg +18 -0
  79. data/docs/fonts/dante.ttf +0 -0
  80. data/docs/fonts/dante.woff +0 -0
  81. data/docs/fonts/fontello.eot +0 -0
  82. data/docs/fonts/fontello.svg +36 -0
  83. data/docs/fonts/fontello.ttf +0 -0
  84. data/docs/fonts/fontello.woff +0 -0
  85. data/docs/images/dante-editor-logo.png +0 -0
  86. data/docs/images/github-logo.png +0 -0
  87. data/docs/index.html +55 -0
  88. data/docs/license.html +52 -0
  89. data/lib/dante2-editor.rb +5 -0
  90. data/lib/dante2-editor/rails.rb +16 -0
  91. data/lib/dante2-editor/version.rb +5 -0
  92. data/package.json +61 -0
  93. data/rakefile +1 -0
  94. data/webpack.config.js +148 -0
  95. data/yarn.lock +4704 -0
  96. metadata +139 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b78a4eed0a1ef6eeae30783c70c7292b850998e2
4
+ data.tar.gz: 5ec8a4832a950e7450e99f8bd38b108b8721153b
5
+ SHA512:
6
+ metadata.gz: f608f000bc57b9e234cfd10f2f2de4e021f8c49267611bf841c636fdc9e63a9ff2a5e78599747cb4eac18dde96efed4addf0a00fc87b439e4d40186b2d63a817
7
+ data.tar.gz: fa07fc3b9af66474ee8f8fa069cf6c5071b08f06fbed561538bceef8a6106ad3b788c08e18b2cd7187eea6ad7cbc85cfbb4b2944ca5a481d30bb438a2617e6fb
data/.gitignore ADDED
@@ -0,0 +1,33 @@
1
+ # Numerous always-ignore extensions
2
+ *.diff
3
+ *.err
4
+ *.orig
5
+ *.log
6
+ *.rej
7
+ *.swo
8
+ *.swp
9
+ *.vi
10
+ *~
11
+ *.sass-cache
12
+
13
+ # OS or Editor folders
14
+ .DS_Store
15
+ .cache
16
+ .project
17
+ .settings
18
+ .tmproj
19
+ nbproject
20
+ Thumbs.db
21
+
22
+ # NPM packages folder.
23
+ node_modules/
24
+
25
+ # Brunch folder for temporary files.
26
+ tmp/
27
+ /pkg/
28
+
29
+ # Brunch output folder.
30
+ public/
31
+
32
+ # Bower stuff.
33
+ bower_components/
data/Gemfile ADDED
@@ -0,0 +1,25 @@
1
+ # If you have OpenSSL installed, we recommend updating
2
+ # the following line to use "https"
3
+ source 'http://rubygems.org'
4
+
5
+ #gem "therubyracer"
6
+ # gem "middleman", "~>3.1.4"
7
+
8
+ # Live-reloading plugin
9
+ # gem "middleman-livereload", "~> 3.1.0"
10
+ # gem 'middleman-gh-pages'
11
+ # For faster file watcher updates on Windows:
12
+ # gem "wdm", "~> 0.1.0", :platforms => [:mswin, :mingw]
13
+ # gem "github-markup"
14
+ # gem "redcarpet"
15
+ gem "sinatra"
16
+ gem 'sinatra-contrib'
17
+ gem "pry"
18
+ gem 'rack-cors', :require => 'rack/cors'
19
+
20
+ #gemspec
21
+
22
+ # Cross-templating language block fix for Ruby 1.8
23
+ #platforms :mri_18 do
24
+ # gem "ruby18_source_location"
25
+ #end
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ backports (3.6.8)
5
+ coderay (1.1.1)
6
+ method_source (0.8.2)
7
+ multi_json (1.12.1)
8
+ pry (0.10.4)
9
+ coderay (~> 1.1.0)
10
+ method_source (~> 0.8.1)
11
+ slop (~> 3.4)
12
+ rack (1.6.4)
13
+ rack-cors (0.4.0)
14
+ rack-protection (1.5.3)
15
+ rack
16
+ rack-test (0.6.3)
17
+ rack (>= 1.0)
18
+ sinatra (1.4.7)
19
+ rack (~> 1.5)
20
+ rack-protection (~> 1.4)
21
+ tilt (>= 1.3, < 3)
22
+ sinatra-contrib (1.4.7)
23
+ backports (>= 2.0)
24
+ multi_json
25
+ rack-protection
26
+ rack-test
27
+ sinatra (~> 1.4.0)
28
+ tilt (>= 1.3, < 3)
29
+ slop (3.6.0)
30
+ tilt (2.0.5)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ pry
37
+ rack-cors
38
+ sinatra
39
+ sinatra-contrib
40
+
41
+ BUNDLED WITH
42
+ 1.10.6
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # Dante II - The rematch
2
+
3
+ ## just another medium clone built on top of DraftJs
4
+
5
+ > Dante II is a complete rewrite of [DanteEditor](https://michelson.github.io/Dante). This version is built on top of Facebook's Draft-Js and reaches all Dante's features with a shiny ultra mega super uber maintainable architecture.
6
+
7
+ See the demo at: [https://michelson.github.io/dante2/](https://michelson.github.io/dante2/)
8
+
9
+ ## Why rewrite a new version of Dante?
10
+
11
+ The previous version of Dante relies a lot on DOM manipulation which causes a mix of presentation and logic. Even with their modular plugin system this condition suppose an sphagetti model to work with on every feature. The biggest problem with this approach is: if you want to make a change that affects the presentation of your users content, let's say you might want to change the default markup for paragraphs, you'll probably end updating all your content in your database, because dealing with "DOM only" suppose that you are going to save html into database, right ?
12
+
13
+
14
+ ## A redesign was needed!
15
+
16
+ Draft-Js handles selection, ranges and markup blocks as a data layer contained in a structure known as editorState, with a clear separation on how rendering, styling and interaction works. So you save content data, not html. That's awesome because you can change the appearance of articles (styles & markup) without database changes.
17
+
18
+ In Draft every change provided from user input is stacked in this editorState building an history of changes, out of the box. This means that pasting, undo/redo and replace/insert blocks at certain selection points are basically calls to the DraftJs API that updates the editorState without DOM manipulation. Also, all the custom blocks are composed as React components!. So, this version have some dependencies which are included in source. DraftJs, React, Immutable. no Jquery.
19
+
20
+ **New Features:**
21
+ + Improved undo/redo.
22
+ + Save Content as a data JSON structure.
23
+ + Load Content as a data JSON structure.
24
+ + Handle image blocks on Copy/Paste and Drop.
25
+ + Global storage lock to handle file uploads.
26
+
27
+ **Features**:
28
+
29
+ + Image upload for paste html.
30
+ + Image upload for legacy images on existent texts.
31
+ + The medium (+) Tooltip to embed or upload media.
32
+ + Tab navigation.
33
+ + Pluggins are React components
34
+
35
+ **Embeds**:
36
+
37
+ + Image Uploader with preview and caption option with a lock system.
38
+ + Embed data for pasted link through OEmbed services.
39
+ + Embed media information for pasted links through OEmbed services.
40
+ + Add or remove tooltip buttons with ease with plugin system.
41
+ + Add custom blocks many with custom options
42
+
43
+
44
+ ## Usage
45
+
46
+ The interface to initialize is almost the Dante as the previous version.
47
+
48
+
49
+ ```javascript
50
+ new Dante({
51
+ upload_url: "http://localhost:9292/uploads/new",
52
+ store_url: "http://localhost:3333/store.json",
53
+ el: "app"
54
+ })
55
+
56
+ ```
57
+
58
+ ### Options:
59
+
60
+ Many configuration options and plugin usage in the documentation page:
61
+
62
+ See [https://michelson.github.io/dante2/doc.html](https://michelson.github.io/dante2/doc.html)
63
+
64
+
65
+ ### Installation for development
66
+
67
+ **node + webpack**
68
+
69
+ + `npm install or yarn install`
70
+
71
+
72
+ #### Development:
73
+
74
+ + `npm start` or `yarn start`
75
+
76
+ Then open http://localhost:8080
77
+
78
+
79
+ #### Build
80
+
81
+ + `npm build` or `yarn build`
82
+
83
+ #### Upload test server (ruby, optional)
84
+
85
+ For development purposes we have a server, written in ruby, to handle file uploading
86
+
87
+ + `bundle install`
88
+
89
+ + `bundle exec rackup`
90
+
91
+ and open http://localhost:9292
92
+
93
+
94
+ ### Ruby:
95
+ For rubists you can install this library as a gem file.
96
+
97
+ Just add the gem as dante2-editor
98
+
99
+
100
+ ### Open source license
101
+
102
+ If you are creating an open source application under a license compatible with the [GNU GPL license v3](https://www.gnu.org/licenses/gpl-3.0.html), you may use Dante2 under the terms of the GPLv3.[Read more about Dante2's license](https://michelson.github.io/dante2/license.html).
103
+
104
+
105
+ ### Alternatives
106
+
107
+ If you are looking for alternatives you can always use the [MIT licensed Dante (1)](https://michelson.github.io/Dante) or choose along others [medium clones](http://howtox.com/medium-editor-clones-in-js) or check out [many many awesome draft-js based editors](https://github.com/nikgraf/awesome-draft-js)
@@ -0,0 +1,55 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
6
+ <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
7
+ <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
8
+ <title>Dante2</title>
9
+ </head>
10
+ <body>
11
+
12
+ <div id="header">
13
+ <div class="logo">
14
+ <div class="menu-buttons">
15
+ <a class="menu-button" target="_blank" href="https://michelson.github.io/dante2/doc.html">
16
+ Documentation
17
+ </a>
18
+
19
+ <a class="menu-button" target="_blank" href="https://michelson.github.io/dante2/license.html">
20
+ License
21
+ </a>
22
+ </div>
23
+
24
+
25
+ <img src=<%= require('images/site/dante-editor-logo.png') %> alt="dante editor" height="21">
26
+ <span>Dante Editor - 0.2.0 </span>
27
+
28
+ </div>
29
+
30
+ <a class="github tooltip-left" data-tooltip="Fork me on github" target="_blank" href="https://github.com/michelson/dante2">
31
+ <img src=<%= require('images/site/github-logo.png') %> alt="Fork me on github" height="28">
32
+ </a>
33
+ </div>
34
+
35
+ <div class="danteArticle"></div>
36
+ <div id="app"></div>
37
+
38
+ <script type="text/javascript">
39
+ document.addEventListener('DOMContentLoaded', function(){
40
+ editor = new Dante(
41
+ {
42
+ api_key: "86c28a410a104c8bb58848733c82f840",
43
+ el: "app",
44
+ content: window.PocData,
45
+ debug: true,
46
+ data_storage: {
47
+ url: "http://localhost:3333/store.json"
48
+ }
49
+ }
50
+ )
51
+ editor.render()
52
+ })
53
+ </script>
54
+ </body>
55
+ </html>
@@ -0,0 +1,52 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
6
+ <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
7
+ <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
8
+ <title>Dante2</title>
9
+ </head>
10
+ <body>
11
+
12
+
13
+ <div id="header">
14
+ <div class="logo">
15
+ <div class="menu-buttons">
16
+ <a class="menu-button" target="_blank" href="https://michelson.github.io/dante2/doc.html">
17
+ Documentation
18
+ </a>
19
+
20
+ <a class="menu-button" target="_blank" href="https://michelson.github.io/dante2/license.html">
21
+ License
22
+ </a>
23
+ </div>
24
+
25
+ <img src='https://michelson.github.io/dante2/images/dante-editor-logo.png' alt="dante editor" height="21">
26
+ <span>Dante Editor - 0.2.0 </span>
27
+
28
+ </div>
29
+
30
+ <a class="github tooltip-left" data-tooltip="Fork me on github" target="_blank" href="https://github.com/michelson/dante2">
31
+ <img src='https://michelson.github.io/dante2/images/github-logo.png' alt="Fork me on github" height="28">
32
+ </a>
33
+ </div>
34
+
35
+ <div class="danteArticle"></div>
36
+ <div id="app"></div>
37
+
38
+ <script type="text/javascript">
39
+ document.addEventListener('DOMContentLoaded', function(){
40
+ editor = new Dante(
41
+ {
42
+ el: "app",
43
+ content: window.PocDataLicense,
44
+ debug: false,
45
+ read_only: true
46
+ }
47
+ )
48
+ editor.render()
49
+ })
50
+ </script>
51
+ </body>
52
+ </html>
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
6
+ <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
7
+ <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
8
+ <title>Dante2</title>
9
+ <link rel="stylesheet" type="text/css" href="/application.css">
10
+ <script src="/vendor.js"></script>
11
+ <script src="/application.js"></script>
12
+ <script>require('initialize');</script>
13
+ </head>
14
+ <body>
15
+
16
+ <div id="header">
17
+ <div class="logo">
18
+ <div class="menu-buttons">
19
+ <a class="menu-button" target="_blank" href="https://michelson.github.io/dante2/doc.html">
20
+ Documentation
21
+ </a>
22
+
23
+ <a class="menu-button" target="_blank" href="https://michelson.github.io/dante2/license.html">
24
+ License
25
+ </a>
26
+ </div>
27
+
28
+
29
+ <img src='https://michelson.github.io/dante2/images/dante-editor-logo.png' alt="dante editor" height="21">
30
+ <span>Dante Editor - 0.2.0 </span>
31
+
32
+ </div>
33
+
34
+ <a class="github tooltip-left" data-tooltip="Fork me on github" target="_blank" href="https://github.com/michelson/dante2">
35
+ <img src='https://michelson.github.io/dante2/images/github-logo.png' alt="Fork me on github" height="28">
36
+ </a>
37
+ </div>
38
+
39
+ <div id="app"></div>
40
+
41
+ <script type="text/javascript">
42
+ document.addEventListener('DOMContentLoaded', function(){
43
+ editor = new Dante(
44
+ {
45
+ upload_url: "http://localhost:9292/uploads/new",
46
+ el: "app",
47
+ content: window.Doc,
48
+ read_only: true
49
+ }
50
+ )
51
+ editor.render()
52
+ window.dante_editor = editor
53
+ })
54
+ </script>
55
+
56
+ </body>
57
+ </html>
@@ -0,0 +1 @@
1
+ {"status": "ok"}
@@ -0,0 +1,1083 @@
1
+
2
+ React = require('react')
3
+ ReactDOM = require('react-dom')
4
+ Immutable = require('immutable')
5
+ { Map, fromJS } = require('immutable')
6
+ {
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
+ } = require('draft-js')
25
+
26
+ DraftPasteProcessor = require('draft-js/lib/DraftPasteProcessor')
27
+
28
+ # {stateToHTML} = require('draft-js-export-html')
29
+ {
30
+ convertToHTML
31
+ convertFromHTML
32
+ } = require('draft-convert')
33
+
34
+ isSoftNewlineEvent = require('draft-js/lib/isSoftNewlineEvent')
35
+
36
+ {
37
+ addNewBlock
38
+ resetBlockWithType
39
+ updateDataOfBlock
40
+ updateTextOfBlock
41
+ getCurrentBlock
42
+ addNewBlockAt
43
+ updateDataOfBlock
44
+ } = require('../model/index.js')
45
+
46
+ DanteImagePopover = require('./popovers/image')
47
+ DanteAnchorPopover = require('./popovers/link')
48
+
49
+ {
50
+ getSelectionRect
51
+ getSelection
52
+ } = require("../utils/selection.js")
53
+ DanteInlineTooltip = require('./popovers/addButton.cjsx')
54
+ DanteTooltip = require('./popovers/toolTip.cjsx')
55
+ Link = require('./decorators/link.cjsx')
56
+
57
+ Debug = require('./debug.cjsx')
58
+ findEntities = require('../utils/find_entities.coffee')
59
+ ImageBlock = require('./blocks/image.cjsx')
60
+ EmbedBlock = require('./blocks/embed.cjsx')
61
+ VideoBlock = require('./blocks/video.cjsx')
62
+ PlaceholderBlock = require('./blocks/placeholder.cjsx')
63
+
64
+ SaveBehavior = require('../utils/save_content.coffee')
65
+ customHTML2Content = require('../utils/html2content.coffee')
66
+
67
+ class Dante
68
+ constructor: (options={})->
69
+ console.log "init editor Dante!"
70
+
71
+ # deep merge on config
72
+ config = Map(fromJS(@defaultOptions(options)))
73
+
74
+ @options = config.mergeDeep(options).toJS()
75
+ console.log @options
76
+
77
+ defaultOptions: (options={})->
78
+ # default options
79
+ defaultOptions = {}
80
+ defaultOptions.el = 'app'
81
+ defaultOptions.content = ""
82
+ defaultOptions.read_only = false
83
+ defaultOptions.spellcheck = false
84
+ defaultOptions.title_placeholder = "Title"
85
+ defaultOptions.body_placeholder = "Write your story"
86
+ # @defaultOptions.api_key = "86c28a410a104c8bb58848733c82f840"
87
+
88
+ defaultOptions.widgets = [
89
+ {
90
+ title: 'add an image'
91
+ icon: 'image'
92
+ type: 'image'
93
+ block: 'ImageBlock'
94
+ editable: true
95
+ renderable: true
96
+ breakOnContinuous: true
97
+ wrapper_class: "graf graf--figure"
98
+ selected_class: "is-selected is-mediaFocused"
99
+ selectedFn: (block)=>
100
+ direction = block.getData().toJS().direction
101
+ switch direction
102
+ when "left" then "graf--layoutOutsetLeft"
103
+ when "center" then ""
104
+ when "wide" then "sectionLayout--fullWidth"
105
+ when "fill" then "graf--layoutFillWidth"
106
+ else
107
+ handleEnterWithoutText: (ctx, block)->
108
+ editorState = ctx.state.editorState
109
+ ctx.onChange(addNewBlockAt(editorState, block.getKey()))
110
+ handleEnterWithText: (ctx, block)->
111
+ editorState = ctx.state.editorState
112
+ ctx.onChange(addNewBlockAt(editorState, block.getKey()))
113
+ widget_options:
114
+ displayOnInlineTooltip: true
115
+ insertion: "upload"
116
+ insert_block: "image"
117
+ options:
118
+ upload_url: options.upload_url
119
+ upload_callback: options.image_upload_callback
120
+ image_delete_callback: options.image_delete_callback
121
+ image_caption_placeholder: options.image_caption_placeholder
122
+ }
123
+ {
124
+ icon: 'embed'
125
+ title: 'insert embed'
126
+ type: 'embed'
127
+ block: 'EmbedBlock'
128
+ editable: true
129
+ renderable: true
130
+ breakOnContinuous: true
131
+ wrapper_class: "graf graf--mixtapeEmbed"
132
+ selected_class: "is-selected is-mediaFocused"
133
+ widget_options:
134
+ displayOnInlineTooltip: true
135
+ insertion: "placeholder"
136
+ insert_block: "embed"
137
+ options:
138
+ endpoint: "//api.embed.ly/1/extract?key=#{options.api_key}&url="
139
+ placeholder: 'Paste a link to embed content from another site (e.g. Twitter) and press Enter'
140
+ handleEnterWithoutText: (ctx, block)->
141
+ editorState = ctx.state.editorState
142
+ ctx.onChange(addNewBlockAt(editorState, block.getKey()))
143
+ handleEnterWithText: (ctx, block)->
144
+ editorState = ctx.state.editorState
145
+ ctx.onChange(addNewBlockAt(editorState, block.getKey()))
146
+ }
147
+ {
148
+ icon: 'video'
149
+ title: 'insert video'
150
+ editable: true
151
+ type: 'video'
152
+ block: 'VideoBlock'
153
+ renderable: true
154
+ breakOnContinuous: true
155
+ wrapper_class: "graf--figure graf--iframe"
156
+ selected_class:" is-selected is-mediaFocused"
157
+ widget_options:
158
+ displayOnInlineTooltip: true
159
+ insertion: "placeholder"
160
+ insert_block: "video"
161
+ options:
162
+ endpoint: "//api.embed.ly/1/oembed?key=#{options.api_key}&url="
163
+ placeholder: 'Paste a YouTube, Vine, Vimeo, or other video link, and press Enter'
164
+ caption: 'Type caption for embed (optional)'
165
+
166
+ handleEnterWithoutText: (ctx, block)->
167
+ editorState = ctx.state.editorState
168
+ ctx.onChange(addNewBlockAt(editorState, block.getKey()))
169
+
170
+ handleEnterWithText: (ctx, block)->
171
+ editorState = ctx.state.editorState
172
+ ctx.onChange(addNewBlockAt(editorState, block.getKey()))
173
+ }
174
+ {
175
+ renderable: true
176
+ editable: true
177
+ block: 'PlaceholderBlock'
178
+ type: 'placeholder'
179
+ wrapper_class: "is-embedable"
180
+ selected_class:" is-selected is-mediaFocused"
181
+ widget_options:
182
+ displayOnInlineTooltip: false
183
+ handleEnterWithText: (ctx, block)->
184
+ editorState = ctx.state.editorState
185
+ data =
186
+ provisory_text: block.getText()
187
+ endpoint: block.getData().get('endpoint')
188
+ type: block.getData().get('type')
189
+
190
+ ctx.onChange(resetBlockWithType(
191
+ editorState,
192
+ data.type,
193
+ data)
194
+ )
195
+ }
196
+ ]
197
+
198
+ defaultOptions.tooltips = [
199
+ {
200
+ ref: 'insert_tooltip'
201
+ component: DanteTooltip
202
+ displayOnSelection: true
203
+ selectionElements: [
204
+ "unstyled"
205
+ "blockquote"
206
+ "ordered-list"
207
+ "unordered-list"
208
+ "unordered-list-item"
209
+ "ordered-list-item"
210
+ "code-block"
211
+ 'header-one'
212
+ 'header-two'
213
+ 'header-three'
214
+ 'header-four'
215
+ ]
216
+ widget_options:
217
+ block_types: [
218
+ # {label: 'p', style: 'unstyled'},
219
+ {label: 'h2', style: 'header-one', type: "block"},
220
+ {label: 'h3', style: 'header-two', type: "block"},
221
+ {label: 'h4', style: 'header-three', type: "block"},
222
+ {label: 'blockquote', style: 'blockquote', type: "block"},
223
+ {label: 'insertunorderedlist', style: 'unordered-list-item', type: "block"},
224
+ {label: 'insertorderedlist', style: 'ordered-list-item', type: "block"},
225
+ {label: 'code', style: 'code-block', type: "block"}
226
+ {label: 'bold', style: 'BOLD', type: "inline"},
227
+ {label: 'italic', style: 'ITALIC', type: "inline"},
228
+ # {label: 'underline', style: 'UNDERLINE', type: "inline"},
229
+ # {label: 'monospace', style: 'CODE', type: "inline"},
230
+ # {label: 'strikethrough', style: 'STRIKETHROUGH', type: "inline"}
231
+ ]
232
+ }
233
+ {
234
+ ref: 'add_tooltip'
235
+ component: DanteInlineTooltip
236
+ }
237
+ {
238
+ ref: 'anchor_popover'
239
+ component: DanteAnchorPopover
240
+ }
241
+ {
242
+ ref: 'image_popover'
243
+ component: DanteImagePopover
244
+ }
245
+ ]
246
+
247
+ defaultOptions.xhr = {
248
+ before_handler: null
249
+ success_handler: null
250
+ error_handler: null
251
+ }
252
+
253
+ defaultOptions.data_storage = {
254
+ url: null
255
+ method: "POST"
256
+ success_handler: null
257
+ failure_handler: null
258
+ interval: 1500
259
+ }
260
+
261
+ defaultOptions.default_wrappers = [
262
+ {className: 'graf--p', block: 'unstyled'},
263
+ {className: 'graf--h2', block: 'header-one'},
264
+ {className: 'graf--h3', block: 'header-two'},
265
+ {className: 'graf--h4', block: 'header-three'},
266
+ {className: 'graf--blockquote', block: 'blockquote'},
267
+ {className: 'graf--insertunorderedlist', block: 'unordered-list-item'},
268
+ {className: 'graf--insertorderedlist', block: 'ordered-list-item'},
269
+ {className: 'graf--code', block: 'code-block'}
270
+ {className: 'graf--bold', block: 'BOLD'},
271
+ {className: 'graf--italic', block: 'ITALIC'},
272
+ ]
273
+
274
+ defaultOptions.continuousBlocks = [
275
+ "unstyled"
276
+ "blockquote"
277
+ "ordered-list"
278
+ "unordered-list"
279
+ "unordered-list-item"
280
+ "ordered-list-item"
281
+ "code-block"
282
+ ]
283
+
284
+ defaultOptions.key_commands = {
285
+ "alt-shift": [
286
+ { key: 65, cmd: 'add-new-block'}
287
+ ],
288
+ "alt-cmd": [
289
+ { key: 49, cmd: 'toggle_block:header-one'}
290
+ { key: 50, cmd: 'toggle_block:header-two'}
291
+ { key: 53, cmd: 'toggle_block:blockquote'}
292
+ ],
293
+ "cmd": [
294
+ { key: 66, cmd: 'toggle_inline:BOLD'}
295
+ { key: 73, cmd: 'toggle_inline:ITALIC'}
296
+ { key: 75, cmd: 'insert:link'}
297
+ ]
298
+ }
299
+
300
+ defaultOptions.character_convert_mapping = {
301
+ '> ': "blockquote",
302
+ '*.': "unordered-list-item",
303
+ '* ': "unordered-list-item",
304
+ '- ': "unordered-list-item",
305
+ '1.': "ordered-list-item",
306
+ '# ': 'header-one',
307
+ '##': 'header-two',
308
+ '==': "unstyled",
309
+ '` ': "code-block",
310
+ }
311
+
312
+ defaultOptions
313
+
314
+ getContent: ->
315
+ #console.log @options.content
316
+ #console.log "IS POC DATA?", @options.content is PocData
317
+ #return PocData if @options.poc
318
+ #console.log @options.content , PocData
319
+ #PocData
320
+ @options.content
321
+
322
+ render: ->
323
+ ReactDOM.render(<DanteEditor content={@getContent()}
324
+ config={@options}/>, document.getElementById(@options.el))
325
+
326
+ class DanteEditor extends React.Component
327
+ constructor: (props) ->
328
+ super props
329
+ #window.main_editor = @
330
+
331
+ @decorator = new CompositeDecorator([
332
+ {
333
+ strategy: findEntities.bind(null, 'LINK', @),
334
+ component: Link
335
+ }
336
+ ])
337
+
338
+ @.blockRenderMap = Map({
339
+ "image":
340
+ element: 'figure'
341
+ "video":
342
+ element: 'figure'
343
+ "embed":
344
+ element: 'div'
345
+ 'unstyled':
346
+ wrapper: null
347
+ element: 'div'
348
+ 'paragraph':
349
+ wrapper: null
350
+ element: 'div'
351
+ 'placeholder':
352
+ wrapper: null
353
+ element: 'div'
354
+
355
+ })
356
+
357
+ @extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(@blockRenderMap);
358
+
359
+ @state =
360
+ editorState: @initializeState()
361
+ read_only: @props.config.read_only
362
+ blockRenderMap: @extendedBlockRenderMap
363
+ locks: 0
364
+ debug: @props.config.debug
365
+
366
+ @widgets = @props.config.widgets
367
+ @tooltips = @props.config.tooltips
368
+
369
+ @key_commands = @props.config.key_commands
370
+
371
+ @continuousBlocks = @props.config.continuousBlocks
372
+
373
+ @block_types = @props.config.block_types
374
+
375
+ @default_wrappers = @props.config.default_wrappers
376
+
377
+ @character_convert_mapping = @props.config.character_convert_mapping
378
+
379
+ @save = new SaveBehavior
380
+ getLocks: @getLocks
381
+ config:
382
+ xhr: @props.config.xhr
383
+ data_storage: @props.config.data_storage
384
+ editorState: @state.editorState
385
+ editorContent: @emitSerializedOutput()
386
+
387
+ initializeState: ()=>
388
+ if @.props.content #and @.props.content.trim() isnt ""
389
+ @decodeEditorContent(@props.content)
390
+ else
391
+ EditorState.createEmpty(@decorator)
392
+
393
+ refreshSelection: (newEditorState)=>
394
+ editorState = @.state.editorState
395
+ # Setting cursor position after inserting to content
396
+ s = @.state.editorState.getSelection()
397
+ c = editorState.getCurrentContent()
398
+
399
+ selectionState = SelectionState.createEmpty(s.getAnchorKey())
400
+ focusOffset = s.getFocusOffset()
401
+ anchorKey = s.getAnchorKey()
402
+ # console.log anchorKey, focusOffset
403
+ selectionState = selectionState.merge({
404
+ anchorOffset: focusOffset,
405
+ focusKey: anchorKey,
406
+ focusOffset: focusOffset,
407
+ })
408
+
409
+ newState = EditorState.forceSelection(newEditorState, selectionState)
410
+
411
+ @onChange(newState)
412
+
413
+ forceRender: (editorState)=>
414
+ selection = @.state.editorState.getSelection()
415
+ content = editorState.getCurrentContent()
416
+ newEditorState = EditorState.createWithContent(content, @decorator)
417
+
418
+ @refreshSelection(newEditorState)
419
+
420
+ onChange: (editorState) =>
421
+ @setPreContent()
422
+
423
+ # console.log "BB", editorState.toJS()
424
+ @.setState({editorState})
425
+ #console.log "changed at", editorState.getSelection().getAnchorKey()
426
+ #console.log "collapsed?", editorState.getSelection().isCollapsed()
427
+
428
+ currentBlock = getCurrentBlock(@state.editorState);
429
+ blockType = currentBlock.getType()
430
+
431
+ if (!editorState.getSelection().isCollapsed())
432
+
433
+ tooltip = @tooltipsWithProp('displayOnSelection')[0]
434
+ return unless @tooltipHasSelectionElement(tooltip, blockType)
435
+
436
+ @handleTooltipDisplayOn('displayOnSelection')
437
+ else
438
+ @handleTooltipDisplayOn('displayOnSelection', false)
439
+
440
+ setTimeout ()=>
441
+ @relocateTooltips()
442
+ , 0
443
+
444
+ @dispatchChangesToSave()
445
+
446
+ dispatchChangesToSave: =>
447
+ clearTimeout @saveTimeout
448
+ @saveTimeout = setTimeout =>
449
+ @save.store(@emitSerializedOutput())
450
+ , 100
451
+
452
+ setPreContent: =>
453
+ content = @emitSerializedOutput()
454
+ # console.log "SET PRE CONTENT", content
455
+ @save.editorContent = content
456
+
457
+ focus: () =>
458
+ #@props.refs.richEditor.focus()
459
+
460
+ getEditorState: =>
461
+ @state.editorState
462
+
463
+ emitSerializedOutput: =>
464
+ #s = @state.editorState.getCurrentContent()
465
+
466
+ raw = convertToRaw( @state.editorState.getCurrentContent() )
467
+ # console.log raw
468
+
469
+ #raw_as_json = JSON.stringify(raw)
470
+ #console.log raw_as_json
471
+ #raw_as_json
472
+ raw
473
+
474
+ decodeEditorContent: (raw_as_json)=>
475
+ # console.log "CONTENT", raw_as_json
476
+ # new_content = convertFromRaw(JSON.parse(raw_as_json))
477
+ new_content = convertFromRaw(raw_as_json)
478
+ editorState = EditorState.createWithContent(new_content, @decorator)
479
+
480
+ ## title utils
481
+ getTextFromEditor: =>
482
+ c = @.state.editorState.getCurrentContent()
483
+ out = c.getBlocksAsArray().map (o)=>
484
+ o.getText()
485
+ .join("\n")
486
+
487
+ console.log out
488
+ return out
489
+
490
+ emitHTML2: ()->
491
+
492
+ html = convertToHTML(
493
+ entityToHTML: (entity, originalText) =>
494
+ if entity.type is 'LINK'
495
+ return "<a href=\"#{entity.data.url}\">#{originalText}</a>"
496
+ else
497
+ return originalText
498
+
499
+ )(@.state.editorState.getCurrentContent())
500
+ #html = convertToHTML(@.state.editorState.getCurrentContent())
501
+
502
+ getLocks: =>
503
+ @state.locks
504
+
505
+ addLock: =>
506
+ @setState
507
+ locks: @state.locks +=1
508
+
509
+ removeLock: =>
510
+ #return unless @state.locks < 0
511
+ @setState
512
+ locks: @state.locks -=1
513
+
514
+ renderableBlocks: =>
515
+ @widgets.filter (o)->
516
+ o.renderable
517
+ .map (o)->
518
+ o.type
519
+
520
+ defaultWrappers: (blockType)=>
521
+ @default_wrappers.filter (o)=>
522
+ o.block is blockType
523
+ .map (o)->
524
+ o.className
525
+
526
+ blockRenderer: (block)=>
527
+
528
+ switch block.getType()
529
+
530
+ when "atomic"
531
+
532
+ entity = block.getEntityAt(0)
533
+
534
+ #return null unless entity
535
+ entity_type = Entity.get(entity).getType()
536
+
537
+ if @renderableBlocks().includes(block.getType())
538
+ return @handleBlockRenderer(block)
539
+
540
+ return null;
541
+
542
+ handleBlockRenderer: (block)=>
543
+ dataBlock = @getDataBlock(block)
544
+ return null unless dataBlock
545
+
546
+ read_only = if @state.read_only then false else null
547
+ editable = if read_only isnt null then read_only else dataBlock.editable
548
+ # console.log "blockrender for #{block.getType()} #{editable}"
549
+
550
+ return (
551
+ component: eval(dataBlock.block)
552
+ editable: editable
553
+ props:
554
+ data: block.getData()
555
+ getEditorState: @getEditorState
556
+ setEditorState: @onChange
557
+ addLock: @addLock
558
+ removeLock: @removeLock
559
+ getLocks: @getLocks
560
+ config: dataBlock.options
561
+ )
562
+
563
+ return null
564
+
565
+ blockStyleFn: (block)=>
566
+ currentBlock = getCurrentBlock(@.state.editorState)
567
+ is_selected = if currentBlock.getKey() is block.getKey() then "is-selected" else ""
568
+
569
+ if @renderableBlocks().includes(block.getType())
570
+ return @styleForBlock(block, currentBlock, is_selected)
571
+
572
+ defaultBlockClass = @defaultWrappers(block.getType())
573
+ if defaultBlockClass.length > 0
574
+ return "graf #{defaultBlockClass[0]} #{is_selected}"
575
+ else
576
+ return "graf nana #{is_selected}"
577
+
578
+ getDataBlock: (block)=>
579
+ @widgets.find (o)=>
580
+ o.type is block.getType()
581
+
582
+ styleForBlock: (block, currentBlock, is_selected)=>
583
+ dataBlock = @getDataBlock(block)
584
+
585
+ return null unless dataBlock
586
+
587
+ selectedFn = if dataBlock.selectedFn then dataBlock.selectedFn(block) else null
588
+ selected_class = if is_selected then dataBlock.selected_class else ''
589
+
590
+ return "#{dataBlock.wrapper_class} #{selected_class} #{selectedFn}"
591
+
592
+ handleTooltipDisplayOn: (prop, display=true)->
593
+ setTimeout =>
594
+ items = @tooltipsWithProp(prop)
595
+ items.map (o)=>
596
+ @refs[o.ref].display(display)
597
+ @refs[o.ref].relocate()
598
+ , 20
599
+
600
+ handlePasteText: (text, html)=>
601
+
602
+ # https://github.com/facebook/draft-js/issues/685
603
+ ###
604
+ html = "<p>chao</p>
605
+ <avv>aaa</avv>
606
+ <p>oli</p>
607
+ <img src='x'/>"
608
+ ###
609
+
610
+ # if not html then fallback to default handler
611
+
612
+ return @handleTXTPaste(text, html) unless html
613
+ return @handleHTMLPaste(text, html) if html
614
+
615
+ handleTXTPaste: (text, html)=>
616
+ currentBlock = getCurrentBlock(@state.editorState)
617
+
618
+ editorState = @state.editorState
619
+
620
+ switch currentBlock.getType()
621
+ when "image", "video", "placeholder"
622
+ newContent = Modifier.replaceText(
623
+ editorState.getCurrentContent(),
624
+ new SelectionState({
625
+ anchorKey: currentBlock.getKey(),
626
+ anchorOffset: 0,
627
+ focusKey: currentBlock.getKey(),
628
+ focusOffset: 2
629
+ }),
630
+ text)
631
+
632
+ editorState = EditorState.push(
633
+ editorState,
634
+ newContent,
635
+ 'replace-text'
636
+ );
637
+
638
+ @onChange(editorState)
639
+
640
+ return true
641
+ else
642
+ return false
643
+
644
+ handleHTMLPaste: (text, html)=>
645
+
646
+ currentBlock = getCurrentBlock(@state.editorState)
647
+
648
+ # TODO: make this configurable
649
+ switch currentBlock.getType()
650
+ when "image", "video", "placeholder"
651
+ return @handleTXTPaste(text, html)
652
+ else
653
+ # keep going
654
+
655
+ newContentState = customHTML2Content(html, @extendedBlockRenderMap)
656
+
657
+ selection = @state.editorState.getSelection();
658
+ endKey = selection.getEndKey()
659
+
660
+ content = @state.editorState.getCurrentContent();
661
+ blocksBefore = content.blockMap.toSeq().takeUntil((v) => (v.key is endKey))
662
+ blocksAfter = content.blockMap.toSeq().skipUntil((v) => (v.key is endKey)).rest()
663
+
664
+ newBlockKey = newContentState.blockMap.first().getKey()
665
+
666
+ newBlockMap = blocksBefore.concat(
667
+ newContentState.blockMap,
668
+ blocksAfter
669
+ ).toOrderedMap();
670
+
671
+ newContent = content.merge({
672
+ blockMap: newBlockMap,
673
+ selectionBefore: selection,
674
+ selectionAfter: selection.merge({
675
+ anchorKey: newBlockKey,
676
+ anchorOffset: 0,
677
+ focusKey: newBlockKey,
678
+ focusOffset: 0,
679
+ isBackward: false,
680
+ })
681
+ });
682
+
683
+ pushedContentState = EditorState.push(
684
+ @state.editorState,
685
+ newContent,
686
+ 'insert-fragment'
687
+ )
688
+
689
+ @onChange(pushedContentState)
690
+
691
+ true
692
+
693
+ handlePasteImage: (files)=>
694
+ #TODO: check file types
695
+ files.map (file)=>
696
+ opts =
697
+ url: URL.createObjectURL(file)
698
+ file: file
699
+
700
+ @onChange(addNewBlock(@state.editorState, 'image', opts))
701
+
702
+ handleDroppedFiles: (state, files)=>
703
+ files.map (file)=>
704
+ opts =
705
+ url: URL.createObjectURL(file)
706
+ file: file
707
+
708
+ @onChange(addNewBlock(@state.editorState, 'image', opts))
709
+
710
+ handleUpArrow: (e)=>
711
+ setTimeout =>
712
+ @forceRender(@state.editorState)
713
+ , 10
714
+
715
+ handleDownArrow: (e)=>
716
+ setTimeout =>
717
+ @forceRender(@state.editorState)
718
+ , 10
719
+
720
+ handleReturn: (e) =>
721
+ if this.props.handleReturn
722
+ if this.props.handleReturn()
723
+ return true;
724
+
725
+ editorState = @state.editorState
726
+ ###
727
+ #if (isSoftNewlineEvent(e)) {
728
+ # this.onChange(RichUtils.insertSoftNewline(editorState));
729
+ # return true;
730
+ #}
731
+ ###
732
+ if !e.altKey && !e.metaKey && !e.ctrlKey
733
+ currentBlock = getCurrentBlock(editorState)
734
+ blockType = currentBlock.getType()
735
+ selection = editorState.getSelection()
736
+
737
+ config_block = @getDataBlock(currentBlock)
738
+
739
+ #if blockType.indexOf('atomic') is 0
740
+ # @.onChange(addNewBlockAt(editorState, currentBlock.getKey()))
741
+ # return true;
742
+
743
+ if currentBlock.getText().length is 0
744
+
745
+ if config_block && config_block.handleEnterWithoutText
746
+ config_block.handleEnterWithText(@, currentBlock)
747
+ @closePopOvers()
748
+ return true
749
+
750
+ #TODO turn this in configurable
751
+ switch (blockType)
752
+ when "header-one"
753
+ @.onChange(resetBlockWithType(editorState, "unstyled"));
754
+ return true;
755
+ else
756
+ return false;
757
+
758
+ if currentBlock.getText().length > 0
759
+
760
+ if blockType is "unstyled"
761
+ # hack hackety hack
762
+ # https://github.com/facebook/draft-js/issues/304
763
+ newContent = Modifier.splitBlock(
764
+ @state.editorState.getCurrentContent(),
765
+ @state.editorState.getSelection()
766
+ )
767
+
768
+ newEditorState = EditorState.push(@state.editorState, newContent, 'insert-characters')
769
+ @.onChange(newEditorState)
770
+
771
+ setTimeout =>
772
+ #TODO: check is element is in viewport
773
+ a = document.getElementsByClassName("is-selected")
774
+ pos = a[0].getBoundingClientRect()
775
+ window.scrollTo(0, pos.top + window.scrollY - 100)
776
+ , 0
777
+
778
+ return true
779
+
780
+ if config_block && config_block.handleEnterWithText
781
+ config_block.handleEnterWithText(@, currentBlock)
782
+ @closePopOvers()
783
+ return true
784
+
785
+ if ( currentBlock.getLength() is selection.getStartOffset())
786
+ if @continuousBlocks.indexOf(blockType) < 0
787
+ @.onChange(addNewBlockAt(editorState, currentBlock.getKey()))
788
+ return true
789
+
790
+ return false
791
+
792
+ # selection.isCollapsed() and # should we check collapsed here?
793
+ if ( currentBlock.getLength() is selection.getStartOffset()) #or (config_block && config_block.breakOnContinuous))
794
+ # it will match the unstyled for custom blocks
795
+ if @continuousBlocks.indexOf(blockType) < 0
796
+ @.onChange(addNewBlockAt(editorState, currentBlock.getKey()))
797
+ return true
798
+ return false
799
+
800
+ return false
801
+
802
+ #return false
803
+
804
+ # TODO: make this configurable
805
+ handleBeforeInput: (chars)=>
806
+ currentBlock = getCurrentBlock(@state.editorState)
807
+ blockType = currentBlock.getType()
808
+ selection = @.state.editorState.getSelection()
809
+ editorState = @state.editorState
810
+
811
+ # close popovers
812
+ if currentBlock.getText().length isnt 0
813
+ #@closeInlineButton()
814
+ @closePopOvers()
815
+
816
+ # handle space on link
817
+ endOffset = selection.getEndOffset()
818
+ endKey = currentBlock.getEntityAt(endOffset - 1)
819
+ endEntityType = endKey && Entity.get(endKey).getType()
820
+ afterEndKey = currentBlock.getEntityAt(endOffset)
821
+ afterEndEntityType = afterEndKey && Entity.get(afterEndKey).getType()
822
+
823
+ # will insert blank space when link found
824
+ if (chars is ' ' && endEntityType is 'LINK' && afterEndEntityType isnt 'LINK')
825
+ newContentState = Modifier.insertText(
826
+ editorState.getCurrentContent(),
827
+ selection,
828
+ ' '
829
+ )
830
+ newEditorState = EditorState.push(editorState, newContentState, 'insert-characters')
831
+ @.onChange(newEditorState)
832
+ return true
833
+
834
+ # block transform
835
+ if blockType.indexOf('atomic') is 0
836
+ return false;
837
+
838
+ blockLength = currentBlock.getLength()
839
+ if selection.getAnchorOffset() > 1 || blockLength > 1
840
+ return false
841
+
842
+ blockTo = @character_convert_mapping[currentBlock.getText() + chars]
843
+
844
+ console.log "BLOCK TO SHOW: #{blockTo}"
845
+
846
+ if !blockTo
847
+ return false
848
+
849
+ @onChange(resetBlockWithType(editorState, blockTo))
850
+
851
+ return true
852
+
853
+ # TODO: make this configurable
854
+ handleKeyCommand: (command)=>
855
+ editorState = @.state.editorState
856
+
857
+ if @.props.handleKeyCommand && @.props.handleKeyCommand(command)
858
+ return true
859
+
860
+ if command is 'add-new-block'
861
+ @.onChange(addNewBlock(editorState, 'blockquote'))
862
+ return true
863
+
864
+ block = getCurrentBlock(editorState);
865
+
866
+ if command.indexOf('toggle_inline:') is 0
867
+ newBlockType = command.split(':')[1]
868
+ currentBlockType = block.getType()
869
+ @onChange(
870
+ RichUtils.toggleInlineStyle(editorState, newBlockType)
871
+ )
872
+ return true
873
+
874
+ if command.indexOf('toggle_block:') is 0
875
+ newBlockType = command.split(':')[1]
876
+ currentBlockType = block.getType()
877
+
878
+ @onChange(
879
+ RichUtils.toggleBlockType(editorState, newBlockType)
880
+ )
881
+ return true;
882
+
883
+ newState = RichUtils.handleKeyCommand(@state.editorState, command);
884
+ if newState
885
+ @.onChange(newState);
886
+ return true;
887
+
888
+ return false;
889
+
890
+ findCommandKey: (opt, command)=>
891
+ # console.log "COMMAND find: #{opt} #{command}"
892
+ @key_commands[opt].find (o)->
893
+ o.key is command
894
+
895
+ KeyBindingFn: (e)=>
896
+
897
+ #⌘ + B / Ctrl + B Bold
898
+ #⌘ + I / Ctrl + I Italic
899
+ #⌘ + K / Ctrl + K Turn into link
900
+ #⌘ + Alt + 1 / Ctrl + Alt + 1 Header
901
+ #⌘ + Alt + 2 / Ctrl + Alt + 2 Sub-Header
902
+ #⌘ + 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)
903
+
904
+ # console.log "CTRL #{e.ctrlKey} #{e.which} alt: #{e.altKey} shift #{e.shiftKey}"
905
+ # console.log('in window::'+ e.ctrlKey+'in mac os'+e.metaKey+'####'+e.META_MASK+'##$&&'+e.CTRL_MASK);
906
+
907
+ if (e.altKey)
908
+ if (e.shiftKey)
909
+ cmd = @findCommandKey("alt-shift", e.which)
910
+ return cmd.cmd if cmd
911
+
912
+ return getDefaultKeyBinding(e)
913
+
914
+ if e.ctrlKey or e.metaKey
915
+ cmd = @findCommandKey("alt-cmd", e.which)
916
+ return cmd.cmd if cmd
917
+ return getDefaultKeyBinding(e)
918
+
919
+ else if e.ctrlKey or e.metaKey
920
+ cmd = @findCommandKey("cmd", e.which)
921
+ return cmd.cmd if cmd
922
+ return getDefaultKeyBinding(e)
923
+
924
+ return getDefaultKeyBinding(e)
925
+
926
+ # will update block state todo: movo to utils
927
+ updateBlockData: (block, options)=>
928
+ data = block.getData()
929
+ newData = data.merge(options)
930
+ newState = updateDataOfBlock(@state.editorState, block, newData)
931
+ # this fixes enter from image caption
932
+ @forceRender(newState)
933
+
934
+ setDirection: (direction_type)=>
935
+
936
+ contentState = @state.editorState.getCurrentContent()
937
+ selectionState = @state.editorState.getSelection()
938
+ block = contentState.getBlockForKey(selectionState.anchorKey);
939
+
940
+ @updateBlockData(block, {direction: direction_type})
941
+
942
+ ## read only utils
943
+ toggleEditable: =>
944
+ @closePopOvers()
945
+
946
+ @setState
947
+ read_only: !@state.read_only
948
+ , @testEmitAndDecode
949
+
950
+ closePopOvers: ()=>
951
+ @tooltips.map (o)=>
952
+ @refs[o.ref].hide()
953
+
954
+ relocateTooltips: ()=>
955
+ @tooltips.map (o)=>
956
+ @refs[o.ref].relocate()
957
+
958
+ tooltipsWithProp: (prop)=>
959
+ @tooltips.filter (o)=>
960
+ o[prop]
961
+
962
+ tooltipHasSelectionElement: (tooltip, element)=>
963
+ tooltip.selectionElements.includes(element)
964
+
965
+ #################################
966
+ # TODO: this methods belongs to popovers/link
967
+ #################################
968
+
969
+ handleShowPopLinkOver: (e)=>
970
+ @showPopLinkOver()
971
+
972
+ handleHidePopLinkOver: (e)=>
973
+ @hidePopLinkOver()
974
+
975
+ showPopLinkOver: (el)=>
976
+ # handles popover display
977
+ # using anchor or from popover
978
+
979
+ # set url first in order to calculate popover width
980
+ @refs.anchor_popover.setState
981
+ url: if el then el.href else @refs.anchor_popover.state.url
982
+
983
+ parent_el = ReactDOM.findDOMNode(@);
984
+ coords = @refs.anchor_popover.relocate(
985
+ el
986
+ ) if el
987
+
988
+ @refs.anchor_popover.setPosition coords if coords
989
+
990
+ @refs.anchor_popover.setState
991
+ show: true
992
+
993
+ @isHover = true
994
+ @cancelHide()
995
+
996
+ hidePopLinkOver: ()=>
997
+ @hideTimeout = setTimeout ()=>
998
+ @refs.anchor_popover.hide()
999
+ , 300
1000
+
1001
+ cancelHide: ()->
1002
+ # console.log "Cancel Hide"
1003
+ clearTimeout @hideTimeout
1004
+
1005
+ ###############################
1006
+
1007
+ render: =>
1008
+
1009
+ return (
1010
+ <div id="content" suppressContentEditableWarning={true}>
1011
+
1012
+ <article className="postArticle">
1013
+ <div className="postContent">
1014
+ <div className="notesSource">
1015
+ <div id="editor"
1016
+ className="postField postField--body">
1017
+
1018
+ <section className="section--first section--last">
1019
+ <div className="section-divider layoutSingleColumn">
1020
+ <hr className="section-divider"/>
1021
+ </div>
1022
+
1023
+ <div className="section-content">
1024
+ <div ref="richEditor"
1025
+ className="section-inner layoutSingleColumn"
1026
+ onClick={@.focus}>
1027
+ <Editor
1028
+ blockRendererFn={@.blockRenderer}
1029
+ editorState={@state.editorState}
1030
+ onChange={@onChange}
1031
+ onUpArrow={@handleUpArrow}
1032
+ onDownArrow={@handleDownArrow}
1033
+ handleReturn={@handleReturn}
1034
+ blockRenderMap={@state.blockRenderMap}
1035
+ blockStyleFn={@.blockStyleFn}
1036
+ handlePastedText={@handlePasteText}
1037
+ handlePastedFiles={@handlePasteImage}
1038
+ handleDroppedFiles={@handleDroppedFiles}
1039
+ handleKeyCommand={@.handleKeyCommand}
1040
+ keyBindingFn={@KeyBindingFn}
1041
+ handleBeforeInput={@.handleBeforeInput}
1042
+ readOnly={@state.read_only}
1043
+ placeholder={@props.config.body_placeholder}
1044
+ ref="editor"
1045
+ />
1046
+ </div>
1047
+ </div>
1048
+ </section>
1049
+
1050
+ </div>
1051
+ </div>
1052
+ </div>
1053
+ </article>
1054
+
1055
+ {
1056
+ @tooltips.map (o, i)=>
1057
+ <o.component
1058
+ ref={o.ref}
1059
+ key={i}
1060
+ editor={@}
1061
+ editorState={@state.editorState}
1062
+ onChange={@onChange}
1063
+ configTooltip={o}
1064
+ widget_options={o.widget_options}
1065
+
1066
+ showPopLinkOver={@showPopLinkOver}
1067
+ hidePopLinkOver={@hidePopLinkOver}
1068
+ handleOnMouseOver={@handleShowPopLinkOver}
1069
+ handleOnMouseOut={@handleHidePopLinkOver}
1070
+ />
1071
+ }
1072
+
1073
+ {
1074
+ if @state.debug
1075
+ <Debug locks={@state.locks}
1076
+ editor={@}
1077
+ />
1078
+ }
1079
+
1080
+ </div>
1081
+ )
1082
+
1083
+ module.exports = Dante