瀏覽代碼

integrated wysiwyg

Skylsmoi 5 年之前
父節點
當前提交
6658ef04d9
共有 4 個文件被更改,包括 118 次插入21 次删除
  1. 94 3
      dist/index.html
  2. 1 0
      package.json
  3. 1 0
      src/component/HtmlDocument.jsx
  4. 22 18
      src/container/HtmlDocument.jsx

+ 94 - 3
dist/index.html 查看文件

15
   <script src="./dev/popper-1.12.3.js"></script>
15
   <script src="./dev/popper-1.12.3.js"></script>
16
   <script src="./dev/bootstrap-4.0.0-beta.2.js"></script>
16
   <script src="./dev/bootstrap-4.0.0-beta.2.js"></script>
17
 
17
 
18
-  <div id='content'></div>
18
+  <script type="text/javascript" src="/asset/tinymce/jquery.tinymce.min.js"></script>
19
+  <script type="text/javascript" src="/asset/tinymce/tinymce.min.js"></script>
19
 
20
 
20
-  <script src='./html-documents.app.dev.js'></script>
21
+  <div id='content'></div>
21
 
22
 
22
   <script type='text/javascript'>
23
   <script type='text/javascript'>
23
-    // appPageHtml.renderApp('content')
24
+    (function () {
25
+      wysiwyg = function (selector, handleOnChange) {
26
+        function base64EncodeAndTinyMceInsert (files) {
27
+          for (var i = 0; i < files.length; i++) {
28
+            if (files[i].size > 1000000)
29
+              files[i].allowed = confirm(files[i].name + " fait plus de 1mo et peut prendre du temps à insérer, voulez-vous continuer ?")
30
+          }
31
+
32
+          for (var i = 0; i < files.length; i++) {
33
+            if (files[i].allowed !== false && files[i].type.match('image.*')) {
34
+              var img = document.createElement('img')
35
+
36
+              var fr = new FileReader()
37
+
38
+              fr.readAsDataURL(files[i])
39
+
40
+              fr.onloadend = function (e) {
41
+                img.src = e.target.result
42
+                tinymce.activeEditor.execCommand('mceInsertContent', false, img.outerHTML)
43
+              }
44
+            }
45
+          }
46
+        }
47
+
48
+        // HACK: The tiny mce source code modal contain a textarea, but we
49
+        // can't edit it (like it's readonly). The following solution
50
+        // solve the bug: https://stackoverflow.com/questions/36952148/tinymce-code-editor-is-readonly-in-jtable-grid
51
+        $(document).on('focusin', function(e) {
52
+          if ($(e.target).closest(".mce-window").length) {
53
+            e.stopImmediatePropagation();
54
+          }
55
+        });
56
+
57
+        tinymce.init({
58
+          selector: selector,
59
+          // height: 130,
60
+          // width: 530,
61
+          menubar: false,
62
+          resize: false,
63
+          plugins: [
64
+            'advlist autolink lists link image charmap print preview anchor textcolor',
65
+            'searchreplace visualblocks code fullscreen',
66
+            'insertdatetime media table contextmenu paste code help'
67
+          ],
68
+          toolbar: 'insert | formatselect | bold italic underline strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify  | numlist bullist outdent indent  | table | code ',
69
+          content_css: [
70
+            '//fonts.googleapis.com/css?family=Lato:300,300i,400,400i',
71
+            '//www.tinymce.com/css/codepen.min.css'
72
+          ],
73
+          setup: function ($editor) {
74
+            $editor.on('change', function(e) {
75
+              handleOnChange({target: {value: $editor.getContent()}}) // target.value to emulate a js event so the react handler can expect one
76
+            })
77
+
78
+            //////////////////////////////////////////////
79
+            // add custom btn to handle image by selecting them with system explorer
80
+            $editor.addButton('customInsertImage', {
81
+              icon: 'mce-ico mce-i-image',
82
+              title: 'Image',
83
+              onclick: function () {
84
+                if ($('#hidden_tinymce_fileinput').length > 0) $('#hidden_tinymce_fileinput').remove()
85
+
86
+                fileTag = document.createElement('input')
87
+                fileTag.id = 'hidden_tinymce_fileinput'
88
+                fileTag.type = 'file'
89
+                $('body').append(fileTag)
90
+
91
+                $('#hidden_tinymce_fileinput').on('change', function () {
92
+                  base64EncodeAndTinyMceInsert($(this)[0].files)
93
+                })
94
+
95
+                $('#hidden_tinymce_fileinput').click()
96
+              }
97
+            })
98
+
99
+            //////////////////////////////////////////////
100
+            // Handle drag & drop image into TinyMce by encoding them in base64 (to avoid uploading them somewhere and keep saving comment in string format)
101
+            $editor
102
+              .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
103
+                e.preventDefault()
104
+                e.stopPropagation()
105
+              })
106
+              .on('drop', function(e) {
107
+                base64EncodeAndTinyMceInsert(e.dataTransfer.files)
108
+              })
109
+          }
110
+        })
111
+      }
112
+    })()
24
   </script>
113
   </script>
114
+
115
+  <script src='./html-documents.app.dev.js'></script>
25
 </body>
116
 </body>
26
 </html>
117
 </html>

+ 1 - 0
package.json 查看文件

51
       "history",
51
       "history",
52
       "btoa",
52
       "btoa",
53
       "wysiwyg",
53
       "wysiwyg",
54
+      "tinymce",
54
       "GLOBAL_renderApp",
55
       "GLOBAL_renderApp",
55
       "GLOBAL_unmountApp",
56
       "GLOBAL_unmountApp",
56
       "GLOBAL_dispatchEvent"
57
       "GLOBAL_dispatchEvent"

+ 1 - 0
src/component/HtmlDocument.jsx 查看文件

14
 
14
 
15
       {props.mode === MODE.EDIT &&
15
       {props.mode === MODE.EDIT &&
16
         <TextAreaApp
16
         <TextAreaApp
17
+          id={props.wysiwygNewVersion}
17
           customClass={'html-documents__editionmode'}
18
           customClass={'html-documents__editionmode'}
18
           onClickCancelBtn={props.onClickCloseEditMode}
19
           onClickCancelBtn={props.onClickCloseEditMode}
19
           onClickValidateBtn={props.onClickValidateBtn}
20
           onClickValidateBtn={props.onClickValidateBtn}

+ 22 - 18
src/container/HtmlDocument.jsx 查看文件

22
       content: props.data ? props.data.content : debug.content,
22
       content: props.data ? props.data.content : debug.content,
23
       timeline: props.data ? [] : [], // debug.timeline,
23
       timeline: props.data ? [] : [], // debug.timeline,
24
       newComment: '',
24
       newComment: '',
25
+      timelineWysiwyg: false,
25
       mode: MODE.VIEW
26
       mode: MODE.VIEW
26
     }
27
     }
27
 
28
 
43
 
44
 
44
   componentDidMount () {
45
   componentDidMount () {
45
     console.log('HtmlDocument did mount')
46
     console.log('HtmlDocument did mount')
46
-    if (this.state.content.content_id === -1) return // debug case
47
 
47
 
48
     this.loadContent()
48
     this.loadContent()
49
-    wysiwyg()
50
   }
49
   }
51
 
50
 
52
   componentDidUpdate (prevProps, prevState) {
51
   componentDidUpdate (prevProps, prevState) {
53
-    console.log('HtmlDocument did update', prevState, this.state)
54
-    if (!prevState.content || !this.state.content) return
52
+    const { state } = this
55
 
53
 
56
-    if (prevState.content.content_id !== this.state.content.content_id) {
57
-      this.loadContent()
58
-    }
54
+    console.log('HtmlDocument did update', prevState, state)
55
+    if (!prevState.content || !state.content) return
56
+
57
+    if (prevState.content.content_id !== state.content.content_id) this.loadContent()
59
 
58
 
60
-    if (prevState.mode === MODE.VIEW && this.state.mode === MODE.EDIT) wysiwyg()
59
+    if (state.mode === MODE.EDIT) wysiwyg('#wysiwygNewVersion', this.handleChangeText)
60
+
61
+    if (!prevState.timelineWysiwyg && state.timelineWysiwyg) wysiwyg('#wysiwygTimelineComment', this.handleChangeNewComment)
62
+    else if (prevState.timelineWysiwyg && !state.timelineWysiwyg) tinymce.remove('#wysiwygTimelineComment')
61
   }
63
   }
62
 
64
 
63
   loadContent = async () => {
65
   loadContent = async () => {
120
     fetch(`${this.state.config.apiUrl}/workspaces/${this.state.content.workspace_id}/html-documents/${this.state.content.content_id}`, {
122
     fetch(`${this.state.config.apiUrl}/workspaces/${this.state.content.workspace_id}/html-documents/${this.state.content.content_id}`, {
121
       ...FETCH_CONFIG,
123
       ...FETCH_CONFIG,
122
       method: 'PUT',
124
       method: 'PUT',
123
-      body: JSON.stringify({
124
-        label: label,
125
-        raw_content: rawContent
126
-      })
125
+      body: JSON.stringify({label: label, raw_content: rawContent})
127
     })
126
     })
128
 
127
 
129
   handleClickBtnCloseApp = () => {
128
   handleClickBtnCloseApp = () => {
141
       })
140
       })
142
   }
141
   }
143
 
142
 
144
-  handleClickNewVersion = () => {
145
-    this.setState({ mode: MODE.EDIT })
146
-  }
143
+  handleClickNewVersion = () => this.setState({ mode: MODE.EDIT })
147
 
144
 
148
   handleCloseNewVersion = () => {
145
   handleCloseNewVersion = () => {
146
+    tinymce.remove('#wysiwygNewVersion')
149
     this.setState({ mode: MODE.VIEW })
147
     this.setState({ mode: MODE.VIEW })
150
   }
148
   }
151
 
149
 
166
   }
164
   }
167
 
165
 
168
   handleChangeText = e => {
166
   handleChangeText = e => {
169
-    const newText = e.target.value // because SyntheticEvent is pooled (react specificity
167
+    const newText = e.target.value // because SyntheticEvent is pooled (react specificity)
170
     this.setState(prev => ({content: {...prev.content, raw_content: newText}}))
168
     this.setState(prev => ({content: {...prev.content, raw_content: newText}}))
171
   }
169
   }
172
 
170
 
190
       .then(resSave => {
188
       .then(resSave => {
191
         if (resSave.apiResponse.status === 200) {
189
         if (resSave.apiResponse.status === 200) {
192
           this.setState({newComment: ''})
190
           this.setState({newComment: ''})
191
+          if (this.state.timelineWysiwyg) tinymce.get('wysiwygTimelineComment').setContent('')
193
           this.loadContent()
192
           this.loadContent()
194
         } else {
193
         } else {
195
           console.warn('Error saving html-document comment. Result:', resSave, 'content:', content, 'config:', config)
194
           console.warn('Error saving html-document comment. Result:', resSave, 'content:', content, 'config:', config)
197
       })
196
       })
198
   }
197
   }
199
 
198
 
199
+  handleToggleWysiwyg = () => this.setState(prev => ({timelineWysiwyg: !prev.timelineWysiwyg}))
200
+
200
   handleChangeStatus = async newStatus => {
201
   handleChangeStatus = async newStatus => {
201
     const { config, content } = this.state
202
     const { config, content } = this.state
202
 
203
 
237
   }
238
   }
238
 
239
 
239
   render () {
240
   render () {
240
-    const { isVisible, loggedUser, content, timeline, newComment, config } = this.state
241
+    const { isVisible, loggedUser, content, timeline, newComment, timelineWysiwyg, config } = this.state
241
 
242
 
242
     if (!isVisible) return null
243
     if (!isVisible) return null
243
 
244
 
256
           availableStatus={config.availableStatuses}
257
           availableStatus={config.availableStatuses}
257
           onClickNewVersionBtn={this.handleClickNewVersion}
258
           onClickNewVersionBtn={this.handleClickNewVersion}
258
           onChangeStatus={this.handleChangeStatus}
259
           onChangeStatus={this.handleChangeStatus}
259
-          selectedStatus={config.availableStatuses.find(s => s.slug === content.status)} // peut être vide avant que api reponde
260
+          selectedStatus={config.availableStatuses.find(s => s.slug === content.status)} // might be empty while api hasn't responded yet
260
           onClickArchive={this.handleClickArchive}
261
           onClickArchive={this.handleClickArchive}
261
           onClickDelete={this.handleClickDelete}
262
           onClickDelete={this.handleClickDelete}
262
           i18n={i18n}
263
           i18n={i18n}
265
         <PopinFixedContent customClass={`${config.slug}__contentpage`}>
266
         <PopinFixedContent customClass={`${config.slug}__contentpage`}>
266
           <HtmlDocumentComponent
267
           <HtmlDocumentComponent
267
             mode={this.state.mode}
268
             mode={this.state.mode}
269
+            wysiwygNewVersion={'wysiwygNewVersion'}
268
             onClickCloseEditMode={this.handleCloseNewVersion}
270
             onClickCloseEditMode={this.handleCloseNewVersion}
269
             onClickValidateBtn={this.handleSaveHtmlDocument}
271
             onClickValidateBtn={this.handleSaveHtmlDocument}
270
             version={timeline.filter(t => t.timelineType === 'revision').length}
272
             version={timeline.filter(t => t.timelineType === 'revision').length}
278
             loggedUser={loggedUser}
280
             loggedUser={loggedUser}
279
             timelineData={timeline}
281
             timelineData={timeline}
280
             newComment={newComment}
282
             newComment={newComment}
283
+            wysiwyg={timelineWysiwyg}
281
             onChangeNewComment={this.handleChangeNewComment}
284
             onChangeNewComment={this.handleChangeNewComment}
282
             onClickValidateNewCommentBtn={this.handleClickValidateNewCommentBtn}
285
             onClickValidateNewCommentBtn={this.handleClickValidateNewCommentBtn}
286
+            onClickWysiwygBtn={this.handleToggleWysiwyg}
283
           />
287
           />
284
         </PopinFixedContent>
288
         </PopinFixedContent>
285
       </PopinFixed>
289
       </PopinFixed>