Browse Source

Merge branch 'develop' of github.com:tracim/tracim_v2 into feature/refactor_content_type_and_content_status

Guénaël Muller 6 years ago
parent
commit
75b93f272a
92 changed files with 878 additions and 496 deletions
  1. 74 0
      README_traduction.md
  2. 4 4
      backend/tracim_backend/models/applications.py
  3. 1 1
      backend/tracim_backend/models/contents.py
  4. 1 1
      backend/tracim_backend/models/context_models.py
  5. 1 1
      backend/tracim_backend/tests/__init__.py
  6. 9 9
      backend/tracim_backend/tests/functional/test_contents.py
  7. 5 6
      backend/tracim_backend/tests/functional/test_system.py
  8. 4 4
      backend/tracim_backend/tests/functional/test_user.py
  9. 111 46
      backend/tracim_backend/tests/functional/test_workspaces.py
  10. 9 5
      backend/tracim_backend/views/core_api/schemas.py
  11. 23 0
      build_full_frontend.sh
  12. 1 1
      frontend/dist/appInterface.js
  13. 16 0
      frontend/i18next.scanner.js
  14. 23 0
      frontend/i18next.scanner/en/translation.json
  15. 23 0
      frontend/i18next.scanner/fr/translation.json
  16. 2 0
      frontend/package.json
  17. 24 17
      frontend/src/action-creator.sync.js
  18. 5 2
      frontend/src/appFactory.js
  19. 1 1
      frontend/src/component/FlashMessage.jsx
  20. 1 1
      frontend/src/component/Footer.jsx
  21. 4 4
      frontend/src/component/Header/MenuActionListItem/DropdownLang.jsx
  22. 4 3
      frontend/src/component/Header/MenuActionListItem/MenuProfil.jsx
  23. 5 4
      frontend/src/component/Header/MenuActionListItem/Notification.jsx
  24. 1 1
      frontend/src/component/Header/MenuActionListItem/Search.jsx
  25. 7 6
      frontend/src/component/Workspace/BtnExtandedAction.jsx
  26. 3 3
      frontend/src/component/Workspace/ContentItemHeader.jsx
  27. 1 1
      frontend/src/component/Workspace/Folder.jsx
  28. 1 1
      frontend/src/component/Workspace/OpenContentApp.jsx
  29. 5 2
      frontend/src/container/Dashboard.jsx
  30. 13 8
      frontend/src/container/Header.jsx
  31. 1 1
      frontend/src/container/Login.jsx
  32. 1 1
      frontend/src/container/Sidebar.jsx
  33. 1 1
      frontend/src/css/Generic.styl
  34. 0 1
      frontend/src/css/Header.styl
  35. 3 3
      frontend/src/css/Login.styl
  36. 1 1
      frontend/src/css/Workspace.styl
  37. 19 9
      frontend/src/i18n.js
  38. 0 0
      frontend/src/img/flag_en.png
  39. 0 0
      frontend/src/img/flag_fr.png
  40. 2 2
      frontend/src/reducer/app.js
  41. 2 2
      frontend/src/reducer/contentType.js
  42. 3 5
      frontend/src/reducer/flashMessage.js
  43. 16 5
      frontend/src/reducer/lang.js
  44. 2 2
      frontend/src/reducer/timezone.js
  45. 14 5
      frontend/src/reducer/user.js
  46. 5 3
      frontend/src/reducer/workspaceContent.js
  47. 6 4
      frontend/src/reducer/workspaceList.js
  48. 0 41
      frontend/src/translate/en.js
  49. 0 41
      frontend/src/translate/fr.js
  50. 17 0
      frontend_app_html-document/build_html-document.sh
  51. 1 1
      frontend_app_html-document/dist/index.html
  52. 14 0
      frontend_app_html-document/i18next.scanner.js
  53. 5 0
      frontend_app_html-document/i18next.scanner/en/translation.json
  54. 5 0
      frontend_app_html-document/i18next.scanner/fr/translation.json
  55. 4 2
      frontend_app_html-document/package.json
  56. 0 13
      frontend_app_html-document/rebuild_html-document.sh
  57. 5 5
      frontend_app_html-document/src/component/HtmlDocument.jsx
  58. 27 9
      frontend_app_html-document/src/container/HtmlDocument.jsx
  59. 31 5
      frontend_app_html-document/src/container/PopupCreateHtmlDocument.jsx
  60. 5 5
      frontend_app_html-document/src/css/index.styl
  61. 17 4
      frontend_app_html-document/src/helper.js
  62. 1 11
      frontend_app_html-document/src/i18n.js
  63. 1 1
      frontend_app_html-document/src/index.js
  64. 0 9
      frontend_app_html-document/src/translate/en.js
  65. 0 9
      frontend_app_html-document/src/translate/fr.js
  66. 17 0
      frontend_app_thread/build_thread.sh
  67. 14 0
      frontend_app_thread/i18next.scanner.js
  68. 4 0
      frontend_app_thread/i18next.scanner/en/translation.json
  69. 4 0
      frontend_app_thread/i18next.scanner/fr/translation.json
  70. 2 0
      frontend_app_thread/package.json
  71. 0 13
      frontend_app_thread/rebuild_thread.sh
  72. 38 5
      frontend_app_thread/src/container/PopupCreateThread.jsx
  73. 16 0
      frontend_app_thread/src/container/Thread.jsx
  74. 2 2
      frontend_app_thread/src/helper.js
  75. 1 11
      frontend_app_thread/src/i18n.js
  76. 1 1
      frontend_app_thread/src/index.js
  77. 0 9
      frontend_app_thread/src/translate/en.js
  78. 0 9
      frontend_app_thread/src/translate/fr.js
  79. 9 0
      frontend_lib/i18next.scanner.js
  80. 1 0
      frontend_lib/i18next.scanner/en/translation.json
  81. 1 0
      frontend_lib/i18next.scanner/fr/translation.json
  82. 3 1
      frontend_lib/package.json
  83. 3 2
      frontend_lib/src/component/CardPopup/CardPopupCreateContent.jsx
  84. 6 7
      frontend_lib/src/component/Input/SelectStatus/SelectStatus.jsx
  85. 8 11
      frontend_lib/src/component/Timeline/Comment.jsx
  86. 1 1
      frontend_lib/src/component/Timeline/Timeline.jsx
  87. 3 6
      frontend_lib/src/component/Timeline/Timeline.styl
  88. 84 84
      frontend_lib/src/component/Timeline/debugData.js
  89. 8 0
      frontend_lib/src/helper.js
  90. 5 1
      frontend_lib/src/index.js
  91. 40 0
      i18next.option.js
  92. 16 0
      install_frontend_dependencies.sh

+ 74 - 0
README_traduction.md View File

1
+# Tracim internationalization
2
+
3
+## How to for Frontend part
4
+
5
+In each frontend repo (frontend, frontend_app_..., frontend_lib), there is a folder i18next.scanner that holds every translation files in JSON.
6
+
7
+___
8
+
9
+### I have found a translation error, how do I fix it ?
10
+
11
+**If the error is in any language other than english:**
12
+
13
+a) you can edit the values of the json files.
14
+
15
+b) Then commit/push your changes
16
+
17
+**If the error is in en.json:**
18
+
19
+1) You must find the key in the according .jsx file of that same repo.
20
+
21
+2) Fix the error
22
+ 
23
+3) Rebuild the translation files with:
24
+
25
+`npm run build-translation`
26
+
27
+This will add your new key in the translation files and remove the old one.
28
+
29
+4) Add translations for your new key in other .json files.
30
+
31
+5) commit/push your changes
32
+
33
+___
34
+
35
+### I have found an untranslated key in a language, how do I fix it ?
36
+
37
+It means you have found an english text even though you have selected another language. 
38
+
39
+Do task a) and b) in the according .json file, in folder i18next.scanner.
40
+
41
+___
42
+
43
+### I have found an untranslated key in a language but the key does not appear in the .json file.
44
+
45
+Do step 3).
46
+
47
+If the key still isn't in the .json file, it means the text in the .jsx file does not implement the translation process.
48
+
49
+So you must:
50
+
51
+I) Find the according .jsx file that have your untranslated key
52
+
53
+II) wrap your untranslated key in the translation function `t`:
54
+
55
+Exemple: `<div>My untranslated key</div>` will become `<div>{this.props.t('My untranslated key')}</div>`
56
+
57
+III) Check that `t` in available in your component, meanings your component must be wraped in the `translate()` higher order function
58
+
59
+``` javascript
60
+import React from 'react'
61
+import { translate } from 'react-i18next'
62
+
63
+class MyComponent extends React.Component {
64
+  render () {
65
+    return (<div>{this.props.t('My untranslated key')}</div>)
66
+  }
67
+}
68
+
69
+export default translate()(MyComponent)
70
+```
71
+
72
+IV) You can destruct `t` from `this.props` in the `render()` like it is done in most components.
73
+
74
+V) Do steps 3), 4), 5)

+ 4 - 4
backend/tracim_backend/models/applications.py View File

50
 
50
 
51
 thread = Application(
51
 thread = Application(
52
     label='Threads',
52
     label='Threads',
53
-    slug='contents/threads',
53
+    slug='contents/thread',
54
     fa_icon='comments-o',
54
     fa_icon='comments-o',
55
     hexcolor='#ad4cf9',
55
     hexcolor='#ad4cf9',
56
     is_active=True,
56
     is_active=True,
61
 
61
 
62
 _file = Application(
62
 _file = Application(
63
     label='Files',
63
     label='Files',
64
-    slug='contents/files',
64
+    slug='contents/file',
65
     fa_icon='paperclip',
65
     fa_icon='paperclip',
66
     hexcolor='#FF9900',
66
     hexcolor='#FF9900',
67
     is_active=True,
67
     is_active=True,
81
 
81
 
82
 html_documents = Application(
82
 html_documents = Application(
83
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
83
     label='Text Documents',  # TODO - G.M - 24-05-2018 - Check label
84
-    slug='contents/html-documents',
84
+    slug='contents/html-document',
85
     fa_icon='file-text-o',
85
     fa_icon='file-text-o',
86
     hexcolor='#3f52e3',
86
     hexcolor='#3f52e3',
87
     is_active=True,
87
     is_active=True,
88
     config={},
88
     config={},
89
-    main_route='/#/workspaces/{workspace_id}/contents?type=html-documents',
89
+    main_route='/#/workspaces/{workspace_id}/contents?type=html-document',
90
 )
90
 )
91
 # TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
91
 # TODO - G.M - 08-06-2018 - This is hardcoded lists of app, make this dynamic.
92
 # List of applications
92
 # List of applications

+ 1 - 1
backend/tracim_backend/models/contents.py View File

159
 )
159
 )
160
 
160
 
161
 html_documents_type = ContentType(
161
 html_documents_type = ContentType(
162
-    slug='html-documents',
162
+    slug='html-document',
163
     fa_icon=html_documents.fa_icon,
163
     fa_icon=html_documents.fa_icon,
164
     hexcolor=html_documents.hexcolor,
164
     hexcolor=html_documents.hexcolor,
165
     label='Text Document',
165
     label='Text Document',

+ 1 - 1
backend/tracim_backend/models/context_models.py View File

291
     ) -> None:
291
     ) -> None:
292
         self.label = label
292
         self.label = label
293
         self.content_type = content_type
293
         self.content_type = content_type
294
-        self.parent_id = parent_id
294
+        self.parent_id = parent_id or None
295
 
295
 
296
 
296
 
297
 class CommentCreation(object):
297
 class CommentCreation(object):

+ 1 - 1
backend/tracim_backend/tests/__init__.py View File

50
     )
50
     )
51
     content_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'page').filter(ContentRevisionRO.content_id == 6)  # nopep8
51
     content_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'page').filter(ContentRevisionRO.content_id == 6)  # nopep8
52
     assert content_query.count() == 0
52
     assert content_query.count() == 0
53
-    html_documents_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'html-documents')  # nopep8
53
+    html_documents_query = dbsession.query(ContentRevisionRO).filter(ContentRevisionRO.type == 'html-document')  # nopep8
54
     html_documents_query.update({ContentRevisionRO.type: 'page'})
54
     html_documents_query.update({ContentRevisionRO.type: 'page'})
55
     transaction.commit()
55
     transaction.commit()
56
     assert content_query.count() > 0
56
     assert content_query.count() > 0

+ 9 - 9
backend/tracim_backend/tests/functional/test_contents.py View File

50
             status=200
50
             status=200
51
         )
51
         )
52
         content = res.json_body
52
         content = res.json_body
53
-        assert content['content_type'] == 'html-documents'
53
+        assert content['content_type'] == 'html-document'
54
         assert content['content_id'] == 6
54
         assert content['content_id'] == 6
55
         assert content['is_archived'] is False
55
         assert content['is_archived'] is False
56
         assert content['is_deleted'] is False
56
         assert content['is_deleted'] is False
91
             status=200
91
             status=200
92
         )
92
         )
93
         content = res.json_body
93
         content = res.json_body
94
-        assert content['content_type'] == 'html-documents'
94
+        assert content['content_type'] == 'html-document'
95
         assert content['content_id'] == 6
95
         assert content['content_id'] == 6
96
         assert content['is_archived'] is False
96
         assert content['is_archived'] is False
97
         assert content['is_deleted'] is False
97
         assert content['is_deleted'] is False
254
             status=200
254
             status=200
255
         )
255
         )
256
         content = res.json_body
256
         content = res.json_body
257
-        assert content['content_type'] == 'html-documents'
257
+        assert content['content_type'] == 'html-document'
258
         assert content['content_id'] == 6
258
         assert content['content_id'] == 6
259
         assert content['is_archived'] is False
259
         assert content['is_archived'] is False
260
         assert content['is_deleted'] is False
260
         assert content['is_deleted'] is False
281
             status=200
281
             status=200
282
         )
282
         )
283
         content = res.json_body
283
         content = res.json_body
284
-        assert content['content_type'] == 'html-documents'
284
+        assert content['content_type'] == 'html-document'
285
         assert content['content_id'] == 6
285
         assert content['content_id'] == 6
286
         assert content['is_archived'] is False
286
         assert content['is_archived'] is False
287
         assert content['is_deleted'] is False
287
         assert content['is_deleted'] is False
323
         revisions = res.json_body
323
         revisions = res.json_body
324
         assert len(revisions) == 3
324
         assert len(revisions) == 3
325
         revision = revisions[0]
325
         revision = revisions[0]
326
-        assert revision['content_type'] == 'html-documents'
326
+        assert revision['content_type'] == 'html-document'
327
         assert revision['content_id'] == 6
327
         assert revision['content_id'] == 6
328
         assert revision['is_archived'] is False
328
         assert revision['is_archived'] is False
329
         assert revision['is_deleted'] is False
329
         assert revision['is_deleted'] is False
345
         assert revision['author']['avatar_url'] is None
345
         assert revision['author']['avatar_url'] is None
346
         assert revision['author']['public_name'] == 'Global manager'
346
         assert revision['author']['public_name'] == 'Global manager'
347
         revision = revisions[1]
347
         revision = revisions[1]
348
-        assert revision['content_type'] == 'html-documents'
348
+        assert revision['content_type'] == 'html-document'
349
         assert revision['content_id'] == 6
349
         assert revision['content_id'] == 6
350
         assert revision['is_archived'] is False
350
         assert revision['is_archived'] is False
351
         assert revision['is_deleted'] is False
351
         assert revision['is_deleted'] is False
367
         assert revision['author']['avatar_url'] is None
367
         assert revision['author']['avatar_url'] is None
368
         assert revision['author']['public_name'] == 'Global manager'
368
         assert revision['author']['public_name'] == 'Global manager'
369
         revision = revisions[2]
369
         revision = revisions[2]
370
-        assert revision['content_type'] == 'html-documents'
370
+        assert revision['content_type'] == 'html-document'
371
         assert revision['content_id'] == 6
371
         assert revision['content_id'] == 6
372
         assert revision['is_archived'] is False
372
         assert revision['is_archived'] is False
373
         assert revision['is_deleted'] is False
373
         assert revision['is_deleted'] is False
410
             status=200
410
             status=200
411
         )
411
         )
412
         content = res.json_body
412
         content = res.json_body
413
-        assert content['content_type'] == 'html-documents'
413
+        assert content['content_type'] == 'html-document'
414
         assert content['content_id'] == 6
414
         assert content['content_id'] == 6
415
         assert content['status'] == 'open'
415
         assert content['status'] == 'open'
416
 
416
 
427
             status=200
427
             status=200
428
         )
428
         )
429
         content = res.json_body
429
         content = res.json_body
430
-        assert content['content_type'] == 'html-documents'
430
+        assert content['content_type'] == 'html-document'
431
         assert content['content_id'] == 6
431
         assert content['content_id'] == 6
432
         assert content['status'] == 'closed-deprecated'
432
         assert content['status'] == 'closed-deprecated'
433
 
433
 

+ 5 - 6
backend/tracim_backend/tests/functional/test_system.py View File

5
 Tests for /api/v2/system subpath endpoints.
5
 Tests for /api/v2/system subpath endpoints.
6
 """
6
 """
7
 
7
 
8
+
8
 class TestApplicationEndpoint(FunctionalTest):
9
 class TestApplicationEndpoint(FunctionalTest):
9
     """
10
     """
10
     Tests for /api/v2/system/applications
11
     Tests for /api/v2/system/applications
25
         res = res.json_body
26
         res = res.json_body
26
         application = res[0]
27
         application = res[0]
27
         assert application['label'] == "Text Documents"
28
         assert application['label'] == "Text Documents"
28
-        assert application['slug'] == 'contents/html-documents'
29
+        assert application['slug'] == 'contents/html-document'
29
         assert application['fa_icon'] == 'file-text-o'
30
         assert application['fa_icon'] == 'file-text-o'
30
         assert application['hexcolor'] == '#3f52e3'
31
         assert application['hexcolor'] == '#3f52e3'
31
         assert application['is_active'] is True
32
         assert application['is_active'] is True
39
         assert 'config' in application
40
         assert 'config' in application
40
         application = res[2]
41
         application = res[2]
41
         assert application['label'] == "Files"
42
         assert application['label'] == "Files"
42
-        assert application['slug'] == 'contents/files'
43
+        assert application['slug'] == 'contents/file'
43
         assert application['fa_icon'] == 'paperclip'
44
         assert application['fa_icon'] == 'paperclip'
44
         assert application['hexcolor'] == '#FF9900'
45
         assert application['hexcolor'] == '#FF9900'
45
         assert application['is_active'] is True
46
         assert application['is_active'] is True
46
         assert 'config' in application
47
         assert 'config' in application
47
         application = res[3]
48
         application = res[3]
48
         assert application['label'] == "Threads"
49
         assert application['label'] == "Threads"
49
-        assert application['slug'] == 'contents/threads'
50
+        assert application['slug'] == 'contents/thread'
50
         assert application['fa_icon'] == 'comments-o'
51
         assert application['fa_icon'] == 'comments-o'
51
         assert application['hexcolor'] == '#ad4cf9'
52
         assert application['hexcolor'] == '#ad4cf9'
52
         assert application['is_active'] is True
53
         assert application['is_active'] is True
122
         assert content_type['creation_label'] == 'Create a Markdown document'
123
         assert content_type['creation_label'] == 'Create a Markdown document'
123
         assert 'available_statuses' in content_type
124
         assert 'available_statuses' in content_type
124
         assert len(content_type['available_statuses']) == 4
125
         assert len(content_type['available_statuses']) == 4
125
-
126
-        content_type = res[4]
127
-        assert content_type['slug'] == 'html-documents'
126
+        assert content_type['slug'] == 'html-document'
128
         assert content_type['fa_icon'] == 'file-text-o'
127
         assert content_type['fa_icon'] == 'file-text-o'
129
         assert content_type['hexcolor'] == '#3f52e3'
128
         assert content_type['hexcolor'] == '#3f52e3'
130
         assert content_type['label'] == 'Text Document'
129
         assert content_type['label'] == 'Text Document'

+ 4 - 4
backend/tracim_backend/tests/functional/test_user.py View File

762
         assert sidebar_entry['fa_icon'] == "th"
762
         assert sidebar_entry['fa_icon'] == "th"
763
 
763
 
764
         sidebar_entry = workspace['sidebar_entries'][2]
764
         sidebar_entry = workspace['sidebar_entries'][2]
765
-        assert sidebar_entry['slug'] == 'contents/html-documents'
765
+        assert sidebar_entry['slug'] == 'contents/html-document'
766
         assert sidebar_entry['label'] == 'Text Documents'
766
         assert sidebar_entry['label'] == 'Text Documents'
767
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-documents'  # nopep8
767
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
768
         assert sidebar_entry['hexcolor'] == "#3f52e3"
768
         assert sidebar_entry['hexcolor'] == "#3f52e3"
769
         assert sidebar_entry['fa_icon'] == "file-text-o"
769
         assert sidebar_entry['fa_icon'] == "file-text-o"
770
 
770
 
776
         assert sidebar_entry['fa_icon'] == "file-code-o"
776
         assert sidebar_entry['fa_icon'] == "file-code-o"
777
 
777
 
778
         sidebar_entry = workspace['sidebar_entries'][4]
778
         sidebar_entry = workspace['sidebar_entries'][4]
779
-        assert sidebar_entry['slug'] == 'contents/files'
779
+        assert sidebar_entry['slug'] == 'contents/file'
780
         assert sidebar_entry['label'] == 'Files'
780
         assert sidebar_entry['label'] == 'Files'
781
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
781
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
782
         assert sidebar_entry['hexcolor'] == "#FF9900"
782
         assert sidebar_entry['hexcolor'] == "#FF9900"
783
         assert sidebar_entry['fa_icon'] == "paperclip"
783
         assert sidebar_entry['fa_icon'] == "paperclip"
784
 
784
 
785
         sidebar_entry = workspace['sidebar_entries'][5]
785
         sidebar_entry = workspace['sidebar_entries'][5]
786
-        assert sidebar_entry['slug'] == 'contents/threads'
786
+        assert sidebar_entry['slug'] == 'contents/thread'
787
         assert sidebar_entry['label'] == 'Threads'
787
         assert sidebar_entry['label'] == 'Threads'
788
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
788
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
789
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
789
         assert sidebar_entry['hexcolor'] == "#ad4cf9"

+ 111 - 46
backend/tracim_backend/tests/functional/test_workspaces.py View File

58
         assert sidebar_entry['fa_icon'] == "th"
58
         assert sidebar_entry['fa_icon'] == "th"
59
 
59
 
60
         sidebar_entry = workspace['sidebar_entries'][2]
60
         sidebar_entry = workspace['sidebar_entries'][2]
61
-        assert sidebar_entry['slug'] == 'contents/html-documents'
61
+        assert sidebar_entry['slug'] == 'contents/html-document'
62
         assert sidebar_entry['label'] == 'Text Documents'
62
         assert sidebar_entry['label'] == 'Text Documents'
63
-        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-documents'  # nopep8
63
+        assert sidebar_entry['route'] == '/#/workspaces/1/contents?type=html-document'  # nopep8
64
         assert sidebar_entry['hexcolor'] == "#3f52e3"
64
         assert sidebar_entry['hexcolor'] == "#3f52e3"
65
         assert sidebar_entry['fa_icon'] == "file-text-o"
65
         assert sidebar_entry['fa_icon'] == "file-text-o"
66
 
66
 
72
         assert sidebar_entry['fa_icon'] == "file-code-o"
72
         assert sidebar_entry['fa_icon'] == "file-code-o"
73
 
73
 
74
         sidebar_entry = workspace['sidebar_entries'][4]
74
         sidebar_entry = workspace['sidebar_entries'][4]
75
-        assert sidebar_entry['slug'] == 'contents/files'
75
+        assert sidebar_entry['slug'] == 'contents/file'
76
         assert sidebar_entry['label'] == 'Files'
76
         assert sidebar_entry['label'] == 'Files'
77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78
         assert sidebar_entry['hexcolor'] == "#FF9900"
78
         assert sidebar_entry['hexcolor'] == "#FF9900"
79
         assert sidebar_entry['fa_icon'] == "paperclip"
79
         assert sidebar_entry['fa_icon'] == "paperclip"
80
 
80
 
81
         sidebar_entry = workspace['sidebar_entries'][5]
81
         sidebar_entry = workspace['sidebar_entries'][5]
82
-        assert sidebar_entry['slug'] == 'contents/threads'
82
+        assert sidebar_entry['slug'] == 'contents/thread'
83
         assert sidebar_entry['label'] == 'Threads'
83
         assert sidebar_entry['label'] == 'Threads'
84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
643
         assert content['show_in_ui'] is True
643
         assert content['show_in_ui'] is True
644
         assert content['slug'] == 'tools'
644
         assert content['slug'] == 'tools'
645
         assert content['status'] == 'open'
645
         assert content['status'] == 'open'
646
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
646
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
647
         assert content['workspace_id'] == 1
647
         assert content['workspace_id'] == 1
648
         content = res[1]
648
         content = res[1]
649
         assert content['content_id'] == 2
649
         assert content['content_id'] == 2
655
         assert content['show_in_ui'] is True
655
         assert content['show_in_ui'] is True
656
         assert content['slug'] == 'menus'
656
         assert content['slug'] == 'menus'
657
         assert content['status'] == 'open'
657
         assert content['status'] == 'open'
658
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
658
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
659
         assert content['workspace_id'] == 1
659
         assert content['workspace_id'] == 1
660
         content = res[2]
660
         content = res[2]
661
         assert content['content_id'] == 11
661
         assert content['content_id'] == 11
662
-        assert content['content_type'] == 'html-documents'
662
+        assert content['content_type'] == 'html-document'
663
         assert content['is_archived'] is False
663
         assert content['is_archived'] is False
664
         assert content['is_deleted'] is False
664
         assert content['is_deleted'] is False
665
         assert content['label'] == 'Current Menu'
665
         assert content['label'] == 'Current Menu'
667
         assert content['show_in_ui'] is True
667
         assert content['show_in_ui'] is True
668
         assert content['slug'] == 'current-menu'
668
         assert content['slug'] == 'current-menu'
669
         assert content['status'] == 'open'
669
         assert content['status'] == 'open'
670
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
670
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
671
         assert content['workspace_id'] == 1
671
         assert content['workspace_id'] == 1
672
 
672
 
673
     def test_api__get_workspace_content__ok_200__get_default_html_documents(self):
673
     def test_api__get_workspace_content__ok_200__get_default_html_documents(self):
682
             )
682
             )
683
         )
683
         )
684
         params = {
684
         params = {
685
-            'content_type': 'html-documents',
685
+            'content_type': 'html-document',
686
         }
686
         }
687
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200, params=params).json_body   # nopep8
687
         res = self.testapp.get('/api/v2/workspaces/1/contents', status=200, params=params).json_body   # nopep8
688
         assert len(res) == 1
688
         assert len(res) == 1
689
         content = res[0]
689
         content = res[0]
690
         assert content
690
         assert content
691
         assert content['content_id'] == 11
691
         assert content['content_id'] == 11
692
-        assert content['content_type'] == 'html-documents'
692
+        assert content['content_type'] == 'html-document'
693
         assert content['is_archived'] is False
693
         assert content['is_archived'] is False
694
         assert content['is_deleted'] is False
694
         assert content['is_deleted'] is False
695
         assert content['label'] == 'Current Menu'
695
         assert content['label'] == 'Current Menu'
697
         assert content['show_in_ui'] is True
697
         assert content['show_in_ui'] is True
698
         assert content['slug'] == 'current-menu'
698
         assert content['slug'] == 'current-menu'
699
         assert content['status'] == 'open'
699
         assert content['status'] == 'open'
700
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
700
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
701
         assert content['workspace_id'] == 1
701
         assert content['workspace_id'] == 1
702
 
702
 
703
     # Root related
703
     # Root related
727
         # TODO - G.M - 30-05-2018 - Check this test
727
         # TODO - G.M - 30-05-2018 - Check this test
728
         assert len(res) == 4
728
         assert len(res) == 4
729
         content = res[1]
729
         content = res[1]
730
-        assert content['content_type'] == 'html-documents'
730
+        assert content['content_type'] == 'html-document'
731
         assert content['content_id'] == 15
731
         assert content['content_id'] == 15
732
         assert content['is_archived'] is False
732
         assert content['is_archived'] is False
733
         assert content['is_deleted'] is False
733
         assert content['is_deleted'] is False
736
         assert content['show_in_ui'] is True
736
         assert content['show_in_ui'] is True
737
         assert content['slug'] == 'new-fruit-salad'
737
         assert content['slug'] == 'new-fruit-salad'
738
         assert content['status'] == 'open'
738
         assert content['status'] == 'open'
739
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
739
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
740
         assert content['workspace_id'] == 3
740
         assert content['workspace_id'] == 3
741
 
741
 
742
         content = res[2]
742
         content = res[2]
743
-        assert content['content_type'] == 'html-documents'
743
+        assert content['content_type'] == 'html-document'
744
         assert content['content_id'] == 16
744
         assert content['content_id'] == 16
745
         assert content['is_archived'] is True
745
         assert content['is_archived'] is True
746
         assert content['is_deleted'] is False
746
         assert content['is_deleted'] is False
749
         assert content['show_in_ui'] is True
749
         assert content['show_in_ui'] is True
750
         assert content['slug'].startswith('fruit-salad')
750
         assert content['slug'].startswith('fruit-salad')
751
         assert content['status'] == 'open'
751
         assert content['status'] == 'open'
752
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
752
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
753
         assert content['workspace_id'] == 3
753
         assert content['workspace_id'] == 3
754
 
754
 
755
         content = res[3]
755
         content = res[3]
756
-        assert content['content_type'] == 'html-documents'
756
+        assert content['content_type'] == 'html-document'
757
         assert content['content_id'] == 17
757
         assert content['content_id'] == 17
758
         assert content['is_archived'] is False
758
         assert content['is_archived'] is False
759
         assert content['is_deleted'] is True
759
         assert content['is_deleted'] is True
762
         assert content['show_in_ui'] is True
762
         assert content['show_in_ui'] is True
763
         assert content['slug'].startswith('bad-fruit-salad')
763
         assert content['slug'].startswith('bad-fruit-salad')
764
         assert content['status'] == 'open'
764
         assert content['status'] == 'open'
765
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
765
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
766
         assert content['workspace_id'] == 3
766
         assert content['workspace_id'] == 3
767
 
767
 
768
     def test_api__get_workspace_content__ok_200__get_all_root_content(self):
768
     def test_api__get_workspace_content__ok_200__get_all_root_content(self):
790
         # TODO - G.M - 30-05-2018 - Check this test
790
         # TODO - G.M - 30-05-2018 - Check this test
791
         assert len(res) == 4
791
         assert len(res) == 4
792
         content = res[1]
792
         content = res[1]
793
-        assert content['content_type'] == 'html-documents'
793
+        assert content['content_type'] == 'html-document'
794
         assert content['content_id'] == 15
794
         assert content['content_id'] == 15
795
         assert content['is_archived'] is False
795
         assert content['is_archived'] is False
796
         assert content['is_deleted'] is False
796
         assert content['is_deleted'] is False
799
         assert content['show_in_ui'] is True
799
         assert content['show_in_ui'] is True
800
         assert content['slug'] == 'new-fruit-salad'
800
         assert content['slug'] == 'new-fruit-salad'
801
         assert content['status'] == 'open'
801
         assert content['status'] == 'open'
802
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
802
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
803
         assert content['workspace_id'] == 3
803
         assert content['workspace_id'] == 3
804
 
804
 
805
         content = res[2]
805
         content = res[2]
806
-        assert content['content_type'] == 'html-documents'
806
+        assert content['content_type'] == 'html-document'
807
         assert content['content_id'] == 16
807
         assert content['content_id'] == 16
808
         assert content['is_archived'] is True
808
         assert content['is_archived'] is True
809
         assert content['is_deleted'] is False
809
         assert content['is_deleted'] is False
812
         assert content['show_in_ui'] is True
812
         assert content['show_in_ui'] is True
813
         assert content['slug'].startswith('fruit-salad')
813
         assert content['slug'].startswith('fruit-salad')
814
         assert content['status'] == 'open'
814
         assert content['status'] == 'open'
815
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
815
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
816
         assert content['workspace_id'] == 3
816
         assert content['workspace_id'] == 3
817
 
817
 
818
         content = res[3]
818
         content = res[3]
819
-        assert content['content_type'] == 'html-documents'
819
+        assert content['content_type'] == 'html-document'
820
         assert content['content_id'] == 17
820
         assert content['content_id'] == 17
821
         assert content['is_archived'] is False
821
         assert content['is_archived'] is False
822
         assert content['is_deleted'] is True
822
         assert content['is_deleted'] is True
825
         assert content['show_in_ui'] is True
825
         assert content['show_in_ui'] is True
826
         assert content['slug'].startswith('bad-fruit-salad')
826
         assert content['slug'].startswith('bad-fruit-salad')
827
         assert content['status'] == 'open'
827
         assert content['status'] == 'open'
828
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
828
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
829
         assert content['workspace_id'] == 3
829
         assert content['workspace_id'] == 3
830
 
830
 
831
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):  # nopep8
831
     def test_api__get_workspace_content__ok_200__get_only_active_root_content(self):  # nopep8
853
         # TODO - G.M - 30-05-2018 - Check this test
853
         # TODO - G.M - 30-05-2018 - Check this test
854
         assert len(res) == 2
854
         assert len(res) == 2
855
         content = res[1]
855
         content = res[1]
856
-        assert content['content_type'] == 'html-documents'
856
+        assert content['content_type'] == 'html-document'
857
         assert content['content_id'] == 15
857
         assert content['content_id'] == 15
858
         assert content['is_archived'] is False
858
         assert content['is_archived'] is False
859
         assert content['is_deleted'] is False
859
         assert content['is_deleted'] is False
862
         assert content['show_in_ui'] is True
862
         assert content['show_in_ui'] is True
863
         assert content['slug'] == 'new-fruit-salad'
863
         assert content['slug'] == 'new-fruit-salad'
864
         assert content['status'] == 'open'
864
         assert content['status'] == 'open'
865
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
865
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
866
         assert content['workspace_id'] == 3
866
         assert content['workspace_id'] == 3
867
 
867
 
868
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):  # nopep8
868
     def test_api__get_workspace_content__ok_200__get_only_archived_root_content(self):  # nopep8
889
         ).json_body   # nopep8
889
         ).json_body   # nopep8
890
         assert len(res) == 1
890
         assert len(res) == 1
891
         content = res[0]
891
         content = res[0]
892
-        assert content['content_type'] == 'html-documents'
892
+        assert content['content_type'] == 'html-document'
893
         assert content['content_id'] == 16
893
         assert content['content_id'] == 16
894
         assert content['is_archived'] is True
894
         assert content['is_archived'] is True
895
         assert content['is_deleted'] is False
895
         assert content['is_deleted'] is False
898
         assert content['show_in_ui'] is True
898
         assert content['show_in_ui'] is True
899
         assert content['slug'].startswith('fruit-salad')
899
         assert content['slug'].startswith('fruit-salad')
900
         assert content['status'] == 'open'
900
         assert content['status'] == 'open'
901
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
901
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
902
         assert content['workspace_id'] == 3
902
         assert content['workspace_id'] == 3
903
 
903
 
904
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):  # nopep8
904
     def test_api__get_workspace_content__ok_200__get_only_deleted_root_content(self):  # nopep8
927
 
927
 
928
         assert len(res) == 1
928
         assert len(res) == 1
929
         content = res[0]
929
         content = res[0]
930
-        assert content['content_type'] == 'html-documents'
930
+        assert content['content_type'] == 'html-document'
931
         assert content['content_id'] == 17
931
         assert content['content_id'] == 17
932
         assert content['is_archived'] is False
932
         assert content['is_archived'] is False
933
         assert content['is_deleted'] is True
933
         assert content['is_deleted'] is True
936
         assert content['show_in_ui'] is True
936
         assert content['show_in_ui'] is True
937
         assert content['slug'].startswith('bad-fruit-salad')
937
         assert content['slug'].startswith('bad-fruit-salad')
938
         assert content['status'] == 'open'
938
         assert content['status'] == 'open'
939
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
939
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
940
         assert content['workspace_id'] == 3
940
         assert content['workspace_id'] == 3
941
 
941
 
942
     def test_api__get_workspace_content__ok_200__get_nothing_root_content(self):
942
     def test_api__get_workspace_content__ok_200__get_nothing_root_content(self):
1058
         assert content['show_in_ui'] is True
1058
         assert content['show_in_ui'] is True
1059
         assert content['slug'] == 'test-thread'
1059
         assert content['slug'] == 'test-thread'
1060
         assert content['status'] == 'open'
1060
         assert content['status'] == 'open'
1061
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1061
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1062
         assert content['workspace_id'] == 1
1062
         assert content['workspace_id'] == 1
1063
 
1063
 
1064
     def test_api__get_workspace_content__ok_200__get_all_filter_content_html_and_legacy_page(self):  # nopep8
1064
     def test_api__get_workspace_content__ok_200__get_all_filter_content_html_and_legacy_page(self):  # nopep8
1130
             'show_archived': 1,
1130
             'show_archived': 1,
1131
             'show_deleted': 1,
1131
             'show_deleted': 1,
1132
             'show_active': 1,
1132
             'show_active': 1,
1133
-            'content_type': 'html-documents',
1133
+            'content_type': 'html-document',
1134
         }
1134
         }
1135
         self.testapp.authorization = (
1135
         self.testapp.authorization = (
1136
             'Basic',
1136
             'Basic',
1146
         ).json_body
1146
         ).json_body
1147
         assert len(res) == 2
1147
         assert len(res) == 2
1148
         content = res[0]
1148
         content = res[0]
1149
-        assert content['content_type'] == 'html-documents'
1149
+        assert content['content_type'] == 'html-document'
1150
         assert content['content_id']
1150
         assert content['content_id']
1151
         assert content['is_archived'] is False
1151
         assert content['is_archived'] is False
1152
         assert content['is_deleted'] is False
1152
         assert content['is_deleted'] is False
1155
         assert content['show_in_ui'] is True
1155
         assert content['show_in_ui'] is True
1156
         assert content['slug'] == 'test-page'
1156
         assert content['slug'] == 'test-page'
1157
         assert content['status'] == 'open'
1157
         assert content['status'] == 'open'
1158
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1158
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1159
         assert content['workspace_id'] == 1
1159
         assert content['workspace_id'] == 1
1160
         content = res[1]
1160
         content = res[1]
1161
-        assert content['content_type'] == 'html-documents'
1161
+        assert content['content_type'] == 'html-document'
1162
         assert content['content_id']
1162
         assert content['content_id']
1163
         assert content['is_archived'] is False
1163
         assert content['is_archived'] is False
1164
         assert content['is_deleted'] is False
1164
         assert content['is_deleted'] is False
1167
         assert content['show_in_ui'] is True
1167
         assert content['show_in_ui'] is True
1168
         assert content['slug'] == 'test-html-page'
1168
         assert content['slug'] == 'test-html-page'
1169
         assert content['status'] == 'open'
1169
         assert content['status'] == 'open'
1170
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1170
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1171
         assert content['workspace_id'] == 1
1171
         assert content['workspace_id'] == 1
1172
         assert res[0]['content_id'] != res[1]['content_id']
1172
         assert res[0]['content_id'] != res[1]['content_id']
1173
 
1173
 
1196
         ).json_body   # nopep8
1196
         ).json_body   # nopep8
1197
         assert len(res) == 3
1197
         assert len(res) == 3
1198
         content = res[0]
1198
         content = res[0]
1199
-        assert content['content_type'] == 'html-documents'
1199
+        assert content['content_type'] == 'html-document'
1200
         assert content['content_id'] == 12
1200
         assert content['content_id'] == 12
1201
         assert content['is_archived'] is False
1201
         assert content['is_archived'] is False
1202
         assert content['is_deleted'] is False
1202
         assert content['is_deleted'] is False
1205
         assert content['show_in_ui'] is True
1205
         assert content['show_in_ui'] is True
1206
         assert content['slug'] == 'new-fruit-salad'
1206
         assert content['slug'] == 'new-fruit-salad'
1207
         assert content['status'] == 'open'
1207
         assert content['status'] == 'open'
1208
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1208
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1209
         assert content['workspace_id'] == 2
1209
         assert content['workspace_id'] == 2
1210
 
1210
 
1211
         content = res[1]
1211
         content = res[1]
1212
-        assert content['content_type'] == 'html-documents'
1212
+        assert content['content_type'] == 'html-document'
1213
         assert content['content_id'] == 13
1213
         assert content['content_id'] == 13
1214
         assert content['is_archived'] is True
1214
         assert content['is_archived'] is True
1215
         assert content['is_deleted'] is False
1215
         assert content['is_deleted'] is False
1218
         assert content['show_in_ui'] is True
1218
         assert content['show_in_ui'] is True
1219
         assert content['slug'].startswith('fruit-salad')
1219
         assert content['slug'].startswith('fruit-salad')
1220
         assert content['status'] == 'open'
1220
         assert content['status'] == 'open'
1221
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1221
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1222
         assert content['workspace_id'] == 2
1222
         assert content['workspace_id'] == 2
1223
 
1223
 
1224
         content = res[2]
1224
         content = res[2]
1225
-        assert content['content_type'] == 'html-documents'
1225
+        assert content['content_type'] == 'html-document'
1226
         assert content['content_id'] == 14
1226
         assert content['content_id'] == 14
1227
         assert content['is_archived'] is False
1227
         assert content['is_archived'] is False
1228
         assert content['is_deleted'] is True
1228
         assert content['is_deleted'] is True
1231
         assert content['show_in_ui'] is True
1231
         assert content['show_in_ui'] is True
1232
         assert content['slug'].startswith('bad-fruit-salad')
1232
         assert content['slug'].startswith('bad-fruit-salad')
1233
         assert content['status'] == 'open'
1233
         assert content['status'] == 'open'
1234
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1234
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1235
         assert content['workspace_id'] == 2
1235
         assert content['workspace_id'] == 2
1236
 
1236
 
1237
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):  # nopep8
1237
     def test_api__get_workspace_content__ok_200__get_only_active_folder_content(self):  # nopep8
1267
         assert content['show_in_ui'] is True
1267
         assert content['show_in_ui'] is True
1268
         assert content['slug'] == 'new-fruit-salad'
1268
         assert content['slug'] == 'new-fruit-salad'
1269
         assert content['status'] == 'open'
1269
         assert content['status'] == 'open'
1270
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1270
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1271
         assert content['workspace_id'] == 2
1271
         assert content['workspace_id'] == 2
1272
 
1272
 
1273
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):  # nopep8
1273
     def test_api__get_workspace_content__ok_200__get_only_archived_folder_content(self):  # nopep8
1294
         ).json_body   # nopep8
1294
         ).json_body   # nopep8
1295
         assert len(res) == 1
1295
         assert len(res) == 1
1296
         content = res[0]
1296
         content = res[0]
1297
-        assert content['content_type'] == 'html-documents'
1297
+        assert content['content_type'] == 'html-document'
1298
         assert content['content_id'] == 13
1298
         assert content['content_id'] == 13
1299
         assert content['is_archived'] is True
1299
         assert content['is_archived'] is True
1300
         assert content['is_deleted'] is False
1300
         assert content['is_deleted'] is False
1303
         assert content['show_in_ui'] is True
1303
         assert content['show_in_ui'] is True
1304
         assert content['slug'].startswith('fruit-salad')
1304
         assert content['slug'].startswith('fruit-salad')
1305
         assert content['status'] == 'open'
1305
         assert content['status'] == 'open'
1306
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1306
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1307
         assert content['workspace_id'] == 2
1307
         assert content['workspace_id'] == 2
1308
 
1308
 
1309
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):  # nopep8
1309
     def test_api__get_workspace_content__ok_200__get_only_deleted_folder_content(self):  # nopep8
1331
 
1331
 
1332
         assert len(res) == 1
1332
         assert len(res) == 1
1333
         content = res[0]
1333
         content = res[0]
1334
-        assert content['content_type'] == 'html-documents'
1334
+        assert content['content_type'] == 'html-document'
1335
         assert content['content_id'] == 14
1335
         assert content['content_id'] == 14
1336
         assert content['is_archived'] is False
1336
         assert content['is_archived'] is False
1337
         assert content['is_deleted'] is True
1337
         assert content['is_deleted'] is True
1340
         assert content['show_in_ui'] is True
1340
         assert content['show_in_ui'] is True
1341
         assert content['slug'].startswith('bad-fruit-salad')
1341
         assert content['slug'].startswith('bad-fruit-salad')
1342
         assert content['status'] == 'open'
1342
         assert content['status'] == 'open'
1343
-        assert set(content['sub_content_types']) == {'thread', 'html-documents', 'folder', 'file'}  # nopep8
1343
+        assert set(content['sub_content_types']) == {'thread', 'html-document', 'folder', 'file'}  # nopep8
1344
         assert content['workspace_id'] == 2
1344
         assert content['workspace_id'] == 2
1345
 
1345
 
1346
     def test_api__get_workspace_content__ok_200__get_nothing_folder_content(self):  # nopep8
1346
     def test_api__get_workspace_content__ok_200__get_nothing_folder_content(self):  # nopep8
1436
             )
1436
             )
1437
         )
1437
         )
1438
         params = {
1438
         params = {
1439
+            'parent_id': None,
1439
             'label': 'GenericCreatedContent',
1440
             'label': 'GenericCreatedContent',
1440
             'content_type': 'markdownpage',
1441
             'content_type': 'markdownpage',
1441
         }
1442
         }
1466
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1467
         active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1467
         assert res.json_body in active_contents
1468
         assert res.json_body in active_contents
1468
 
1469
 
1470
+    def test_api__post_content_create_generic_content__ok_200__no_parent_id_param(self) -> None:  # nopep8
1471
+        """
1472
+        Create generic content
1473
+        """
1474
+        self.testapp.authorization = (
1475
+            'Basic',
1476
+            (
1477
+                'admin@admin.admin',
1478
+                'admin@admin.admin'
1479
+            )
1480
+        )
1481
+        params = {
1482
+            'label': 'GenericCreatedContent',
1483
+            'content_type': 'markdownpage',
1484
+        }
1485
+        res = self.testapp.post_json(
1486
+            '/api/v2/workspaces/1/contents',
1487
+            params=params,
1488
+            status=200
1489
+        )
1490
+        assert res
1491
+        assert res.json_body
1492
+        assert res.json_body['status'] == 'open'
1493
+        assert res.json_body['content_id']
1494
+        assert res.json_body['content_type'] == 'markdownpage'
1495
+        assert res.json_body['is_archived'] is False
1496
+        assert res.json_body['is_deleted'] is False
1497
+        assert res.json_body['workspace_id'] == 1
1498
+        assert res.json_body['slug'] == 'genericcreatedcontent'
1499
+        assert res.json_body['parent_id'] is None
1500
+        assert res.json_body['show_in_ui'] is True
1501
+        assert res.json_body['sub_content_types']
1502
+        params_active = {
1503
+            'parent_id': 0,
1504
+            'show_archived': 0,
1505
+            'show_deleted': 0,
1506
+            'show_active': 1,
1507
+        }
1508
+        # INFO - G.M - 2018-06-165 - Verify if new content is correctly created
1509
+        active_contents = self.testapp.get('/api/v2/workspaces/1/contents', params=params_active, status=200).json_body  # nopep8
1510
+        assert res.json_body in active_contents
1511
+
1512
+    def test_api__post_content_create_generic_content__err_400__parent_id_0(self) -> None:  # nopep8
1513
+        """
1514
+        Create generic content
1515
+        """
1516
+        self.testapp.authorization = (
1517
+            'Basic',
1518
+            (
1519
+                'admin@admin.admin',
1520
+                'admin@admin.admin'
1521
+            )
1522
+        )
1523
+        params = {
1524
+            'parent_id': 0,
1525
+            'label': 'GenericCreatedContent',
1526
+            'content_type': 'markdownpage',
1527
+        }
1528
+        res = self.testapp.post_json(
1529
+            '/api/v2/workspaces/1/contents',
1530
+            params=params,
1531
+            status=400
1532
+        )
1533
+
1469
     def test_api__post_content_create_generic_content__ok_200__in_folder(self) -> None:  # nopep8
1534
     def test_api__post_content_create_generic_content__ok_200__in_folder(self) -> None:  # nopep8
1470
         """
1535
         """
1471
         Create generic content in folder
1536
         Create generic content in folder

+ 9 - 5
backend/tracim_backend/views/core_api/schemas.py View File

645
         description='Title of the content to create'
645
         description='Title of the content to create'
646
     )
646
     )
647
     content_type = marshmallow.fields.String(
647
     content_type = marshmallow.fields.String(
648
-        example='html-documents',
649
-        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),  # nopep8
648
+        example='html-document',
649
+        validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
650
     )
650
     )
651
     parent_id = marshmallow.fields.Integer(
651
     parent_id = marshmallow.fields.Integer(
652
         example=35,
652
         example=35,
653
-        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.'
653
+        description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.', # nopep8
654
+        allow_none=True,
655
+        default=None,
656
+        validate=Range(min=1, error="Value must be positive"),
654
     )
657
     )
655
 
658
 
656
 
659
 
657
     @post_load
660
     @post_load
658
-    def make_content_filter(self, data):
661
+    def make_content_creation(self, data):
659
         return ContentCreation(**data)
662
         return ContentCreation(**data)
660
 
663
 
661
 
664
 
677
     )
680
     )
678
     label = marshmallow.fields.Str(example='Intervention Report 12')
681
     label = marshmallow.fields.Str(example='Intervention Report 12')
679
     content_type = marshmallow.fields.Str(
682
     content_type = marshmallow.fields.Str(
680
-        example='html-documents',
683
+        example='html-document',
681
         validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
684
         validate=OneOf(CONTENT_TYPES.endpoint_allowed_types_slug()),
682
     )
685
     )
683
     sub_content_types = marshmallow.fields.List(
686
     sub_content_types = marshmallow.fields.List(
716
 # Content
719
 # Content
717
 #####
720
 #####
718
 
721
 
722
+
719
 class ContentSchema(ContentDigestSchema):
723
 class ContentSchema(ContentDigestSchema):
720
     current_revision_id = marshmallow.fields.Int(example=12)
724
     current_revision_id = marshmallow.fields.Int(example=12)
721
     created = marshmallow.fields.DateTime(
725
     created = marshmallow.fields.DateTime(

+ 23 - 0
build_full_frontend.sh View File

9
 
9
 
10
 echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}it also assumes your webpack dev server of frontend is running"
10
 echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}it also assumes your webpack dev server of frontend is running"
11
 
11
 
12
+# Tracim Lib
13
+
12
 log "cd frontend_lib"
14
 log "cd frontend_lib"
13
 cd frontend_lib
15
 cd frontend_lib
14
 log "npm run buildtracimlib$windoz"
16
 log "npm run buildtracimlib$windoz"
15
 npm run buildtracimlib$windoz
17
 npm run buildtracimlib$windoz
16
 cd -
18
 cd -
17
 
19
 
20
+# app Html Document
21
+
18
 log "cd frontend_app_html-document"
22
 log "cd frontend_app_html-document"
19
 cd frontend_app_html-document
23
 cd frontend_app_html-document
24
+
20
 log "npm run build$windoz # for frontend_app_html-document"
25
 log "npm run build$windoz # for frontend_app_html-document"
21
 npm run build$windoz
26
 npm run build$windoz
27
+
22
 log "cp dist/html-document.app.js"
28
 log "cp dist/html-document.app.js"
23
 cp dist/html-document.app.js ../frontend/dist/app
29
 cp dist/html-document.app.js ../frontend/dist/app
30
+
31
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/tml-document_en_translation.json"
32
+cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
33
+
34
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
35
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json
24
 cd -
36
 cd -
25
 
37
 
38
+# app Thread
39
+
26
 log "cd frontend_app_thread"
40
 log "cd frontend_app_thread"
27
 cd frontend_app_thread
41
 cd frontend_app_thread
42
+
28
 log "npm run build$windoz # for frontend_app_thread"
43
 log "npm run build$windoz # for frontend_app_thread"
29
 npm run build$windoz
44
 npm run build$windoz
45
+
30
 log "cp dist/thread.app.js"
46
 log "cp dist/thread.app.js"
31
 cp dist/thread.app.js ../frontend/dist/app
47
 cp dist/thread.app.js ../frontend/dist/app
48
+
49
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
50
+cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
51
+
52
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
53
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json
54
+cd -

+ 1 - 1
frontend/dist/appInterface.js View File

3
 
3
 
4
   getSelectedApp = name => {
4
   getSelectedApp = name => {
5
     switch (name) {
5
     switch (name) {
6
-      case 'html-documents':
6
+      case 'html-document':
7
         return appHtmlDocument
7
         return appHtmlDocument
8
       case 'thread':
8
       case 'thread':
9
         return appThread
9
         return appThread

+ 16 - 0
frontend/i18next.scanner.js View File

1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// this script is run by npm run build-translation and generate i18next.scanner/**/*.json
7
+
8
+// --------------------
9
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
10
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
11
+// --------------------
12
+
13
+vfs.src(['./src/**/*.jsx'])
14
+  // .pipe(sort()) // Sort files in stream by path
15
+  .pipe(scanner(option))
16
+  .pipe(vfs.dest('./i18next.scanner'))

+ 23 - 0
frontend/i18next.scanner/en/translation.json View File

1
+{
2
+  "Create your own collaborative workspaces on trac.im": "Create your own collaborative workspaces on trac.im",
3
+  "Copyright 2013 - 2017": "Copyright 2013 - 2017",
4
+  "Disconnection error": "Disconnection error",
5
+  "Error": "Error",
6
+  "Email or password invalid": "Email or password invalid",
7
+  "Type": "Type",
8
+  "Status": "Status",
9
+  "Create in folder...": "Create in folder...",
10
+  "Search...": "Search...",
11
+  "Create a workspace": "Create a workspace",
12
+  "Title": "Title",
13
+  "Change": "Change",
14
+  "Move": "Move",
15
+  "Download": "Download",
16
+  "Archive": "Archive",
17
+  "Delete": "Delete",
18
+  "My Account": "My Account",
19
+  "Logout": "Logout",
20
+  "Notification": "Notification",
21
+  "Archive Topic": "Archive Topic",
22
+  "Deleted File": "Deleted File"
23
+}

+ 23 - 0
frontend/i18next.scanner/fr/translation.json View File

1
+{
2
+  "Create your own collaborative workspaces on trac.im": "Créer votre propre espace de travail sur trac.im",
3
+  "Copyright 2013 - 2017": "Copyright 2013 - 2017",
4
+  "Disconnection error": "Erreur de déconnexion",
5
+  "Error": "Erreur",
6
+  "Email or password invalid": "Email ou mot de passe invalid",
7
+  "Type": "Type",
8
+  "Status": "Statut",
9
+  "Create in folder...": "Créer dans le dossier ...",
10
+  "Search...": "Rechercher ...",
11
+  "Create a workspace": "Créer un espace de travail",
12
+  "Title": "Titre",
13
+  "Change": "Modifier",
14
+  "Move": "Déplacer",
15
+  "Download": "Télécharger",
16
+  "Archive": "Archiver",
17
+  "Delete": "Supprimer",
18
+  "My Account": "Mon Compte",
19
+  "Logout": "Se Déconnecter",
20
+  "Notification": "Notification",
21
+  "Archive Topic": "Conversation archivée",
22
+  "Deleted File": "Fichier supprimé"
23
+}

+ 2 - 0
frontend/package.json View File

10
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -- webpack-dev-server --watch --colors --inline --hot --progress",
10
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -- webpack-dev-server --watch --colors --inline --hot --progress",
11
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
11
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
12
     "build": "NODE_ENV=production webpack -p",
12
     "build": "NODE_ENV=production webpack -p",
13
+    "build-translation": "node i18next.scanner.js",
13
     "test": "echo \"Error: no test specified\" && exit 1"
14
     "test": "echo \"Error: no test specified\" && exit 1"
14
   },
15
   },
15
   "author": "",
16
   "author": "",
52
     "whatwg-fetch": "^2.0.3"
53
     "whatwg-fetch": "^2.0.3"
53
   },
54
   },
54
   "devDependencies": {
55
   "devDependencies": {
56
+    "i18next-scanner": "^2.6.1",
55
     "json-server": "^0.12.0",
57
     "json-server": "^0.12.0",
56
     "webpack-dashboard": "^1.0.2",
58
     "webpack-dashboard": "^1.0.2",
57
     "webpack-dev-server": "^2.9.2"
59
     "webpack-dev-server": "^2.9.2"

+ 24 - 17
frontend/src/action-creator.sync.js View File

1
+export const SET = 'Set'
2
+export const UPDATE = 'Update'
3
+export const ADD = 'Add'
4
+export const REMOVE = 'Remove'
5
+
1
 export const TIMEZONE = 'Timezone'
6
 export const TIMEZONE = 'Timezone'
2
-export const setTimezone = timezone => ({ type: `Set/${TIMEZONE}`, timezone })
7
+export const setTimezone = timezone => ({ type: `${SET}/${TIMEZONE}`, timezone })
3
 
8
 
4
 export const FLASH_MESSAGE = 'FlashMessage'
9
 export const FLASH_MESSAGE = 'FlashMessage'
5
 export const newFlashMessage = (msgText = '', msgType = 'info', msgDelay = 5000) => dispatch => {
10
 export const newFlashMessage = (msgText = '', msgType = 'info', msgDelay = 5000) => dispatch => {
6
   msgDelay !== 0 && window.setTimeout(() => dispatch(removeFlashMessage(msgText)), msgDelay)
11
   msgDelay !== 0 && window.setTimeout(() => dispatch(removeFlashMessage(msgText)), msgDelay)
7
   return dispatch(addFlashMessage({message: msgText, type: msgType}))
12
   return dispatch(addFlashMessage({message: msgText, type: msgType}))
8
 }
13
 }
9
-export const addFlashMessage = msg => ({ type: `Add/${FLASH_MESSAGE}`, msg })
10
-export const removeFlashMessage = msg => ({ type: `Remove/${FLASH_MESSAGE}`, msg })
14
+export const addFlashMessage = msg => ({ type: `${ADD}/${FLASH_MESSAGE}`, msg })
15
+export const removeFlashMessage = msg => ({ type: `${REMOVE}/${FLASH_MESSAGE}`, msg })
11
 
16
 
17
+export const USER = 'User'
12
 export const USER_LOGIN = 'User/Login'
18
 export const USER_LOGIN = 'User/Login'
13
 export const USER_LOGOUT = 'User/Logout'
19
 export const USER_LOGOUT = 'User/Logout'
14
 export const USER_DATA = 'User/Data'
20
 export const USER_DATA = 'User/Data'
15
 export const USER_ROLE = 'User/Role'
21
 export const USER_ROLE = 'User/Role'
16
 export const USER_CONNECTED = 'User/Connected'
22
 export const USER_CONNECTED = 'User/Connected'
17
 export const USER_DISCONNECTED = 'User/Disconnected'
23
 export const USER_DISCONNECTED = 'User/Disconnected'
18
-export const setUserConnected = user => ({ type: `Set/${USER_CONNECTED}`, user })
19
-export const setUserDisconnected = () => ({ type: `Set/${USER_DISCONNECTED}` })
20
-export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })
21
-export const setUserRole = userRole => ({ type: `Set/${USER_ROLE}`, userRole }) // this actually update workspaceList state
24
+export const USER_LANG = 'User/Lang'
25
+export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
26
+export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
27
+export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
28
+export const setUserRole = userRole => ({ type: `${SET}/${USER}/Role`, userRole }) // this actually update workspaceList state
29
+export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
22
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
30
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
23
-  ({ type: `Update/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
31
+  ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
24
 
32
 
25
 export const WORKSPACE = 'Workspace'
33
 export const WORKSPACE = 'Workspace'
26
-export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `Set/${WORKSPACE}/Content`, workspaceContent, filterStr })
27
-export const updateWorkspaceFilter = filterList => ({ type: `Update/${WORKSPACE}/Filter`, filterList })
34
+export const setWorkspaceContent = (workspaceContent, filterStr = '') => ({ type: `${SET}/${WORKSPACE}/Content`, workspaceContent, filterStr })
35
+export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
28
 
36
 
29
 export const FOLDER = 'Folder'
37
 export const FOLDER = 'Folder'
30
-export const setFolderData = (folderId, content) => ({ type: `Set/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
38
+export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
31
 
39
 
32
 export const WORKSPACE_LIST = 'WorkspaceList'
40
 export const WORKSPACE_LIST = 'WorkspaceList'
33
-export const updateWorkspaceListData = workspaceList => ({ type: `Update/${WORKSPACE_LIST}`, workspaceList })
34
-export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `Set/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
41
+export const updateWorkspaceListData = workspaceList => ({ type: `${UPDATE}/${WORKSPACE_LIST}`, workspaceList })
42
+export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
35
 
43
 
36
 export const APP_LIST = 'App/List'
44
 export const APP_LIST = 'App/List'
37
-export const setAppList = appList => ({ type: `Set/${APP_LIST}`, appList })
45
+export const setAppList = appList => ({ type: `${SET}/${APP_LIST}`, appList })
38
 
46
 
39
 export const CONTENT_TYPE_LIST = 'ContentType/List'
47
 export const CONTENT_TYPE_LIST = 'ContentType/List'
40
-export const setContentTypeList = contentTypeList => ({ type: `Set/${CONTENT_TYPE_LIST}`, contentTypeList })
48
+export const setContentTypeList = contentTypeList => ({ type: `${SET}/${CONTENT_TYPE_LIST}`, contentTypeList })
41
 
49
 
42
 export const LANG = 'Lang'
50
 export const LANG = 'Lang'
43
-export const updateLangList = langList => ({ type: `Update/${LANG}`, langList })
44
-export const setLangActive = langId => ({ type: `Set/${LANG}/Active`, langId })
51
+export const updateLangList = langList => ({ type: `${UPDATE}/${LANG}`, langList })

+ 5 - 2
frontend/src/appFactory.js View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { FETCH_CONFIG } from './helper.js'
2
 import { FETCH_CONFIG } from './helper.js'
3
+import i18n from './i18n.js'
3
 
4
 
4
 export function appFactory (WrappedComponent) {
5
 export function appFactory (WrappedComponent) {
5
   return class AppFactory extends React.Component {
6
   return class AppFactory extends React.Component {
10
         domContainer: 'appContainer',
11
         domContainer: 'appContainer',
11
         apiUrl: FETCH_CONFIG.apiUrl,
12
         apiUrl: FETCH_CONFIG.apiUrl,
12
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
13
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
13
-        apiHeader: FETCH_CONFIG.headers
14
+        apiHeader: FETCH_CONFIG.headers,
15
+        translation: i18n.store.data
14
       },
16
       },
15
       content
17
       content
16
     })
18
     })
22
         domContainer: 'popupCreateContentContainer',
24
         domContainer: 'popupCreateContentContainer',
23
         apiUrl: FETCH_CONFIG.apiUrl,
25
         apiUrl: FETCH_CONFIG.apiUrl,
24
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
26
         mockApiUrl: FETCH_CONFIG.mockApiUrl,
25
-        apiHeader: FETCH_CONFIG.headers // should this be used by app ? right now, apps have their own headers
27
+        apiHeader: FETCH_CONFIG.headers, // should this be used by app ? right now, apps have their own headers
28
+        translation: i18n.store.data
26
       },
29
       },
27
       idWorkspace,
30
       idWorkspace,
28
       idFolder: idFolder === 'null' ? null : idFolder
31
       idFolder: idFolder === 'null' ? null : idFolder

+ 1 - 1
frontend/src/component/FlashMessage.jsx View File

22
 
22
 
23
               <div className='flashmessage__container__content__text'>
23
               <div className='flashmessage__container__content__text'>
24
                 <div className='flashmessage__container__content__text__title'>
24
                 <div className='flashmessage__container__content__text__title'>
25
-                  {props.t('FlashMessage.error')}
25
+                  {props.t('Error')}
26
                 </div>
26
                 </div>
27
                 <div className='flashmessage__container__content__text__paragraph'>
27
                 <div className='flashmessage__container__content__text__paragraph'>
28
                   {props.flashMessage[0].message}
28
                   {props.flashMessage[0].message}

+ 1 - 1
frontend/src/component/Footer.jsx View File

7
   return (
7
   return (
8
     <footer className='footer text-right'>
8
     <footer className='footer text-right'>
9
       <div className='footer__text'>
9
       <div className='footer__text'>
10
-        {t('Footer.marketing_msg')} - {t('Footer.copyright')}
10
+        {t('Create your own collaborative workspaces on trac.im')} - {t('Copyright 2013 - 2017')}
11
       </div>
11
       </div>
12
       <img className='footer__logo' src={logoFooter} />
12
       <img className='footer__logo' src={logoFooter} />
13
     </footer>
13
     </footer>

+ 4 - 4
frontend/src/component/Header/MenuActionListItem/DropdownLang.jsx View File

2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
 
3
 
4
 const DropdownLang = props => {
4
 const DropdownLang = props => {
5
-  const activeLang = props.langList.find(l => l.active) || {id: 'fr', name: 'Français', src: '', active: true}
6
   return (
5
   return (
7
     <li className='header__menu__rightside__itemlanguage'>
6
     <li className='header__menu__rightside__itemlanguage'>
8
       <div className='header__menu__rightside__itemlanguage__languagedropdown dropdown'>
7
       <div className='header__menu__rightside__itemlanguage__languagedropdown dropdown'>
14
           aria-haspopup='true'
13
           aria-haspopup='true'
15
           aria-expanded='false'
14
           aria-expanded='false'
16
         >
15
         >
17
-          <img className='languagedropdown__btnlanguage__imgselected' src={activeLang.src} />
16
+          <img className='languagedropdown__btnlanguage__imgselected' src={props.langList.find(l => l.id === props.idLangActive).icon} />
18
         </button>
17
         </button>
19
         <div className='languagedropdown__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
18
         <div className='languagedropdown__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
20
-          { props.langList.map((l, i) => l.active === false &&
19
+          { props.langList.filter(l => l.id !== props.idLangActive).map((l, i) =>
21
             <div className='subdropdown__link dropdown-item' onClick={() => props.onChangeLang(l.id)} key={i}>
20
             <div className='subdropdown__link dropdown-item' onClick={() => props.onChangeLang(l.id)} key={i}>
22
-              <img className='subdropdown__flag' src={l.src} />
21
+              <img className='subdropdown__flag' src={l.icon} />
23
             </div>
22
             </div>
24
           )}
23
           )}
25
         </div>
24
         </div>
31
 
30
 
32
 DropdownLang.propTypes = {
31
 DropdownLang.propTypes = {
33
   langList: PropTypes.array.isRequired,
32
   langList: PropTypes.array.isRequired,
33
+  idLangActive: PropTypes.string.isRequired,
34
   onChangeLang: PropTypes.func.isRequired
34
   onChangeLang: PropTypes.func.isRequired
35
 }
35
 }

+ 4 - 3
frontend/src/component/Header/MenuActionListItem/MenuProfil.jsx View File

2
 import { Link } from 'react-router-dom'
2
 import { Link } from 'react-router-dom'
3
 import PropTypes from 'prop-types'
3
 import PropTypes from 'prop-types'
4
 import { PAGE } from '../../../helper.js'
4
 import { PAGE } from '../../../helper.js'
5
+import { translate } from 'react-i18next'
5
 
6
 
6
 const MenuProfil = props => {
7
 const MenuProfil = props => {
7
   return props.user.logged
8
   return props.user.logged
17
           <div className='profilgroup__setting dropdown-menu' aria-labelledby='dropdownMenuButton'>
18
           <div className='profilgroup__setting dropdown-menu' aria-labelledby='dropdownMenuButton'>
18
             <Link className='setting__link dropdown-item' to={PAGE.ACCOUNT}>
19
             <Link className='setting__link dropdown-item' to={PAGE.ACCOUNT}>
19
               <i className='fa fa-fw fa-user-o mr-2' />
20
               <i className='fa fa-fw fa-user-o mr-2' />
20
-              Mon compte
21
+              {props.t('My Account')}
21
             </Link>
22
             </Link>
22
             {/* <div className='setting__link dropdown-item'>Mot de passe</div> */}
23
             {/* <div className='setting__link dropdown-item'>Mot de passe</div> */}
23
             <div className='setting__link dropdown-item' onClick={props.onClickLogout}>
24
             <div className='setting__link dropdown-item' onClick={props.onClickLogout}>
24
               <i className='fa fa-fw fa-sign-out mr-2' />
25
               <i className='fa fa-fw fa-sign-out mr-2' />
25
-              Se déconnecter
26
+              {props.t('Logout')}
26
             </div>
27
             </div>
27
           </div>
28
           </div>
28
         </div>
29
         </div>
30
     )
31
     )
31
     : ''
32
     : ''
32
 }
33
 }
33
-export default MenuProfil
34
+export default translate()(MenuProfil)
34
 
35
 
35
 MenuProfil.propTypes = {
36
 MenuProfil.propTypes = {
36
   user: PropTypes.object.isRequired,
37
   user: PropTypes.object.isRequired,

+ 5 - 4
frontend/src/component/Header/MenuActionListItem/Notification.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2
 // import PropTypes from 'prop-types'
3
 // import PropTypes from 'prop-types'
3
 
4
 
4
 const Notification = props => {
5
 const Notification = props => {
13
           aria-haspopup='true'
14
           aria-haspopup='true'
14
           aria-expanded='false'
15
           aria-expanded='false'
15
         >
16
         >
16
-          Notification
17
+          {props.t('Notification')}
17
         </button>
18
         </button>
18
         <div className='timeline__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
19
         <div className='timeline__subdropdown dropdown-menu' aria-labelledby='headerDropdownMenuButton'>
19
           <div className='timeline__subdropdown__text dropdown-item' >
20
           <div className='timeline__subdropdown__text dropdown-item' >
20
-            Conversation archivé
21
+            {props.t('Archive Topic')}
21
           </div>
22
           </div>
22
           <div className='timeline__subdropdown__text dropdown-item' >
23
           <div className='timeline__subdropdown__text dropdown-item' >
23
-            Fichier supprimé
24
+            {props.t('Deleted File')}
24
           </div>
25
           </div>
25
         </div>
26
         </div>
26
       </div>
27
       </div>
27
     </li>
28
     </li>
28
   )
29
   )
29
 }
30
 }
30
-export default Notification
31
+export default translate()(Notification)

+ 1 - 1
frontend/src/component/Header/MenuActionListItem/Search.jsx View File

9
         <input
9
         <input
10
           type='text'
10
           type='text'
11
           className='search__input form-control'
11
           className='search__input form-control'
12
-          placeholder={`${props.t('Header.Search')}...`}
12
+          placeholder={`${props.t('Search...')}`}
13
           aria-describedby='headerInputSearch'
13
           aria-describedby='headerInputSearch'
14
           onChange={props.onChangeInput}
14
           onChange={props.onChangeInput}
15
         />
15
         />

+ 7 - 6
frontend/src/component/Workspace/BtnExtandedAction.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
+import { translate } from 'react-i18next'
3
 
4
 
4
 const ExtandedAction = props => {
5
 const ExtandedAction = props => {
5
   return (
6
   return (
22
             <i className='fa fa-fw fa-pencil' />
23
             <i className='fa fa-fw fa-pencil' />
23
           </div>
24
           </div>
24
           <div className='subdropdown__item__text'>
25
           <div className='subdropdown__item__text'>
25
-            Modifier
26
+            {props.t('Change')}
26
           </div>
27
           </div>
27
         </div>
28
         </div>
28
 
29
 
31
             <i className='fa fa-fw fa-arrows-alt' />
32
             <i className='fa fa-fw fa-arrows-alt' />
32
           </div>
33
           </div>
33
           <div className='subdropdown__item__text'>
34
           <div className='subdropdown__item__text'>
34
-            Déplacer
35
+            {props.t('Move')}
35
           </div>
36
           </div>
36
         </div>
37
         </div>
37
 
38
 
40
             <i className='fa fa-fw fa-download' />
41
             <i className='fa fa-fw fa-download' />
41
           </div>
42
           </div>
42
           <div className='subdropdown__item__text'>
43
           <div className='subdropdown__item__text'>
43
-            Télécharger
44
+            {props.t('Download')}
44
           </div>
45
           </div>
45
         </div> */ }
46
         </div> */ }
46
 
47
 
49
             <i className='fa fa-fw fa-archive' />
50
             <i className='fa fa-fw fa-archive' />
50
           </div>
51
           </div>
51
           <div className='subdropdown__item__text'>
52
           <div className='subdropdown__item__text'>
52
-            Archiver
53
+            {props.t('Archive')}
53
           </div>
54
           </div>
54
         </div>
55
         </div>
55
 
56
 
58
             <i className='fa fa-fw fa-trash-o' />
59
             <i className='fa fa-fw fa-trash-o' />
59
           </div>
60
           </div>
60
           <div className='subdropdown__item__text'>
61
           <div className='subdropdown__item__text'>
61
-            Supprimer
62
+            {props.t('Delete')}
62
           </div>
63
           </div>
63
         </div>
64
         </div>
64
 
65
 
67
   )
68
   )
68
 }
69
 }
69
 
70
 
70
-export default ExtandedAction
71
+export default translate()(ExtandedAction)
71
 
72
 
72
 ExtandedAction.propTypes = {
73
 ExtandedAction.propTypes = {
73
   onClickExtendedAction: PropTypes.object.isRequired
74
   onClickExtendedAction: PropTypes.object.isRequired

+ 3 - 3
frontend/src/component/Workspace/ContentItemHeader.jsx View File

5
   return (
5
   return (
6
     <div className='content__header'>
6
     <div className='content__header'>
7
       <div className='content__header__type'>
7
       <div className='content__header__type'>
8
-        {props.t('FileItemHeader.type')}
8
+        {props.t('Type')}
9
       </div>
9
       </div>
10
       <div className='content__header__name'>
10
       <div className='content__header__name'>
11
-        {props.t('FileItemHeader.document_name')}
11
+        {props.t('Title')}
12
       </div>
12
       </div>
13
       <div className='content__header__status'>
13
       <div className='content__header__status'>
14
-        {props.t('FileItemHeader.status')}
14
+        {props.t('Status')}
15
       </div>
15
       </div>
16
     </div>
16
     </div>
17
   )
17
   )

+ 1 - 1
frontend/src/component/Workspace/Folder.jsx View File

65
                 aria-expanded='false'
65
                 aria-expanded='false'
66
                 onClick={e => e.stopPropagation()}
66
                 onClick={e => e.stopPropagation()}
67
               >
67
               >
68
-                {t('Folder.create')} ...
68
+                {t('Create in folder...')}
69
               </button>
69
               </button>
70
 
70
 
71
               <div className='addbtn__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>
71
               <div className='addbtn__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>

+ 1 - 1
frontend/src/component/Workspace/OpenContentApp.jsx View File

22
 
22
 
23
       if (appOpenedType === contentToOpen.type) { // app already open
23
       if (appOpenedType === contentToOpen.type) { // app already open
24
         GLOBAL_dispatchEvent({
24
         GLOBAL_dispatchEvent({
25
-          type: `${contentToOpen.type}_reloadContent`, // handled by html-documents:src/container/HtmlDocument.jsx
25
+          type: `${contentToOpen.type}_reloadContent`, // handled by html-document:src/container/HtmlDocument.jsx
26
           data: contentToOpen
26
           data: contentToOpen
27
         })
27
         })
28
       } else { // open another app
28
       } else { // open another app

+ 5 - 2
frontend/src/container/Dashboard.jsx View File

414
                         </li>
414
                         </li>
415
                       </ul>
415
                       </ul>
416
 
416
 
417
-                      <div className='dashboard__memberlist__btnadd'>
417
+                      <div
418
+                        className='dashboard__memberlist__btnadd'
419
+                        onClick={this.handleToggleNewMemberDashboard}
420
+                      >
418
                         <div className='dashboard__memberlist__btnadd__button'>
421
                         <div className='dashboard__memberlist__btnadd__button'>
419
                           <div className='dashboard__memberlist__btnadd__button__avatar'>
422
                           <div className='dashboard__memberlist__btnadd__button__avatar'>
420
                             <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
423
                             <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
423
                           </div>
426
                           </div>
424
                           <div
427
                           <div
425
                             className='dashboard__memberlist__btnadd__button__text'
428
                             className='dashboard__memberlist__btnadd__button__text'
426
-                            onClick={this.handleToggleNewMemberDashboard}
429
+
427
                           >
430
                           >
428
                              Ajouter un membre
431
                              Ajouter un membre
429
                           </div>
432
                           </div>

+ 13 - 8
frontend/src/container/Header.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3
 import i18n from '../i18n.js'
4
 import i18n from '../i18n.js'
5
+import appFactory from '../appFactory.js'
4
 import { translate } from 'react-i18next'
6
 import { translate } from 'react-i18next'
5
 import Cookies from 'js-cookie'
7
 import Cookies from 'js-cookie'
6
 import Logo from '../component/Header/Logo.jsx'
8
 import Logo from '../component/Header/Logo.jsx'
14
 import logoHeader from '../img/logo-tracim.png'
16
 import logoHeader from '../img/logo-tracim.png'
15
 import {
17
 import {
16
   newFlashMessage,
18
   newFlashMessage,
17
-  setLangActive,
19
+  setUserLang,
18
   setUserDisconnected
20
   setUserDisconnected
19
 } from '../action-creator.sync.js'
21
 } from '../action-creator.sync.js'
20
 import {
22
 import {
21
   postUserLogout
23
   postUserLogout
22
 } from '../action-creator.async.js'
24
 } from '../action-creator.async.js'
23
-import { COOKIE } from '../helper.js'
25
+import { COOKIE, PAGE } from '../helper.js'
24
 
26
 
25
 class Header extends React.Component {
27
 class Header extends React.Component {
26
   handleClickLogo = () => {}
28
   handleClickLogo = () => {}
32
   handleChangeInput = e => this.setState({inputSearchValue: e.target.value})
34
   handleChangeInput = e => this.setState({inputSearchValue: e.target.value})
33
   handleClickSubmit = () => {}
35
   handleClickSubmit = () => {}
34
 
36
 
35
-  handleChangeLang = langId => {
36
-    this.props.dispatch(setLangActive(langId))
37
-    i18n.changeLanguage(langId)
37
+  handleChangeLang = idLang => {
38
+    this.props.dispatch(setUserLang(idLang))
39
+    i18n.changeLanguage(idLang)
40
+    this.props.emitEventApp('allApp_changeLang', idLang)
38
   }
41
   }
39
 
42
 
40
   handleClickHelp = () => {}
43
   handleClickHelp = () => {}
41
 
44
 
42
   handleClickLogout = async () => {
45
   handleClickLogout = async () => {
43
-    const { dispatch, t } = this.props
46
+    const { history, dispatch, t } = this.props
44
 
47
 
45
     const fetchPostUserLogout = await dispatch(postUserLogout())
48
     const fetchPostUserLogout = await dispatch(postUserLogout())
46
     if (fetchPostUserLogout.status === 204) {
49
     if (fetchPostUserLogout.status === 204) {
48
       Cookies.remove(COOKIE.USER_AUTH)
51
       Cookies.remove(COOKIE.USER_AUTH)
49
 
52
 
50
       dispatch(setUserDisconnected())
53
       dispatch(setUserDisconnected())
54
+      history.push(PAGE.LOGIN)
51
     } else {
55
     } else {
52
-      dispatch(newFlashMessage(t('Login.logout_error', 'danger')))
56
+      dispatch(newFlashMessage(t('Disconnection error', 'danger')))
53
     }
57
     }
54
   }
58
   }
55
 
59
 
82
 
86
 
83
               <MenuActionListItemDropdownLang
87
               <MenuActionListItemDropdownLang
84
                 langList={lang}
88
                 langList={lang}
89
+                idLangActive={user.lang}
85
                 onChangeLang={this.handleChangeLang}
90
                 onChangeLang={this.handleChangeLang}
86
               />
91
               />
87
 
92
 
104
 }
109
 }
105
 
110
 
106
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
111
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
107
-export default connect(mapStateToProps)(translate()(Header))
112
+export default withRouter(connect(mapStateToProps)(translate()(appFactory(Header))))

+ 1 - 1
frontend/src/container/Login.jsx View File

57
 
57
 
58
       history.push(PAGE.HOME)
58
       history.push(PAGE.HOME)
59
     } else if (fetchPostUserLogin.status === 400) {
59
     } else if (fetchPostUserLogin.status === 400) {
60
-      dispatch(newFlashMessage(t('Login.fail'), 'danger'))
60
+      dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
61
     }
61
     }
62
   }
62
   }
63
 
63
 

+ 1 - 1
frontend/src/container/Sidebar.jsx View File

86
 
86
 
87
             <div className='sidebar__btnnewworkspace'>
87
             <div className='sidebar__btnnewworkspace'>
88
               <button className='sidebar__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'>
88
               <button className='sidebar__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'>
89
-                {t('Sidebar.create_new_workspace')}
89
+                {t('Create a workspace')}
90
               </button>
90
               </button>
91
             </div>
91
             </div>
92
 
92
 

+ 1 - 1
frontend/src/css/Generic.styl View File

237
       margin-bottom 15px
237
       margin-bottom 15px
238
       border 1px solid grey
238
       border 1px solid grey
239
       border-radius 10px
239
       border-radius 10px
240
-      padding 15px 25px
240
+      padding 15px

+ 0 - 1
frontend/src/css/Header.styl View File

40
         background-color transparent
40
         background-color transparent
41
         cursor pointer
41
         cursor pointer
42
       &__itemlanguage
42
       &__itemlanguage
43
-        display none
44
         &__languagedropdown
43
         &__languagedropdown
45
           .languagedropdown
44
           .languagedropdown
46
             &__btnlanguage
45
             &__btnlanguage

+ 3 - 3
frontend/src/css/Login.styl View File

65
         width 130px
65
         width 130px
66
   &__footer
66
   &__footer
67
     position fixed
67
     position fixed
68
-    top 95%
69
-    left calc(50% - 152px) // 152px => width of the text / 2
68
+    bottom 2%
69
+    left calc(50% - 155px) // 155px => width of the text / 2
70
     &__text
70
     &__text
71
-      width 305px
71
+      width 310px
72
       font-size 17px
72
       font-size 17px
73
 
73
 
74
 @media (min-width: min-lg) and (max-width: max-lg)
74
 @media (min-width: min-lg) and (max-width: max-lg)

+ 1 - 1
frontend/src/css/Workspace.styl View File

8
     &__button
8
     &__button
9
       display flex
9
       display flex
10
       justify-content flex-end
10
       justify-content flex-end
11
-      margin 50px 15px 0 0
11
+      margin 45px 15px 45px 0
12
       width 100%
12
       width 100%
13
       &__btnaddworkspace
13
       &__btnaddworkspace
14
         margin-bottom 50px
14
         margin-bottom 50px

+ 19 - 9
frontend/src/i18n.js View File

1
 import i18n from 'i18next'
1
 import i18n from 'i18next'
2
 import { reactI18nextModule } from 'react-i18next'
2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
3
+import { frLib, enLib } from 'tracim_frontend_lib'
4
+import en from '../i18next.scanner/en/translation.json'
5
+import fr from '../i18next.scanner/fr/translation.json'
6
+
7
+// get translation files of apps
8
+// theses files are generated by build_appname.sh
9
+const htmlDocEnTranslation = require('../dist/app/html-document_en_translation.json')
10
+const htmlDocFrTranslation = require('../dist/app/html-document_fr_translation.json')
6
 
11
 
7
 i18n
12
 i18n
8
   .use(reactI18nextModule)
13
   .use(reactI18nextModule)
9
   .init({
14
   .init({
10
-    fallbackLng: 'fr',
15
+    fallbackLng: 'en',
11
     // have a common namespace used around the full app
16
     // have a common namespace used around the full app
12
     ns: ['translation'], // namespace
17
     ns: ['translation'], // namespace
13
     defaultNS: 'translation',
18
     defaultNS: 'translation',
14
     debug: true,
19
     debug: true,
15
-    // interpolation: {
16
-    //   escapeValue: false, // not needed for react!!
17
-    // },
18
     react: {
20
     react: {
19
       wait: true
21
       wait: true
20
     },
22
     },
21
     resources: {
23
     resources: {
22
       en: {
24
       en: {
23
-        translation: {...langEn.translation, ...en.translation}
25
+        translation: {
26
+          ...enLib, // fronted_lib
27
+          ...en, // frontend
28
+          ...htmlDocEnTranslation // html-document
29
+        }
24
       },
30
       },
25
       fr: {
31
       fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
32
+        translation: {
33
+          ...frLib, // fronted_lib
34
+          ...fr, // frontend
35
+          ...htmlDocFrTranslation // html-document
36
+        }
27
       }
37
       }
28
     }
38
     }
29
   })
39
   })

frontend/src/img/drapeau-en.png → frontend/src/img/flag_en.png View File


frontend/src/img/drapeau-fr.png → frontend/src/img/flag_fr.png View File


+ 2 - 2
frontend/src/reducer/app.js View File

1
-import { APP_LIST } from '../action-creator.sync.js'
1
+import { SET, APP_LIST } from '../action-creator.sync.js'
2
 
2
 
3
 export default function app (state = [], action) {
3
 export default function app (state = [], action) {
4
   switch (action.type) {
4
   switch (action.type) {
5
-    case `Set/${APP_LIST}`:
5
+    case `${SET}/${APP_LIST}`:
6
       return action.appList.map(a => ({
6
       return action.appList.map(a => ({
7
         label: a.label,
7
         label: a.label,
8
         slug: a.slug,
8
         slug: a.slug,

+ 2 - 2
frontend/src/reducer/contentType.js View File

1
-import { CONTENT_TYPE_LIST } from '../action-creator.sync.js'
1
+import { SET, CONTENT_TYPE_LIST } from '../action-creator.sync.js'
2
 
2
 
3
 export function contentType (state = [], action) {
3
 export function contentType (state = [], action) {
4
   switch (action.type) {
4
   switch (action.type) {
5
-    case `Set/${CONTENT_TYPE_LIST}`:
5
+    case `${SET}/${CONTENT_TYPE_LIST}`:
6
       return action.contentTypeList.map(ct => ({
6
       return action.contentTypeList.map(ct => ({
7
         label: ct.label,
7
         label: ct.label,
8
         slug: ct.slug,
8
         slug: ct.slug,

+ 3 - 5
frontend/src/reducer/flashMessage.js View File

1
-import {
2
-  FLASH_MESSAGE
3
-} from '../action-creator.sync.js'
1
+import { ADD, REMOVE, FLASH_MESSAGE } from '../action-creator.sync.js'
4
 
2
 
5
 export default function flashMessage (state = [], action) {
3
 export default function flashMessage (state = [], action) {
6
   switch (action.type) {
4
   switch (action.type) {
7
-    case `Add/${FLASH_MESSAGE}`:
5
+    case `${ADD}/${FLASH_MESSAGE}`:
8
       return [...state, {
6
       return [...state, {
9
         message: action.msg.message,
7
         message: action.msg.message,
10
         type: action.msg.type || 'info' // may be info, success, danger
8
         type: action.msg.type || 'info' // may be info, success, danger
11
       }]
9
       }]
12
 
10
 
13
-    case `Remove/${FLASH_MESSAGE}`:
11
+    case `${REMOVE}/${FLASH_MESSAGE}`:
14
       return state.filter(fm => fm.message === action.message)
12
       return state.filter(fm => fm.message === action.message)
15
 
13
 
16
     default:
14
     default:

+ 16 - 5
frontend/src/reducer/lang.js View File

1
-import { LANG } from '../action-creator.sync.js'
1
+import { UPDATE, LANG } from '../action-creator.sync.js'
2
+import flagEn from '../img/flag_en.png'
3
+import flagFr from '../img/flag_fr.png'
2
 
4
 
3
-export function lang (state = [], action) {
5
+const defaultLang = [{
6
+  id: 'en',
7
+  icon: flagEn
8
+}, {
9
+  id: 'fr',
10
+  icon: flagFr
11
+}]
12
+
13
+export function lang (state = defaultLang, action) {
4
   switch (action.type) {
14
   switch (action.type) {
5
-    case `Update/${LANG}`:
15
+    case `${UPDATE}/${LANG}`:
6
       return action.langList
16
       return action.langList
7
 
17
 
8
-    case `Set/${LANG}/Active`:
9
-      return state.map(l => ({...l, active: l.id === action.langId}))
18
+    // Côme - 2018/07/30 - deprecated, lang active is saved in user reducer
19
+    // case `Set/${LANG}/Active`:
20
+    //   return state.map(l => ({...l, active: l.id === action.langId}))
10
 
21
 
11
     default:
22
     default:
12
       return state
23
       return state

+ 2 - 2
frontend/src/reducer/timezone.js View File

1
-import { TIMEZONE } from '../action-creator.sync.js'
1
+import { SET, TIMEZONE } from '../action-creator.sync.js'
2
 
2
 
3
 export function timezone (state = [], action) {
3
 export function timezone (state = [], action) {
4
   switch (action.type) {
4
   switch (action.type) {
5
-    case `Set/${TIMEZONE}`:
5
+    case `${SET}/${TIMEZONE}`:
6
       return action.timezone
6
       return action.timezone
7
 
7
 
8
     default:
8
     default:

+ 14 - 5
frontend/src/reducer/user.js View File

1
 import {
1
 import {
2
+  SET,
3
+  UPDATE,
2
   USER_CONNECTED,
4
   USER_CONNECTED,
3
   USER_DISCONNECTED,
5
   USER_DISCONNECTED,
4
-  USER_DATA
6
+  USER_DATA,
7
+  USER_LANG
5
 } from '../action-creator.sync.js'
8
 } from '../action-creator.sync.js'
6
 
9
 
7
 const defaultUser = {
10
 const defaultUser = {
8
   user_id: -1,
11
   user_id: -1,
9
   logged: null, // null avoid to be redirected to /login while whoami ep has not responded yet
12
   logged: null, // null avoid to be redirected to /login while whoami ep has not responded yet
13
+  auth: '',
10
   timezone: '',
14
   timezone: '',
11
   profile: {
15
   profile: {
12
     id: 1,
16
     id: 1,
17
   caldav_url: null,
21
   caldav_url: null,
18
   avatar_url: null,
22
   avatar_url: null,
19
   created: '',
23
   created: '',
20
-  display_name: ''
24
+  public_name: '',
25
+  lang: 'en' // @FIXME Côme - 2018/07/30 - remove this line when api returns the lang (https://github.com/tracim/tracim/issues/734)
21
 }
26
 }
22
 
27
 
23
 export default function user (state = defaultUser, action) {
28
 export default function user (state = defaultUser, action) {
24
   switch (action.type) {
29
   switch (action.type) {
25
-    case `Set/${USER_CONNECTED}`:
30
+    case `${SET}/${USER_CONNECTED}`:
26
       return {
31
       return {
32
+        ...state,
27
         ...action.user,
33
         ...action.user,
28
         avatar_url: 'https://www.algoo.fr/static/images/people_images/PERSO_SEUL.png' // @FIXME use avatar from api when db handles it
34
         avatar_url: 'https://www.algoo.fr/static/images/people_images/PERSO_SEUL.png' // @FIXME use avatar from api when db handles it
29
       }
35
       }
30
 
36
 
31
-    case `Set/${USER_DISCONNECTED}`:
37
+    case `${SET}/${USER_DISCONNECTED}`:
32
       return defaultUser
38
       return defaultUser
33
 
39
 
34
-    case `Update/${USER_DATA}`:
40
+    case `${UPDATE}/${USER_DATA}`:
35
       return {...state, ...action.data}
41
       return {...state, ...action.data}
36
 
42
 
43
+    case `${SET}/${USER_LANG}`:
44
+      return {...state, lang: action.lang}
45
+
37
     default:
46
     default:
38
       return state
47
       return state
39
   }
48
   }

+ 5 - 3
frontend/src/reducer/workspaceContent.js View File

1
 import {
1
 import {
2
+  SET,
3
+  UPDATE,
2
   WORKSPACE,
4
   WORKSPACE,
3
   FOLDER
5
   FOLDER
4
 } from '../action-creator.sync.js'
6
 } from '../action-creator.sync.js'
5
 
7
 
6
 export default function workspace (state = [], action) {
8
 export default function workspace (state = [], action) {
7
   switch (action.type) {
9
   switch (action.type) {
8
-    case `Set/${WORKSPACE}/Content`:
10
+    case `${SET}/${WORKSPACE}/Content`:
9
       return action.workspaceContent.map(wsc => ({
11
       return action.workspaceContent.map(wsc => ({
10
         id: wsc.content_id,
12
         id: wsc.content_id,
11
         label: wsc.label,
13
         label: wsc.label,
20
         subContentTypeSlug: wsc.sub_content_type_slug
22
         subContentTypeSlug: wsc.sub_content_type_slug
21
       }))
23
       }))
22
 
24
 
23
-    case `Update/${WORKSPACE}/Filter`: // not used anymore ?
25
+    case `${UPDATE}/${WORKSPACE}/Filter`: // not used anymore ?
24
       return {...state, filter: action.filterList}
26
       return {...state, filter: action.filterList}
25
 
27
 
26
-    case `Set/${WORKSPACE}/${FOLDER}/Content`:
28
+    case `${SET}/${WORKSPACE}/${FOLDER}/Content`:
27
       const setFolderContent = (contentItem, action) => {
29
       const setFolderContent = (contentItem, action) => {
28
         if (contentItem.id === action.folderId) return {...contentItem, content: action.content}
30
         if (contentItem.id === action.folderId) return {...contentItem, content: action.content}
29
 
31
 

+ 6 - 4
frontend/src/reducer/workspaceList.js View File

1
 import {
1
 import {
2
+  SET,
3
+  UPDATE,
2
   WORKSPACE_LIST,
4
   WORKSPACE_LIST,
3
   USER_ROLE
5
   USER_ROLE
4
 } from '../action-creator.sync.js'
6
 } from '../action-creator.sync.js'
7
 
9
 
8
 export function workspaceList (state = [], action) {
10
 export function workspaceList (state = [], action) {
9
   switch (action.type) {
11
   switch (action.type) {
10
-    case `Update/${WORKSPACE_LIST}`:
12
+    case `${UPDATE}/${WORKSPACE_LIST}`:
11
       return action.workspaceList.map(ws => ({
13
       return action.workspaceList.map(ws => ({
12
         id: ws.workspace_id,
14
         id: ws.workspace_id,
13
         label: ws.label,
15
         label: ws.label,
23
         isOpenInSidebar: false
25
         isOpenInSidebar: false
24
       }))
26
       }))
25
 
27
 
26
-    case `Set/${WORKSPACE_LIST}/isOpenInSidebar`:
28
+    case `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`:
27
       return state.map(ws => ws.id === action.workspaceId
29
       return state.map(ws => ws.id === action.workspaceId
28
         ? {...ws, isOpenInSidebar: action.isOpenInSidebar}
30
         ? {...ws, isOpenInSidebar: action.isOpenInSidebar}
29
         : ws
31
         : ws
30
       )
32
       )
31
 
33
 
32
-    case `Set/${USER_ROLE}`: // not used yet
34
+    case `${SET}/${USER_ROLE}`: // not used yet
33
       return state.map(ws => {
35
       return state.map(ws => {
34
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
36
         const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
35
         return {
37
         return {
39
         }
41
         }
40
       })
42
       })
41
 
43
 
42
-    case `Update/${USER_ROLE}/SubscriptionNotif`: // not used yet
44
+    case `${UPDATE}/${USER_ROLE}/SubscriptionNotif`: // not used yet
43
       return state.map(ws => ws.id === action.workspaceId
45
       return state.map(ws => ws.id === action.workspaceId
44
         ? {...ws, notif: action.subscriptionNotif}
46
         ? {...ws, notif: action.subscriptionNotif}
45
         : ws
47
         : ws

+ 0 - 41
frontend/src/translate/en.js View File

1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    Header: {
4
-      Search: 'Search'
5
-    },
6
-    Footer: {
7
-      marketing_msg: 'Create your own collaborative workspaces on trac.im',
8
-      copyright: 'Copyright 2013 - 2017'
9
-    },
10
-    FlashMessage: {
11
-      error: 'Error'
12
-    },
13
-    Login: {
14
-      fail: 'Unknown email or password',
15
-      logout_error: 'Disconnection error'
16
-    },
17
-    FileItemHeader: {
18
-      type: 'Type',
19
-      document_name: "Document's name",
20
-      status: 'Status'
21
-    },
22
-    Folder: {
23
-      create: 'Create in this folder',
24
-      content_type: 'Content type'
25
-    },
26
-    Sidebar: {
27
-      create_new_workspace: 'Create new workspace'
28
-    },
29
-    Account: {
30
-      title: 'My account'
31
-    },
32
-    role: {
33
-      reader: 'Reader',
34
-      contributor: 'Contributor',
35
-      content_manager: 'Content manager',
36
-      manager: 'Manager'
37
-    }
38
-  }
39
-}
40
-
41
-export default en

+ 0 - 41
frontend/src/translate/fr.js View File

1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    Header: {
4
-      Search: 'Rechercher'
5
-    },
6
-    Footer: {
7
-      marketing_msg: 'Créer votre propre espace de travail collaboratif sur trac.im',
8
-      copyright: 'Copyright 2013 - 2017'
9
-    },
10
-    FlashMessage: {
11
-      error: 'Erreur'
12
-    },
13
-    Login: {
14
-      fail: 'Email ou mot de passe inconnu.',
15
-      logout_error: 'Erreur de déconnexion.'
16
-    },
17
-    FileItemHeader: {
18
-      type: 'Type',
19
-      document_name: 'Nom du document',
20
-      status: 'Statut'
21
-    },
22
-    Folder: {
23
-      create: 'Créer dans ce dossier',
24
-      content_type: 'Type de contenu'
25
-    },
26
-    Sidebar: {
27
-      create_new_workspace: 'Créer un workspace'
28
-    },
29
-    Account: {
30
-      title: 'Mon compte'
31
-    },
32
-    role: {
33
-      reader: 'Lecteur',
34
-      contributor: 'Contributeur',
35
-      content_manager: 'Gestionnaire de contenu',
36
-      manager: 'Responsable'
37
-    }
38
-  }
39
-}
40
-
41
-export default fr

+ 17 - 0
frontend_app_html-document/build_html-document.sh View File

1
+#!/bin/bash
2
+
3
+. ../bash_library.sh # source bash_library.sh
4
+
5
+windoz=""
6
+if [[ $1 = "-w" ]]; then
7
+    windoz="windoz"
8
+fi
9
+
10
+log "npm run build$windoz"
11
+npm run build$windoz
12
+log "cp dist/html-document.app.js ../frontend/dist/app"
13
+cp dist/html-document.app.js ../frontend/dist/app
14
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json"
15
+cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
16
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
17
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json

+ 1 - 1
frontend_app_html-document/dist/index.html View File

3
 <head>
3
 <head>
4
   <meta charset='utf-8' />
4
   <meta charset='utf-8' />
5
   <meta name="viewport" content="width=device-width, user-scalable=no" />
5
   <meta name="viewport" content="width=device-width, user-scalable=no" />
6
-  <title>Html-documents App Tracim</title>
6
+  <title>Html-document App Tracim</title>
7
   <link rel='shortcut icon' href='favicon.ico'>
7
   <link rel='shortcut icon' href='favicon.ico'>
8
 
8
 
9
   <link rel="stylesheet" type="text/css" href="./font/font-awesome-4.7.0/css/font-awesome.css">
9
   <link rel="stylesheet" type="text/css" href="./font/font-awesome-4.7.0/css/font-awesome.css">

+ 14 - 0
frontend_app_html-document/i18next.scanner.js View File

1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// --------------------
7
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
8
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
9
+// --------------------
10
+
11
+vfs.src(['./src/**/*.jsx'])
12
+// .pipe(sort()) // Sort files in stream by path
13
+  .pipe(scanner(option))
14
+  .pipe(vfs.dest('./i18next.scanner'))

+ 5 - 0
frontend_app_html-document/i18next.scanner/en/translation.json View File

1
+{
2
+  "Last version": "Last version",
3
+  "Validate and create": "Validate and create",
4
+  "Document's title": "Document's title"
5
+}

+ 5 - 0
frontend_app_html-document/i18next.scanner/fr/translation.json View File

1
+{
2
+  "Last version": "Dernière version",
3
+  "Validate and create": "Valider et créer",
4
+  "Document's title": "Titre du document"
5
+}

+ 4 - 2
frontend_app_html-document/package.json View File

1
 {
1
 {
2
-  "name": "tracim_app_html-documents",
2
+  "name": "tracim_app_html-document",
3
   "version": "1.1.2",
3
   "version": "1.1.2",
4
   "description": "",
4
   "description": "",
5
   "main": "index.js",
5
   "main": "index.js",
7
     "servdev": "NODE_ENV=development webpack-dev-server --watch --colors --inline --hot --progress",
7
     "servdev": "NODE_ENV=development webpack-dev-server --watch --colors --inline --hot --progress",
8
     "servdevwindoz": "set NODE_ENV=development&& webpack-dev-server --watch --colors --inline --hot --progress",
8
     "servdevwindoz": "set NODE_ENV=development&& webpack-dev-server --watch --colors --inline --hot --progress",
9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9871 -- webpack-dev-server --watch --colors --inline --hot --progress",
9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9871 -- webpack-dev-server --watch --colors --inline --hot --progress",
10
-    "buildwindoz": "set NODE_ENV=production&& webpack -p",
11
     "build": "NODE_ENV=production webpack -p",
10
     "build": "NODE_ENV=production webpack -p",
11
+    "build-translation": "node i18next.scanner.js",
12
+    "buildwindoz": "set NODE_ENV=production&& webpack -p",
12
     "test": "echo \"Error: no test specified\" && exit 1"
13
     "test": "echo \"Error: no test specified\" && exit 1"
13
   },
14
   },
14
   "author": "",
15
   "author": "",
41
     "whatwg-fetch": "^2.0.3"
42
     "whatwg-fetch": "^2.0.3"
42
   },
43
   },
43
   "devDependencies": {
44
   "devDependencies": {
45
+    "i18next-scanner": "^2.6.1",
44
     "webpack-dashboard": "^1.1.1",
46
     "webpack-dashboard": "^1.1.1",
45
     "webpack-dev-server": "^2.9.2"
47
     "webpack-dev-server": "^2.9.2"
46
   },
48
   },

+ 0 - 13
frontend_app_html-document/rebuild_html-document.sh View File

1
-#!/bin/bash
2
-
3
-. ../bash_library.sh # source bash_library.sh
4
-
5
-windoz=""
6
-if  [[ $1 = "-w" ]]; then
7
-    windoz="windoz"
8
-fi
9
-
10
-log "npm run build$windoz"
11
-npm run build$windoz
12
-log "cp dist/html-document.app.js ../frontend/dist/app"
13
-cp dist/html-document.app.js ../frontend/dist/app

+ 5 - 5
frontend_app_html-document/src/component/HtmlDocument.jsx View File

4
 
4
 
5
 const HtmlDocument = props => {
5
 const HtmlDocument = props => {
6
   return (
6
   return (
7
-    <div className='wsContentHtmlDocument__contentpage__textnote html-documents__contentpage__textnote'>
7
+    <div className='wsContentHtmlDocument__contentpage__textnote html-document__contentpage__textnote'>
8
       {(props.mode === MODE.VIEW || props.mode === MODE.REVISION) &&
8
       {(props.mode === MODE.VIEW || props.mode === MODE.REVISION) &&
9
         <div>
9
         <div>
10
-          <div className='html-documents__contentpage__textnote__version'>
10
+          <div className='html-document__contentpage__textnote__version'>
11
             version n°
11
             version n°
12
             <div dangerouslySetInnerHTML={{__html: props.mode === MODE.VIEW ? props.lastVersion : props.version}} />
12
             <div dangerouslySetInnerHTML={{__html: props.mode === MODE.VIEW ? props.lastVersion : props.version}} />
13
             {props.mode === MODE.REVISION &&
13
             {props.mode === MODE.REVISION &&
14
-              <div className='html-documents__contentpage__textnote__lastversion'>
14
+              <div className='html-document__contentpage__textnote__lastversion'>
15
                 (dernière version : {props.lastVersion})
15
                 (dernière version : {props.lastVersion})
16
               </div>
16
               </div>
17
             }
17
             }
18
           </div>
18
           </div>
19
           {/* need try to inject html in stateless component () => <span>{props.text}</span> */}
19
           {/* need try to inject html in stateless component () => <span>{props.text}</span> */}
20
-          <div className='html-documents__contentpage__textnote__text' dangerouslySetInnerHTML={{__html: props.text}} />
20
+          <div className='html-document__contentpage__textnote__text' dangerouslySetInnerHTML={{__html: props.text}} />
21
         </div>
21
         </div>
22
       }
22
       }
23
 
23
 
24
       {props.mode === MODE.EDIT &&
24
       {props.mode === MODE.EDIT &&
25
         <TextAreaApp
25
         <TextAreaApp
26
           id={props.wysiwygNewVersion}
26
           id={props.wysiwygNewVersion}
27
-          customClass={'html-documents__editionmode'}
27
+          customClass={'html-document__editionmode'}
28
           customColor={props.customColor}
28
           customColor={props.customColor}
29
           onClickCancelBtn={props.onClickCloseEditMode}
29
           onClickCancelBtn={props.onClickCloseEditMode}
30
           onClickValidateBtn={props.onClickValidateBtn}
30
           onClickValidateBtn={props.onClickValidateBtn}

+ 27 - 9
frontend_app_html-document/src/container/HtmlDocument.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import HtmlDocumentComponent from '../component/HtmlDocument.jsx'
2
 import HtmlDocumentComponent from '../component/HtmlDocument.jsx'
3
+import { translate } from 'react-i18next'
4
+import i18n from '../i18n.js'
3
 import {
5
 import {
6
+  addAllResourceI18n,
4
   handleFetchResult,
7
   handleFetchResult,
5
   PopinFixed,
8
   PopinFixed,
6
   PopinFixedHeader,
9
   PopinFixedHeader,
20
   putHtmlDocContent,
23
   putHtmlDocContent,
21
   putHtmlDocStatus
24
   putHtmlDocStatus
22
 } from '../action.async.js'
25
 } from '../action.async.js'
23
-import i18n from '../i18n.js'
24
 
26
 
25
 class HtmlDocument extends React.Component {
27
 class HtmlDocument extends React.Component {
26
   constructor (props) {
28
   constructor (props) {
27
     super(props)
29
     super(props)
28
     this.state = {
30
     this.state = {
29
-      appName: 'html-documents',
31
+      appName: 'html-document',
30
       isVisible: true,
32
       isVisible: true,
31
       config: props.data ? props.data.config : debug.config,
33
       config: props.data ? props.data.config : debug.config,
32
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
34
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
38
       mode: MODE.VIEW
40
       mode: MODE.VIEW
39
     }
41
     }
40
 
42
 
43
+    // i18n has been init, add resources from frontend
44
+    addAllResourceI18n(i18n, this.state.config.translation)
45
+    i18n.changeLanguage(this.state.loggedUser.lang)
46
+
41
     document.addEventListener('appCustomEvent', this.customEventReducer)
47
     document.addEventListener('appCustomEvent', this.customEventReducer)
42
   }
48
   }
43
 
49
 
44
   customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
50
   customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
45
     switch (type) {
51
     switch (type) {
46
-      case 'html-documents_showApp':
52
+      case 'html-document_showApp':
47
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
53
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
48
         this.setState({isVisible: true})
54
         this.setState({isVisible: true})
49
         break
55
         break
50
-      case 'html-documents_hideApp':
56
+      case 'html-document_hideApp':
51
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
57
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
52
         this.setState({isVisible: false})
58
         this.setState({isVisible: false})
53
         break
59
         break
54
-      case 'html-documents_reloadContent':
60
+      case 'html-document_reloadContent':
55
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
61
         console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
56
         this.setState(prev => ({content: {...prev.content, ...data}, isVisible: true}))
62
         this.setState(prev => ({content: {...prev.content, ...data}, isVisible: true}))
63
+        break
64
+      case 'allApp_changeLang':
65
+        console.log('%c<HtmlDocument> Custom event', 'color: #28a745', type, data)
66
+        this.setState(prev => ({
67
+          loggedUser: {
68
+            ...prev.loggedUser,
69
+            lang: data
70
+          }
71
+        }))
72
+        i18n.changeLanguage(data)
73
+        break
57
     }
74
     }
58
   }
75
   }
59
 
76
 
284
 
301
 
285
   render () {
302
   render () {
286
     const { isVisible, loggedUser, content, timeline, newComment, timelineWysiwyg, config, mode } = this.state
303
     const { isVisible, loggedUser, content, timeline, newComment, timelineWysiwyg, config, mode } = this.state
304
+    const { t } = this.props
287
 
305
 
288
     if (!isVisible) return null
306
     if (!isVisible) return null
289
 
307
 
316
 
334
 
317
               {mode === MODE.REVISION &&
335
               {mode === MODE.REVISION &&
318
                 <button
336
                 <button
319
-                  className='wsContentGeneric__option__menu__lastversion html-documents__lastversionbtn btn'
337
+                  className='wsContentGeneric__option__menu__lastversion html-document__lastversionbtn btn'
320
                   onClick={this.handleClickLastVersion}
338
                   onClick={this.handleClickLastVersion}
321
                   style={{backgroundColor: config.hexcolor, color: '#fdfdfd'}}
339
                   style={{backgroundColor: config.hexcolor, color: '#fdfdfd'}}
322
                 >
340
                 >
323
                   <i className='fa fa-code-fork' />
341
                   <i className='fa fa-code-fork' />
324
-                  Dernière version
342
+                  {t('Last version')}
325
                 </button>
343
                 </button>
326
               }
344
               }
327
             </div>
345
             </div>
358
             lastVersion={timeline.filter(t => t.timelineType === 'revision').length}
376
             lastVersion={timeline.filter(t => t.timelineType === 'revision').length}
359
             text={content.raw_content}
377
             text={content.raw_content}
360
             onChangeText={this.handleChangeText}
378
             onChangeText={this.handleChangeText}
361
-            key={'html-documents'}
379
+            key={'html-document'}
362
           />
380
           />
363
 
381
 
364
           <Timeline
382
           <Timeline
381
   }
399
   }
382
 }
400
 }
383
 
401
 
384
-export default HtmlDocument
402
+export default translate()(HtmlDocument)

+ 31 - 5
frontend_app_html-document/src/container/PopupCreateHtmlDocument.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2
 import {
3
 import {
3
-  CardPopupCreateContent, handleFetchResult
4
+  CardPopupCreateContent,
5
+  handleFetchResult,
6
+  addAllResourceI18n
4
 } from 'tracim_frontend_lib'
7
 } from 'tracim_frontend_lib'
5
 import { postHtmlDocContent } from '../action.async.js'
8
 import { postHtmlDocContent } from '../action.async.js'
9
+import i18n from '../i18n.js'
6
 
10
 
7
 const debug = { // outdated
11
 const debug = { // outdated
8
   config: {
12
   config: {
9
     label: 'Text Document',
13
     label: 'Text Document',
10
-    slug: 'html-documents',
14
+    slug: 'html-document',
11
     faIcon: 'file-text-o',
15
     faIcon: 'file-text-o',
12
     hexcolor: '#3f52e3',
16
     hexcolor: '#3f52e3',
13
     creationLabel: 'Write a document',
17
     creationLabel: 'Write a document',
35
   constructor (props) {
39
   constructor (props) {
36
     super(props)
40
     super(props)
37
     this.state = {
41
     this.state = {
38
-      appName: 'html-documents',
42
+      appName: 'html-document', // must remain 'html-document' because it is the name of the react built app (which contains HtmlDocument and PopupCreateHtmlDocument)
39
       config: props.data ? props.data.config : debug.config,
43
       config: props.data ? props.data.config : debug.config,
40
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
44
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
41
       idWorkspace: props.data ? props.data.idWorkspace : debug.idWorkspace,
45
       idWorkspace: props.data ? props.data.idWorkspace : debug.idWorkspace,
42
       idFolder: props.data ? props.data.idFolder : debug.idFolder,
46
       idFolder: props.data ? props.data.idFolder : debug.idFolder,
43
       newContentName: ''
47
       newContentName: ''
44
     }
48
     }
49
+
50
+    // i18n has been init, add resources from frontend
51
+    addAllResourceI18n(i18n, this.state.config.translation)
52
+    i18n.changeLanguage(this.state.loggedUser.lang)
53
+
54
+    document.addEventListener('appCustomEvent', this.customEventReducer)
55
+  }
56
+
57
+  customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
58
+    switch (type) {
59
+      case 'allApp_changeLang':
60
+        console.log('%c<PopupCreateHtmlDocument> Custom event', 'color: #28a745', type, data)
61
+        this.setState(prev => ({
62
+          loggedUser: {
63
+            ...prev.loggedUser,
64
+            lang: data
65
+          }
66
+        }))
67
+        i18n.changeLanguage(data)
68
+        break
69
+    }
45
   }
70
   }
46
 
71
 
47
   handleChangeNewContentName = e => this.setState({newContentName: e.target.value})
72
   handleChangeNewContentName = e => this.setState({newContentName: e.target.value})
88
         faIcon={this.state.config.faIcon}
113
         faIcon={this.state.config.faIcon}
89
         contentName={this.state.newContentName}
114
         contentName={this.state.newContentName}
90
         onChangeContentName={this.handleChangeNewContentName}
115
         onChangeContentName={this.handleChangeNewContentName}
91
-        btnValidateLabel='Valider et créer'
116
+        btnValidateLabel={this.props.t('Validate and create')}
117
+        inputPlaceholder={this.props.t("Document's title")}
92
       />
118
       />
93
     )
119
     )
94
   }
120
   }
95
 }
121
 }
96
 
122
 
97
-export default PopupCreateHtmlDocument
123
+export default translate()(PopupCreateHtmlDocument)

+ 5 - 5
frontend_app_html-document/src/css/index.styl View File

1
 @import "../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
1
 @import "../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
 
2
 
3
-.html-documents
3
+.html-document
4
   width 70%
4
   width 70%
5
   &__header
5
   &__header
6
     &__editionmode
6
     &__editionmode
68
     background-color htmlColor
68
     background-color htmlColor
69
 
69
 
70
 @media (min-width: min-sm) and (max-width: max-lg)
70
 @media (min-width: min-sm) and (max-width: max-lg)
71
-  .html-documents
71
+  .html-document
72
     &__contentpage
72
     &__contentpage
73
       &__content
73
       &__content
74
         margin 10px
74
         margin 10px
77
 
77
 
78
 @media (min-width: min-md) and (max-width: max-md)
78
 @media (min-width: min-md) and (max-width: max-md)
79
 
79
 
80
-  .html-documents
80
+  .html-document
81
     top 68px
81
     top 68px
82
 
82
 
83
 @media (min-width: min-sm) and (max-width: max-sm)
83
 @media (min-width: min-sm) and (max-width: max-sm)
84
 
84
 
85
-  .html-documents
85
+  .html-document
86
     top 68px
86
     top 68px
87
     width 100%
87
     width 100%
88
 
88
 
89
 @media (max-width: max-xs)
89
 @media (max-width: max-xs)
90
 
90
 
91
-  .html-documents
91
+  .html-document
92
     top 68px
92
     top 68px
93
     width 100%
93
     width 100%
94
     &__contentpage
94
     &__contentpage

+ 17 - 4
frontend_app_html-document/src/helper.js View File

16
 export const debug = {
16
 export const debug = {
17
   config: {
17
   config: {
18
     label: 'Text Document',
18
     label: 'Text Document',
19
-    slug: 'html-documents',
19
+    slug: 'html-document',
20
     faIcon: 'file-text-o',
20
     faIcon: 'file-text-o',
21
     hexcolor: '#3f52e3',
21
     hexcolor: '#3f52e3',
22
     creationLabel: 'Write a document',
22
     creationLabel: 'Write a document',
51
       faIcon: 'warning',
51
       faIcon: 'warning',
52
       hexcolor: '#ababab',
52
       hexcolor: '#ababab',
53
       globalStatus: 'closed'
53
       globalStatus: 'closed'
54
-    }]
54
+    }],
55
+    translation: {
56
+      en: {
57
+        translation: {
58
+          'Last version': 'Last version debug en'
59
+        }
60
+      },
61
+      fr: {
62
+        translation: {
63
+          'Last version': 'Dernière version debug fr'
64
+        }
65
+      }
66
+    }
55
   },
67
   },
56
   loggedUser: { // @FIXME this object is outdated
68
   loggedUser: { // @FIXME this object is outdated
57
     user_id: 5,
69
     user_id: 5,
59
     firstname: 'Côme',
71
     firstname: 'Côme',
60
     lastname: 'Stoilenom',
72
     lastname: 'Stoilenom',
61
     email: 'osef@algoo.fr',
73
     email: 'osef@algoo.fr',
74
+    lang: 'en',
62
     avatar_url: 'https://avatars3.githubusercontent.com/u/11177014?s=460&v=4',
75
     avatar_url: 'https://avatars3.githubusercontent.com/u/11177014?s=460&v=4',
63
     auth: btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
76
     auth: btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
64
   },
77
   },
69
       user_id: 1 // -1 or 1 for debug
82
       user_id: 1 // -1 or 1 for debug
70
     },
83
     },
71
     content_id: 22, // 1 or 22 for debug
84
     content_id: 22, // 1 or 22 for debug
72
-    content_type: 'html-documents',
85
+    content_type: 'html-document',
73
     created: '2018-06-18T14:59:26Z',
86
     created: '2018-06-18T14:59:26Z',
74
     current_revision_id: 11,
87
     current_revision_id: 11,
75
     is_archived: false,
88
     is_archived: false,
86
     show_in_ui: true,
99
     show_in_ui: true,
87
     slug: 'current-menu',
100
     slug: 'current-menu',
88
     status: 'open',
101
     status: 'open',
89
-    sub_content_types: ['thread', 'html-documents', 'file', 'folder'],
102
+    sub_content_types: ['thread', 'html-document', 'file', 'folder'],
90
     workspace_id: 1
103
     workspace_id: 1
91
   },
104
   },
92
   timeline: timelineDebugData
105
   timeline: timelineDebugData

+ 1 - 11
frontend_app_html-document/src/i18n.js View File

1
 import i18n from 'i18next'
1
 import i18n from 'i18next'
2
 import { reactI18nextModule } from 'react-i18next'
2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
6
 
3
 
7
 i18n
4
 i18n
8
   .use(reactI18nextModule)
5
   .use(reactI18nextModule)
18
     react: {
15
     react: {
19
       wait: true
16
       wait: true
20
     },
17
     },
21
-    resources: {
22
-      en: {
23
-        translation: {...langEn.translation, ...en.translation}
24
-      },
25
-      fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
27
-      }
28
-    }
18
+    resources: {} // init with empty resources, they will come from frontend in app constructor
29
   })
19
   })
30
 
20
 
31
 export default i18n
21
 export default i18n

+ 1 - 1
frontend_app_html-document/src/index.js View File

10
 require('./css/index.styl')
10
 require('./css/index.styl')
11
 
11
 
12
 const appInterface = {
12
 const appInterface = {
13
-  name: 'HtmlDocument',
13
+  name: 'html-document',
14
   isRendered: false,
14
   isRendered: false,
15
   renderAppFull: data => {
15
   renderAppFull: data => {
16
     return ReactDOM.render(
16
     return ReactDOM.render(

+ 0 - 9
frontend_app_html-document/src/translate/en.js View File

1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'New version'
5
-    }
6
-  }
7
-}
8
-
9
-export default en

+ 0 - 9
frontend_app_html-document/src/translate/fr.js View File

1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'nouvelle version'
5
-    }
6
-  }
7
-}
8
-
9
-export default fr

+ 17 - 0
frontend_app_thread/build_thread.sh View File

1
+#!/bin/bash
2
+
3
+. ../bash_library.sh # source bash_library.sh
4
+
5
+windoz=""
6
+if  [[ $1 = "-w" ]]; then
7
+    windoz="windoz"
8
+fi
9
+
10
+log "npm run build$windoz"
11
+npm run build$windoz
12
+log "cp dist/thread.app.js ../frontend/dist/app"
13
+cp dist/thread.app.js ../frontend/dist/app
14
+log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
15
+cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
16
+log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
17
+cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json

+ 14 - 0
frontend_app_thread/i18next.scanner.js View File

1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+// --------------------
7
+// 2018/07/27 - currently, last version is 2.6.5 but a bug is spaming log with errors. So I'm using 2.6.1
8
+// this issue seems related : https://github.com/i18next/i18next-scanner/issues/88
9
+// --------------------
10
+
11
+vfs.src(['./src/**/*.jsx'])
12
+// .pipe(sort()) // Sort files in stream by path
13
+  .pipe(scanner(option))
14
+  .pipe(vfs.dest('./i18next.scanner'))

+ 4 - 0
frontend_app_thread/i18next.scanner/en/translation.json View File

1
+{
2
+  "Validate and create": "Validate and create",
3
+  "Topic's subject": "Topic's subject"
4
+}

+ 4 - 0
frontend_app_thread/i18next.scanner/fr/translation.json View File

1
+{
2
+  "Validate and create": "Valider et créer",
3
+  "Topic's subject": "Sujet de la discussion"
4
+}

+ 2 - 0
frontend_app_thread/package.json View File

8
     "servdevwindoz": "set NODE_ENV=development&& webpack-dev-server --watch --colors --inline --hot --progress",
8
     "servdevwindoz": "set NODE_ENV=development&& webpack-dev-server --watch --colors --inline --hot --progress",
9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9872 -- webpack-dev-server --watch --colors --inline --hot --progress",
9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9872 -- webpack-dev-server --watch --colors --inline --hot --progress",
10
     "build": "NODE_ENV=production webpack -p",
10
     "build": "NODE_ENV=production webpack -p",
11
+    "build-translation": "node i18next.scanner.js",
11
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
12
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
12
     "test": "echo \"Error: no test specified\" && exit 1"
13
     "test": "echo \"Error: no test specified\" && exit 1"
13
   },
14
   },
41
     "whatwg-fetch": "^2.0.3"
42
     "whatwg-fetch": "^2.0.3"
42
   },
43
   },
43
   "devDependencies": {
44
   "devDependencies": {
45
+    "i18next-scanner": "^2.6.1",
44
     "webpack-dashboard": "^1.1.1",
46
     "webpack-dashboard": "^1.1.1",
45
     "webpack-dev-server": "^2.9.2"
47
     "webpack-dev-server": "^2.9.2"
46
   },
48
   },

+ 0 - 13
frontend_app_thread/rebuild_thread.sh View File

1
-#!/bin/bash
2
-
3
-. ../bash_library.sh # source bash_library.sh
4
-
5
-windoz=""
6
-if  [[ $1 = "-w" ]]; then
7
-    windoz="windoz"
8
-fi
9
-
10
-log "npm run build$windoz"
11
-npm run build$windoz
12
-log "cp dist/thread.app.js ../frontend/dist/app"
13
-cp dist/thread.app.js ../frontend/dist/app

+ 38 - 5
frontend_app_thread/src/container/PopupCreateThread.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2
 import {
3
 import {
4
+  addAllResourceI18n,
3
   CardPopupCreateContent,
5
   CardPopupCreateContent,
4
   handleFetchResult
6
   handleFetchResult
5
 } from 'tracim_frontend_lib'
7
 } from 'tracim_frontend_lib'
6
 import { postThreadContent } from '../action.async.js'
8
 import { postThreadContent } from '../action.async.js'
9
+import i18n from '../i18n.js'
7
 
10
 
8
 const debug = { // outdated
11
 const debug = { // outdated
9
   config: {
12
   config: {
10
-    label: 'Thread',
13
+    label: 'PopupCreateThread',
11
     slug: 'thread',
14
     slug: 'thread',
12
     faIcon: 'file-text-o',
15
     faIcon: 'file-text-o',
13
     hexcolor: '#ad4cf9',
16
     hexcolor: '#ad4cf9',
19
       'Accept': 'application/json',
22
       'Accept': 'application/json',
20
       'Content-Type': 'application/json',
23
       'Content-Type': 'application/json',
21
       'Authorization': 'Basic ' + btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
24
       'Authorization': 'Basic ' + btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
25
+    },
26
+    translation: {
27
+      en: {
28
+        translation: {}
29
+      },
30
+      fr: {
31
+        translation: {}
32
+      }
22
     }
33
     }
23
   },
34
   },
24
   loggedUser: {
35
   loggedUser: {
33
   idFolder: null
44
   idFolder: null
34
 }
45
 }
35
 
46
 
36
-class PopupCreateHtmlDocument extends React.Component {
47
+class PopupCreateThread extends React.Component {
37
   constructor (props) {
48
   constructor (props) {
38
     super(props)
49
     super(props)
39
     this.state = {
50
     this.state = {
40
-      appName: 'thread',
51
+      appName: 'thread', // must remain 'thread' because it is the name of the react built app (which contains Threac and PopupCreateThread)
41
       config: props.data ? props.data.config : debug.config,
52
       config: props.data ? props.data.config : debug.config,
42
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
53
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
43
       idWorkspace: props.data ? props.data.idWorkspace : debug.idWorkspace,
54
       idWorkspace: props.data ? props.data.idWorkspace : debug.idWorkspace,
44
       idFolder: props.data ? props.data.idFolder : debug.idFolder,
55
       idFolder: props.data ? props.data.idFolder : debug.idFolder,
45
       newContentName: ''
56
       newContentName: ''
46
     }
57
     }
58
+
59
+    // i18n has been init, add resources from frontend
60
+    addAllResourceI18n(i18n, this.state.config.translation)
61
+    i18n.changeLanguage(this.state.loggedUser.lang)
62
+
63
+    document.addEventListener('appCustomEvent', this.customEventReducer)
64
+  }
65
+
66
+  customEventReducer = ({ detail: { type, data } }) => { // action: { type: '', data: {} }
67
+    switch (type) {
68
+      case 'allApp_changeLang':
69
+        console.log('%c<PopupCreateThread> Custom event', 'color: #28a745', type, data)
70
+        this.setState(prev => ({
71
+          loggedUser: {
72
+            ...prev.loggedUser,
73
+            lang: data
74
+          }
75
+        }))
76
+        i18n.changeLanguage(data)
77
+        break
78
+    }
47
   }
79
   }
48
 
80
 
49
   handleChangeNewContentName = e => this.setState({newContentName: e.target.value})
81
   handleChangeNewContentName = e => this.setState({newContentName: e.target.value})
89
         faIcon={this.state.config.faIcon}
121
         faIcon={this.state.config.faIcon}
90
         contentName={this.state.newContentName}
122
         contentName={this.state.newContentName}
91
         onChangeContentName={this.handleChangeNewContentName}
123
         onChangeContentName={this.handleChangeNewContentName}
92
-        btnValidateLabel='Valider et créer'
124
+        btnValidateLabel={this.props.t('Validate and create')}
125
+        inputPlaceholder={this.props.t("Topic's subject")}
93
       />
126
       />
94
     )
127
     )
95
   }
128
   }
96
 }
129
 }
97
 
130
 
98
-export default PopupCreateHtmlDocument
131
+export default translate()(PopupCreateThread)

+ 16 - 0
frontend_app_thread/src/container/Thread.jsx View File

2
 import i18n from '../i18n.js'
2
 import i18n from '../i18n.js'
3
 import { debug } from '../helper.js'
3
 import { debug } from '../helper.js'
4
 import {
4
 import {
5
+  addAllResourceI18n,
5
   handleFetchResult,
6
   handleFetchResult,
6
   PopinFixed,
7
   PopinFixed,
7
   PopinFixedHeader,
8
   PopinFixedHeader,
33
       timelineWysiwyg: false
34
       timelineWysiwyg: false
34
     }
35
     }
35
 
36
 
37
+    // i18n has been init, add resources from frontend
38
+    addAllResourceI18n(i18n, this.state.config.translation)
39
+    i18n.changeLanguage(this.state.loggedUser.lang)
40
+
36
     document.addEventListener('appCustomEvent', this.customEventReducer)
41
     document.addEventListener('appCustomEvent', this.customEventReducer)
37
   }
42
   }
38
 
43
 
49
       case 'thread_reloadContent':
54
       case 'thread_reloadContent':
50
         console.log('%c<Thread> Custom event', 'color: #28a745', type, data)
55
         console.log('%c<Thread> Custom event', 'color: #28a745', type, data)
51
         this.setState(prev => ({content: {...prev.content, ...data}, isVisible: true}))
56
         this.setState(prev => ({content: {...prev.content, ...data}, isVisible: true}))
57
+        break
58
+      case 'allApp_changeLang':
59
+        console.log('%c<Thread> Custom event', 'color: #28a745', type, data)
60
+        this.setState(prev => ({
61
+          loggedUser: {
62
+            ...prev.loggedUser,
63
+            lang: data
64
+          }
65
+        }))
66
+        i18n.changeLanguage(data)
67
+        break
52
     }
68
     }
53
   }
69
   }
54
 
70
 

+ 2 - 2
frontend_app_thread/src/helper.js View File

5
   }
5
   }
6
 }
6
 }
7
 
7
 
8
-export const debug = { // copied from html-documents => outdated
8
+export const debug = { // copied from html-document => outdated
9
   config: {
9
   config: {
10
     label: 'Thread',
10
     label: 'Thread',
11
     slug: 'thread',
11
     slug: 'thread',
78
     show_in_ui: true,
78
     show_in_ui: true,
79
     slug: 'current-menu',
79
     slug: 'current-menu',
80
     status: 'open',
80
     status: 'open',
81
-    sub_content_types: ['thread', 'html-documents', 'file', 'folder'],
81
+    sub_content_types: ['thread', 'html-document', 'file', 'folder'],
82
     workspace_id: 1
82
     workspace_id: 1
83
   },
83
   },
84
   timeline: [] // timelineDebugData
84
   timeline: [] // timelineDebugData

+ 1 - 11
frontend_app_thread/src/i18n.js View File

1
 import i18n from 'i18next'
1
 import i18n from 'i18next'
2
 import { reactI18nextModule } from 'react-i18next'
2
 import { reactI18nextModule } from 'react-i18next'
3
-import { langFr, langEn } from 'tracim_frontend_lib'
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
6
 
3
 
7
 i18n
4
 i18n
8
   .use(reactI18nextModule)
5
   .use(reactI18nextModule)
18
     react: {
15
     react: {
19
       wait: true
16
       wait: true
20
     },
17
     },
21
-    resources: {
22
-      en: {
23
-        translation: {...langEn.translation, ...en.translation}
24
-      },
25
-      fr: {
26
-        translation: {...langFr.translation, ...fr.translation}
27
-      }
28
-    }
18
+    resources: {} // init with empty resources, they will come from frontend in app constructor
29
   })
19
   })
30
 
20
 
31
 export default i18n
21
 export default i18n

+ 1 - 1
frontend_app_thread/src/index.js View File

6
 require('./css/index.styl')
6
 require('./css/index.styl')
7
 
7
 
8
 const appInterface = {
8
 const appInterface = {
9
-  name: 'Thread',
9
+  name: 'thread',
10
   isRendered: false,
10
   isRendered: false,
11
   renderAppFull: data => {
11
   renderAppFull: data => {
12
     return ReactDOM.render(
12
     return ReactDOM.render(

+ 0 - 9
frontend_app_thread/src/translate/en.js View File

1
-const en = {
2
-  translation: { // 'en' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'New version'
5
-    }
6
-  }
7
-}
8
-
9
-export default en

+ 0 - 9
frontend_app_thread/src/translate/fr.js View File

1
-const fr = {
2
-  translation: { // 'fr' in the namespace 'translation'
3
-    PopinFixedOption: {
4
-      new_version: 'nouvelle version'
5
-    }
6
-  }
7
-}
8
-
9
-export default fr

+ 9 - 0
frontend_lib/i18next.scanner.js View File

1
+const scanner = require('i18next-scanner')
2
+const vfs = require('vinyl-fs')
3
+
4
+const option = require('../i18next.option.js')
5
+
6
+vfs.src(['./src/**/*.jsx'])
7
+// .pipe(sort()) // Sort files in stream by path
8
+  .pipe(scanner(option))
9
+  .pipe(vfs.dest('./i18next.scanner'))

+ 1 - 0
frontend_lib/i18next.scanner/en/translation.json View File

1
+{}

+ 1 - 0
frontend_lib/i18next.scanner/fr/translation.json View File

1
+{}

+ 3 - 1
frontend_lib/package.json View File

9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9870 -- webpack-dev-server --watch --colors --inline --hot --progress",
9
     "servdev-dashboard": "NODE_ENV=development webpack-dashboard -m -p 9870 -- webpack-dev-server --watch --colors --inline --hot --progress",
10
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
10
     "buildwindoz": "set NODE_ENV=production&& webpack -p",
11
     "build": "NODE_ENV=production webpack -p",
11
     "build": "NODE_ENV=production webpack -p",
12
+    "build-translation": "node i18next.scanner.js",
12
     "buildtracimlib": "NODE_ENV=production webpack -p && echo '/* eslint-disable */' | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && printf '\n/* eslint-enable */\n' >> dist/tracim_frontend_lib.js",
13
     "buildtracimlib": "NODE_ENV=production webpack -p && echo '/* eslint-disable */' | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && printf '\n/* eslint-enable */\n' >> dist/tracim_frontend_lib.js",
13
-    "buildtracimlibwindoz": "set NODE_ENV=production&& webpack -p && echo '/* eslint-disable */' | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && printf '\n/* eslint-enable */\n' >> dist/tracim_frontend_lib.js",
14
+    "buildtracimlibwindoz": "set NODE_ENV=production&& webpack -p && echo /* eslint-disable */ | cat - dist/tracim_frontend_lib.js > temp && mv temp dist/tracim_frontend_lib.js && echo /* eslint-enable */>> dist/tracim_frontend_lib.js",
14
     "test": "echo \"Error: no test specified\" && exit 1"
15
     "test": "echo \"Error: no test specified\" && exit 1"
15
   },
16
   },
16
   "author": "",
17
   "author": "",
44
     "whatwg-fetch": "^2.0.3"
45
     "whatwg-fetch": "^2.0.3"
45
   },
46
   },
46
   "devDependencies": {
47
   "devDependencies": {
48
+    "i18next-scanner": "^2.6.1",
47
     "webpack-dashboard": "^1.1.1",
49
     "webpack-dashboard": "^1.1.1",
48
     "webpack-dev-server": "^2.9.2"
50
     "webpack-dev-server": "^2.9.2"
49
   },
51
   },

+ 3 - 2
frontend_lib/src/component/CardPopup/CardPopupCreateContent.jsx View File

27
           <input
27
           <input
28
             type='text'
28
             type='text'
29
             className='createcontent__form__input'
29
             className='createcontent__form__input'
30
-            placeHolder='Nommez votre contenu...'
30
+            placeHolder={props.inputPlaceholder}
31
             value={props.contentName}
31
             value={props.contentName}
32
             onChange={props.onChangeContentName}
32
             onChange={props.onChangeContentName}
33
           />
33
           />
64
   label: PropTypes.string,
64
   label: PropTypes.string,
65
   customColor: PropTypes.string,
65
   customColor: PropTypes.string,
66
   faIcon: PropTypes.string,
66
   faIcon: PropTypes.string,
67
-  btnValidateLabel: PropTypes.string
67
+  btnValidateLabel: PropTypes.string,
68
+  inputPlaceholder: PropTypes.string
68
 }
69
 }
69
 
70
 
70
 PopupCreateContent.defaultProps = {
71
 PopupCreateContent.defaultProps = {

+ 6 - 7
frontend_lib/src/component/Input/SelectStatus/SelectStatus.jsx View File

27
       </button>
27
       </button>
28
 
28
 
29
       <div className='selectStatus__submenu dropdown-menu' aria-labelledby='dropdownMenu2'>
29
       <div className='selectStatus__submenu dropdown-menu' aria-labelledby='dropdownMenu2'>
30
-        <h6 className='dropdown-header'>{props.t('Input.SelectStatus.file_status')}</h6>
31
-
32
-        <div className='dropdown-divider' />
33
-
34
         {props.availableStatus.map(s =>
30
         {props.availableStatus.map(s =>
35
           <button
31
           <button
36
             className='selectStatus__submenu__item current dropdown-item'
32
             className='selectStatus__submenu__item current dropdown-item'
37
             type='button'
33
             type='button'
38
             onClick={() => props.onChangeStatus(s.slug)}
34
             onClick={() => props.onChangeStatus(s.slug)}
39
             key={`status_${s.slug}`}
35
             key={`status_${s.slug}`}
40
-            style={{color: s.hexcolor}}
36
+            // style={{color: s.hexcolor}}
41
           >
37
           >
42
-            {s.label /* props.t('Input.SelectStatus.ongoing') */}
38
+            {s.label}
43
             <div className='selectStatus__submenu__item__icon'>
39
             <div className='selectStatus__submenu__item__icon'>
44
-              <i className={`fa fa-fw fa-${s.faIcon}`} />
40
+              <i
41
+                className={`fa fa-fw fa-${s.faIcon}`}
42
+                style={{color: s.hexcolor}}
43
+              />
45
             </div>
44
             </div>
46
           </button>
45
           </button>
47
         )}
46
         )}

+ 8 - 11
frontend_lib/src/component/Timeline/Comment.jsx View File

25
           {props.avatar ? <img src={props.avatar} /> : ''}
25
           {props.avatar ? <img src={props.avatar} /> : ''}
26
         </div>
26
         </div>
27
       </div>
27
       </div>
28
-      <div
29
-        className={classnames(`${props.customClass}__messagelist__item__authorandhour`, 'timeline__body__messagelist__item__authorandhour')}>
30
-          <div className='mr-5'>
31
-            {props.createdAt}
32
-          </div>
28
+      <div className={classnames(`${props.customClass}__messagelist__item__authorandhour`, 'timeline__body__messagelist__item__authorandhour')}>
29
+        <div className={classnames(`${props.customClass}__messagelist__item__authorandhour__author`, 'timeline__body__messagelist__item__authorandhour__author')}>
33
           {props.author}
30
           {props.author}
31
+        </div>
32
+        <div className={classnames(`${props.customClass}__messagelist__item__authorandhour__date`, 'timeline__body__messagelist__item__authorandhour__date')}>
33
+          {props.createdAt}
34
+        </div>
34
       </div>
35
       </div>
35
       <div
36
       <div
36
         className={classnames(`${props.customClass}__messagelist__item__content`, 'timeline__body__messagelist__item__content')}
37
         className={classnames(`${props.customClass}__messagelist__item__content`, 'timeline__body__messagelist__item__content')}
37
         style={props.fromMe ? styleSent : styleReceived}
38
         style={props.fromMe ? styleSent : styleReceived}
38
-
39
-      >
40
-        <div className='timeline__body__messagelist__item__content__text'>
41
-          {props.text}
42
-        </div>
43
-      </div>
39
+        dangerouslySetInnerHTML={{__html: props.text}}
40
+      />
44
     </li>
41
     </li>
45
   )
42
   )
46
 }
43
 }

+ 1 - 1
frontend_lib/src/component/Timeline/Timeline.jsx View File

107
                   }}
107
                   }}
108
                   key={'timeline__comment__advancedtext'}
108
                   key={'timeline__comment__advancedtext'}
109
                 >
109
                 >
110
-                  {props.wysiwyg ? 'Texte Simple' : 'Texte Avancé'}
110
+                  {props.wysiwyg ? 'Texte Simple' : 'Texte Riche'}
111
                 </button>
111
                 </button>
112
               </div>
112
               </div>
113
 
113
 

+ 3 - 6
frontend_lib/src/component/Timeline/Timeline.styl View File

36
         padding 0 25px 0 35px
36
         padding 0 25px 0 35px
37
         &__avatar
37
         &__avatar
38
           position relative
38
           position relative
39
-          top 40px
39
+          top 60px
40
           left -20px
40
           left -20px
41
           border-radius 50%
41
           border-radius 50%
42
           width 45px
42
           width 45px
46
             height 45px
46
             height 45px
47
             border-radius 25px
47
             border-radius 25px
48
         &__authorandhour
48
         &__authorandhour
49
-          display flex
50
-          align-items center
51
-          justify-content flex-end
52
-          margin-right 35px
49
+          margin-left 35px
53
           opacity 0.7
50
           opacity 0.7
54
           font-size 14px
51
           font-size 14px
55
         &__content
52
         &__content
118
       flex-direction row-reverse
115
       flex-direction row-reverse
119
     &__avatar
116
     &__avatar
120
       left 20px
117
       left 20px
121
-    &__createhour
118
+    &__authorandhour
122
       margin-left 0
119
       margin-left 0
123
       margin-right 35px
120
       margin-right 35px
124
 
121
 

+ 84 - 84
frontend_lib/src/component/Timeline/debugData.js View File

1
 export const TimelineDebugData = [{
1
 export const TimelineDebugData = [{
2
   'is_deleted': false,
2
   'is_deleted': false,
3
   'is_archived': false,
3
   'is_archived': false,
4
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
4
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
5
   'raw_content': '',
5
   'raw_content': '',
6
   'status': 'open',
6
   'status': 'open',
7
   'content_id': 22,
7
   'content_id': 22,
11
   'created': '21/06/2018 à 15:12:40',
11
   'created': '21/06/2018 à 15:12:40',
12
   'slug': 'bonjour',
12
   'slug': 'bonjour',
13
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
13
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
14
-  'content_type': 'html-documents',
14
+  'content_type': 'html-document',
15
   'workspace_id': 1,
15
   'workspace_id': 1,
16
   'revision_id': 22,
16
   'revision_id': 22,
17
   'label': 'bonjour?',
17
   'label': 'bonjour?',
21
 }, {
21
 }, {
22
   'is_deleted': false,
22
   'is_deleted': false,
23
   'is_archived': false,
23
   'is_archived': false,
24
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
24
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
25
   'raw_content': 'bonjour.',
25
   'raw_content': 'bonjour.',
26
   'status': 'open',
26
   'status': 'open',
27
   'content_id': 22,
27
   'content_id': 22,
31
   'created': '28/06/2018 à 15:13:26',
31
   'created': '28/06/2018 à 15:13:26',
32
   'slug': 'bonjour',
32
   'slug': 'bonjour',
33
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
33
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
34
-  'content_type': 'html-documents',
34
+  'content_type': 'html-document',
35
   'workspace_id': 1,
35
   'workspace_id': 1,
36
   'revision_id': 30,
36
   'revision_id': 30,
37
   'label': 'bonjour?',
37
   'label': 'bonjour?',
71
 }, {
71
 }, {
72
   'is_deleted': false,
72
   'is_deleted': false,
73
   'is_archived': false,
73
   'is_archived': false,
74
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
74
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
75
   'raw_content': 'bonjour.\nmerci.',
75
   'raw_content': 'bonjour.\nmerci.',
76
   'status': 'open',
76
   'status': 'open',
77
   'content_id': 22,
77
   'content_id': 22,
81
   'created': '28/06/2018 à 15:43:51',
81
   'created': '28/06/2018 à 15:43:51',
82
   'slug': 'bonjour',
82
   'slug': 'bonjour',
83
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
83
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
84
-  'content_type': 'html-documents',
84
+  'content_type': 'html-document',
85
   'workspace_id': 1,
85
   'workspace_id': 1,
86
   'revision_id': 33,
86
   'revision_id': 33,
87
   'label': 'bonjour?',
87
   'label': 'bonjour?',
106
 }, {
106
 }, {
107
   'is_deleted': false,
107
   'is_deleted': false,
108
   'is_archived': false,
108
   'is_archived': false,
109
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
109
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
110
   'raw_content': 'bonjour.\nmerci.',
110
   'raw_content': 'bonjour.\nmerci.',
111
   'status': 'open',
111
   'status': 'open',
112
   'content_id': 22,
112
   'content_id': 22,
116
   'created': '28/06/2018 à 16:01:41',
116
   'created': '28/06/2018 à 16:01:41',
117
   'slug': 'current-menu2',
117
   'slug': 'current-menu2',
118
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
118
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
119
-  'content_type': 'html-documents',
119
+  'content_type': 'html-document',
120
   'workspace_id': 1,
120
   'workspace_id': 1,
121
   'revision_id': 35,
121
   'revision_id': 35,
122
   'label': 'Current Menu2',
122
   'label': 'Current Menu2',
126
 }, {
126
 }, {
127
   'is_deleted': false,
127
   'is_deleted': false,
128
   'is_archived': false,
128
   'is_archived': false,
129
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
129
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
130
   'raw_content': 'bonjour.\nmerci.',
130
   'raw_content': 'bonjour.\nmerci.',
131
   'status': 'open',
131
   'status': 'open',
132
   'content_id': 22,
132
   'content_id': 22,
136
   'created': '28/06/2018 à 16:06:53',
136
   'created': '28/06/2018 à 16:06:53',
137
   'slug': 'current-menu25',
137
   'slug': 'current-menu25',
138
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
138
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
139
-  'content_type': 'html-documents',
139
+  'content_type': 'html-document',
140
   'workspace_id': 1,
140
   'workspace_id': 1,
141
   'revision_id': 36,
141
   'revision_id': 36,
142
   'label': 'Current Menu25',
142
   'label': 'Current Menu25',
146
 }, {
146
 }, {
147
   'is_deleted': false,
147
   'is_deleted': false,
148
   'is_archived': false,
148
   'is_archived': false,
149
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
149
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
150
   'raw_content': 'bonjour.\nmerci.',
150
   'raw_content': 'bonjour.\nmerci.',
151
   'status': 'open',
151
   'status': 'open',
152
   'content_id': 22,
152
   'content_id': 22,
156
   'created': '28/06/2018 à 16:23:30',
156
   'created': '28/06/2018 à 16:23:30',
157
   'slug': '',
157
   'slug': '',
158
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
158
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
159
-  'content_type': 'html-documents',
159
+  'content_type': 'html-document',
160
   'workspace_id': 1,
160
   'workspace_id': 1,
161
   'revision_id': 37,
161
   'revision_id': 37,
162
   'label': '',
162
   'label': '',
166
 }, {
166
 }, {
167
   'is_deleted': false,
167
   'is_deleted': false,
168
   'is_archived': false,
168
   'is_archived': false,
169
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
169
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
170
   'raw_content': 'bonjour.\nmerci.',
170
   'raw_content': 'bonjour.\nmerci.',
171
   'status': 'open',
171
   'status': 'open',
172
   'content_id': 22,
172
   'content_id': 22,
176
   'created': '28/06/2018 à 16:23:43',
176
   'created': '28/06/2018 à 16:23:43',
177
   'slug': 'woot',
177
   'slug': 'woot',
178
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
178
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
179
-  'content_type': 'html-documents',
179
+  'content_type': 'html-document',
180
   'workspace_id': 1,
180
   'workspace_id': 1,
181
   'revision_id': 38,
181
   'revision_id': 38,
182
   'label': 'woot',
182
   'label': 'woot',
186
 }, {
186
 }, {
187
   'is_deleted': false,
187
   'is_deleted': false,
188
   'is_archived': false,
188
   'is_archived': false,
189
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
189
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
190
   'raw_content': 'bonjour.\nmerci.',
190
   'raw_content': 'bonjour.\nmerci.',
191
   'status': 'open',
191
   'status': 'open',
192
   'content_id': 22,
192
   'content_id': 22,
196
   'created': '28/06/2018 à 16:35:18',
196
   'created': '28/06/2018 à 16:35:18',
197
   'slug': 'woot2',
197
   'slug': 'woot2',
198
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
198
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
199
-  'content_type': 'html-documents',
199
+  'content_type': 'html-document',
200
   'workspace_id': 1,
200
   'workspace_id': 1,
201
   'revision_id': 39,
201
   'revision_id': 39,
202
   'label': 'woot2',
202
   'label': 'woot2',
206
 }, {
206
 }, {
207
   'is_deleted': false,
207
   'is_deleted': false,
208
   'is_archived': false,
208
   'is_archived': false,
209
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
209
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
210
   'raw_content': 'bonjour.\nmerci.',
210
   'raw_content': 'bonjour.\nmerci.',
211
   'status': 'open',
211
   'status': 'open',
212
   'content_id': 22,
212
   'content_id': 22,
216
   'created': '28/06/2018 à 16:36:10',
216
   'created': '28/06/2018 à 16:36:10',
217
   'slug': 'woot3',
217
   'slug': 'woot3',
218
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
218
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
219
-  'content_type': 'html-documents',
219
+  'content_type': 'html-document',
220
   'workspace_id': 1,
220
   'workspace_id': 1,
221
   'revision_id': 40,
221
   'revision_id': 40,
222
   'label': 'woot3',
222
   'label': 'woot3',
226
 }, {
226
 }, {
227
   'is_deleted': false,
227
   'is_deleted': false,
228
   'is_archived': false,
228
   'is_archived': false,
229
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
229
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
230
   'raw_content': 'bonjour.\nmerci.',
230
   'raw_content': 'bonjour.\nmerci.',
231
   'status': 'open',
231
   'status': 'open',
232
   'content_id': 22,
232
   'content_id': 22,
236
   'created': '28/06/2018 à 16:37:21',
236
   'created': '28/06/2018 à 16:37:21',
237
   'slug': 'woot4',
237
   'slug': 'woot4',
238
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
238
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
239
-  'content_type': 'html-documents',
239
+  'content_type': 'html-document',
240
   'workspace_id': 1,
240
   'workspace_id': 1,
241
   'revision_id': 41,
241
   'revision_id': 41,
242
   'label': 'woot4',
242
   'label': 'woot4',
246
 }, {
246
 }, {
247
   'is_deleted': false,
247
   'is_deleted': false,
248
   'is_archived': false,
248
   'is_archived': false,
249
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
249
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
250
   'raw_content': 'bonjour.\nmerci.\nbravo.',
250
   'raw_content': 'bonjour.\nmerci.\nbravo.',
251
   'status': 'open',
251
   'status': 'open',
252
   'content_id': 22,
252
   'content_id': 22,
256
   'created': '28/06/2018 à 16:37:36',
256
   'created': '28/06/2018 à 16:37:36',
257
   'slug': 'woot4',
257
   'slug': 'woot4',
258
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
258
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
259
-  'content_type': 'html-documents',
259
+  'content_type': 'html-document',
260
   'workspace_id': 1,
260
   'workspace_id': 1,
261
   'revision_id': 42,
261
   'revision_id': 42,
262
   'label': 'woot4',
262
   'label': 'woot4',
266
 }, {
266
 }, {
267
   'is_deleted': false,
267
   'is_deleted': false,
268
   'is_archived': false,
268
   'is_archived': false,
269
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
269
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
270
   'raw_content': 'bonjour.\nmerci.\nbravo.',
270
   'raw_content': 'bonjour.\nmerci.\nbravo.',
271
   'status': 'closed-validated',
271
   'status': 'closed-validated',
272
   'content_id': 22,
272
   'content_id': 22,
276
   'created': '29/06/2018 à 16:04:02',
276
   'created': '29/06/2018 à 16:04:02',
277
   'slug': 'woot4',
277
   'slug': 'woot4',
278
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
278
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
279
-  'content_type': 'html-documents',
279
+  'content_type': 'html-document',
280
   'workspace_id': 1,
280
   'workspace_id': 1,
281
   'revision_id': 46,
281
   'revision_id': 46,
282
   'label': 'woot4',
282
   'label': 'woot4',
286
 }, {
286
 }, {
287
   'is_deleted': false,
287
   'is_deleted': false,
288
   'is_archived': false,
288
   'is_archived': false,
289
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
289
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
290
   'raw_content': 'bonjour.\nmerci.\nbravo.',
290
   'raw_content': 'bonjour.\nmerci.\nbravo.',
291
   'status': 'closed-deprecated',
291
   'status': 'closed-deprecated',
292
   'content_id': 22,
292
   'content_id': 22,
296
   'created': '29/06/2018 à 16:05:32',
296
   'created': '29/06/2018 à 16:05:32',
297
   'slug': 'woot4',
297
   'slug': 'woot4',
298
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
298
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
299
-  'content_type': 'html-documents',
299
+  'content_type': 'html-document',
300
   'workspace_id': 1,
300
   'workspace_id': 1,
301
   'revision_id': 47,
301
   'revision_id': 47,
302
   'label': 'woot4',
302
   'label': 'woot4',
306
 }, {
306
 }, {
307
   'is_deleted': false,
307
   'is_deleted': false,
308
   'is_archived': false,
308
   'is_archived': false,
309
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
309
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
310
   'raw_content': 'bonjour.\nmerci.\nbravo.',
310
   'raw_content': 'bonjour.\nmerci.\nbravo.',
311
   'status': 'open',
311
   'status': 'open',
312
   'content_id': 22,
312
   'content_id': 22,
316
   'created': '29/06/2018 à 16:06:35',
316
   'created': '29/06/2018 à 16:06:35',
317
   'slug': 'woot4',
317
   'slug': 'woot4',
318
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
318
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
319
-  'content_type': 'html-documents',
319
+  'content_type': 'html-document',
320
   'workspace_id': 1,
320
   'workspace_id': 1,
321
   'revision_id': 48,
321
   'revision_id': 48,
322
   'label': 'woot4',
322
   'label': 'woot4',
326
 }, {
326
 }, {
327
   'is_deleted': false,
327
   'is_deleted': false,
328
   'is_archived': false,
328
   'is_archived': false,
329
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
329
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
330
   'raw_content': 'bonjour.\nmerci.\nbravo.',
330
   'raw_content': 'bonjour.\nmerci.\nbravo.',
331
   'status': 'closed-deprecated',
331
   'status': 'closed-deprecated',
332
   'content_id': 22,
332
   'content_id': 22,
336
   'created': '29/06/2018 à 16:08:36',
336
   'created': '29/06/2018 à 16:08:36',
337
   'slug': 'woot4',
337
   'slug': 'woot4',
338
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
338
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
339
-  'content_type': 'html-documents',
339
+  'content_type': 'html-document',
340
   'workspace_id': 1,
340
   'workspace_id': 1,
341
   'revision_id': 49,
341
   'revision_id': 49,
342
   'label': 'woot4',
342
   'label': 'woot4',
346
 }, {
346
 }, {
347
   'is_deleted': false,
347
   'is_deleted': false,
348
   'is_archived': false,
348
   'is_archived': false,
349
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
349
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
350
   'raw_content': 'bonjour.\nmerci.\nbravo.',
350
   'raw_content': 'bonjour.\nmerci.\nbravo.',
351
   'status': 'open',
351
   'status': 'open',
352
   'content_id': 22,
352
   'content_id': 22,
356
   'created': '29/06/2018 à 16:13:15',
356
   'created': '29/06/2018 à 16:13:15',
357
   'slug': 'woot4',
357
   'slug': 'woot4',
358
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
358
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
359
-  'content_type': 'html-documents',
359
+  'content_type': 'html-document',
360
   'workspace_id': 1,
360
   'workspace_id': 1,
361
   'revision_id': 50,
361
   'revision_id': 50,
362
   'label': 'woot4',
362
   'label': 'woot4',
366
 }, {
366
 }, {
367
   'is_deleted': false,
367
   'is_deleted': false,
368
   'is_archived': false,
368
   'is_archived': false,
369
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
369
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
370
   'raw_content': 'bonjour.\nmerci.\nbravo.',
370
   'raw_content': 'bonjour.\nmerci.\nbravo.',
371
   'status': 'closed-validated',
371
   'status': 'closed-validated',
372
   'content_id': 22,
372
   'content_id': 22,
376
   'created': '29/06/2018 à 16:13:19',
376
   'created': '29/06/2018 à 16:13:19',
377
   'slug': 'woot4',
377
   'slug': 'woot4',
378
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
378
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
379
-  'content_type': 'html-documents',
379
+  'content_type': 'html-document',
380
   'workspace_id': 1,
380
   'workspace_id': 1,
381
   'revision_id': 51,
381
   'revision_id': 51,
382
   'label': 'woot4',
382
   'label': 'woot4',
386
 }, {
386
 }, {
387
   'is_deleted': false,
387
   'is_deleted': false,
388
   'is_archived': false,
388
   'is_archived': false,
389
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
389
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
390
   'raw_content': 'bonjour.\nmerci.\nbravo.',
390
   'raw_content': 'bonjour.\nmerci.\nbravo.',
391
   'status': 'open',
391
   'status': 'open',
392
   'content_id': 22,
392
   'content_id': 22,
396
   'created': '29/06/2018 à 16:14:34',
396
   'created': '29/06/2018 à 16:14:34',
397
   'slug': 'woot4',
397
   'slug': 'woot4',
398
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
398
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
399
-  'content_type': 'html-documents',
399
+  'content_type': 'html-document',
400
   'workspace_id': 1,
400
   'workspace_id': 1,
401
   'revision_id': 52,
401
   'revision_id': 52,
402
   'label': 'woot4',
402
   'label': 'woot4',
406
 }, {
406
 }, {
407
   'is_deleted': false,
407
   'is_deleted': false,
408
   'is_archived': false,
408
   'is_archived': false,
409
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
409
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
410
   'raw_content': 'bonjour.\nmerci.\nbravo.',
410
   'raw_content': 'bonjour.\nmerci.\nbravo.',
411
   'status': 'closed-unvalidated',
411
   'status': 'closed-unvalidated',
412
   'content_id': 22,
412
   'content_id': 22,
416
   'created': '29/06/2018 à 16:14:38',
416
   'created': '29/06/2018 à 16:14:38',
417
   'slug': 'woot4',
417
   'slug': 'woot4',
418
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
418
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
419
-  'content_type': 'html-documents',
419
+  'content_type': 'html-document',
420
   'workspace_id': 1,
420
   'workspace_id': 1,
421
   'revision_id': 53,
421
   'revision_id': 53,
422
   'label': 'woot4',
422
   'label': 'woot4',
426
 }, {
426
 }, {
427
   'is_deleted': false,
427
   'is_deleted': false,
428
   'is_archived': false,
428
   'is_archived': false,
429
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
429
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
430
   'raw_content': 'bonjour.\nmerci.\nbravo.',
430
   'raw_content': 'bonjour.\nmerci.\nbravo.',
431
   'status': 'closed-deprecated',
431
   'status': 'closed-deprecated',
432
   'content_id': 22,
432
   'content_id': 22,
436
   'created': '29/06/2018 à 16:15:29',
436
   'created': '29/06/2018 à 16:15:29',
437
   'slug': 'woot4',
437
   'slug': 'woot4',
438
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
438
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
439
-  'content_type': 'html-documents',
439
+  'content_type': 'html-document',
440
   'workspace_id': 1,
440
   'workspace_id': 1,
441
   'revision_id': 54,
441
   'revision_id': 54,
442
   'label': 'woot4',
442
   'label': 'woot4',
446
 }, {
446
 }, {
447
   'is_deleted': false,
447
   'is_deleted': false,
448
   'is_archived': false,
448
   'is_archived': false,
449
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
449
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
450
   'raw_content': 'bonjour.\nmerci.\nbravo.',
450
   'raw_content': 'bonjour.\nmerci.\nbravo.',
451
   'status': 'open',
451
   'status': 'open',
452
   'content_id': 22,
452
   'content_id': 22,
456
   'created': '29/06/2018 à 16:15:43',
456
   'created': '29/06/2018 à 16:15:43',
457
   'slug': 'woot4',
457
   'slug': 'woot4',
458
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
458
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
459
-  'content_type': 'html-documents',
459
+  'content_type': 'html-document',
460
   'workspace_id': 1,
460
   'workspace_id': 1,
461
   'revision_id': 55,
461
   'revision_id': 55,
462
   'label': 'woot4',
462
   'label': 'woot4',
466
 }, {
466
 }, {
467
   'is_deleted': false,
467
   'is_deleted': false,
468
   'is_archived': false,
468
   'is_archived': false,
469
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
469
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
470
   'raw_content': 'bonjour.\nmerci.\nbravo.',
470
   'raw_content': 'bonjour.\nmerci.\nbravo.',
471
   'status': 'closed-unvalidated',
471
   'status': 'closed-unvalidated',
472
   'content_id': 22,
472
   'content_id': 22,
476
   'created': '29/06/2018 à 16:15:53',
476
   'created': '29/06/2018 à 16:15:53',
477
   'slug': 'woot4',
477
   'slug': 'woot4',
478
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
478
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
479
-  'content_type': 'html-documents',
479
+  'content_type': 'html-document',
480
   'workspace_id': 1,
480
   'workspace_id': 1,
481
   'revision_id': 56,
481
   'revision_id': 56,
482
   'label': 'woot4',
482
   'label': 'woot4',
486
 }, {
486
 }, {
487
   'is_deleted': false,
487
   'is_deleted': false,
488
   'is_archived': false,
488
   'is_archived': false,
489
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
489
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
490
   'raw_content': 'bonjour.\nmerci.\nbravo.',
490
   'raw_content': 'bonjour.\nmerci.\nbravo.',
491
   'status': 'closed-deprecated',
491
   'status': 'closed-deprecated',
492
   'content_id': 22,
492
   'content_id': 22,
496
   'created': '29/06/2018 à 16:15:55',
496
   'created': '29/06/2018 à 16:15:55',
497
   'slug': 'woot4',
497
   'slug': 'woot4',
498
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
498
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
499
-  'content_type': 'html-documents',
499
+  'content_type': 'html-document',
500
   'workspace_id': 1,
500
   'workspace_id': 1,
501
   'revision_id': 57,
501
   'revision_id': 57,
502
   'label': 'woot4',
502
   'label': 'woot4',
506
 }, {
506
 }, {
507
   'is_deleted': false,
507
   'is_deleted': false,
508
   'is_archived': false,
508
   'is_archived': false,
509
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
509
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
510
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.</p>',
510
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.</p>',
511
   'status': 'closed-deprecated',
511
   'status': 'closed-deprecated',
512
   'content_id': 22,
512
   'content_id': 22,
516
   'created': '02/07/2018 à 12:46:16',
516
   'created': '02/07/2018 à 12:46:16',
517
   'slug': 'woot4',
517
   'slug': 'woot4',
518
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
518
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
519
-  'content_type': 'html-documents',
519
+  'content_type': 'html-document',
520
   'workspace_id': 1,
520
   'workspace_id': 1,
521
   'revision_id': 58,
521
   'revision_id': 58,
522
   'label': 'woot4',
522
   'label': 'woot4',
631
 }, {
631
 }, {
632
   'is_deleted': false,
632
   'is_deleted': false,
633
   'is_archived': false,
633
   'is_archived': false,
634
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
634
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
635
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1</p>',
635
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1</p>',
636
   'status': 'closed-deprecated',
636
   'status': 'closed-deprecated',
637
   'content_id': 22,
637
   'content_id': 22,
641
   'created': '02/07/2018 à 15:48:04',
641
   'created': '02/07/2018 à 15:48:04',
642
   'slug': 'woot4',
642
   'slug': 'woot4',
643
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
643
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
644
-  'content_type': 'html-documents',
644
+  'content_type': 'html-document',
645
   'workspace_id': 1,
645
   'workspace_id': 1,
646
   'revision_id': 66,
646
   'revision_id': 66,
647
   'label': 'woot4',
647
   'label': 'woot4',
651
 }, {
651
 }, {
652
   'is_deleted': false,
652
   'is_deleted': false,
653
   'is_archived': false,
653
   'is_archived': false,
654
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
654
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
655
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1</p>',
655
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1</p>',
656
   'status': 'open',
656
   'status': 'open',
657
   'content_id': 22,
657
   'content_id': 22,
661
   'created': '02/07/2018 à 15:49:32',
661
   'created': '02/07/2018 à 15:49:32',
662
   'slug': 'woot4',
662
   'slug': 'woot4',
663
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
663
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
664
-  'content_type': 'html-documents',
664
+  'content_type': 'html-document',
665
   'workspace_id': 1,
665
   'workspace_id': 1,
666
   'revision_id': 67,
666
   'revision_id': 67,
667
   'label': 'woot4',
667
   'label': 'woot4',
671
 }, {
671
 }, {
672
   'is_deleted': false,
672
   'is_deleted': false,
673
   'is_archived': false,
673
   'is_archived': false,
674
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
674
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
675
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>',
675
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>',
676
   'status': 'open',
676
   'status': 'open',
677
   'content_id': 22,
677
   'content_id': 22,
681
   'created': '02/07/2018 à 15:50:36',
681
   'created': '02/07/2018 à 15:50:36',
682
   'slug': 'woot4',
682
   'slug': 'woot4',
683
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
683
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
684
-  'content_type': 'html-documents',
684
+  'content_type': 'html-document',
685
   'workspace_id': 1,
685
   'workspace_id': 1,
686
   'revision_id': 68,
686
   'revision_id': 68,
687
   'label': 'woot4',
687
   'label': 'woot4',
691
 }, {
691
 }, {
692
   'is_deleted': false,
692
   'is_deleted': false,
693
   'is_archived': false,
693
   'is_archived': false,
694
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
694
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
695
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.123</p>',
695
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.123</p>',
696
   'status': 'open',
696
   'status': 'open',
697
   'content_id': 22,
697
   'content_id': 22,
701
   'created': '02/07/2018 à 15:54:17',
701
   'created': '02/07/2018 à 15:54:17',
702
   'slug': 'woot4',
702
   'slug': 'woot4',
703
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
703
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
704
-  'content_type': 'html-documents',
704
+  'content_type': 'html-document',
705
   'workspace_id': 1,
705
   'workspace_id': 1,
706
   'revision_id': 69,
706
   'revision_id': 69,
707
   'label': 'woot4',
707
   'label': 'woot4',
711
 }, {
711
 }, {
712
   'is_deleted': false,
712
   'is_deleted': false,
713
   'is_archived': false,
713
   'is_archived': false,
714
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
714
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
715
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1234</p>',
715
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.1234</p>',
716
   'status': 'open',
716
   'status': 'open',
717
   'content_id': 22,
717
   'content_id': 22,
721
   'created': '02/07/2018 à 15:54:22',
721
   'created': '02/07/2018 à 15:54:22',
722
   'slug': 'woot4',
722
   'slug': 'woot4',
723
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
723
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
724
-  'content_type': 'html-documents',
724
+  'content_type': 'html-document',
725
   'workspace_id': 1,
725
   'workspace_id': 1,
726
   'revision_id': 70,
726
   'revision_id': 70,
727
   'label': 'woot4',
727
   'label': 'woot4',
731
 }, {
731
 }, {
732
   'is_deleted': false,
732
   'is_deleted': false,
733
   'is_archived': false,
733
   'is_archived': false,
734
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
734
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
735
   'raw_content': '<p>end</p>',
735
   'raw_content': '<p>end</p>',
736
   'status': 'open',
736
   'status': 'open',
737
   'content_id': 22,
737
   'content_id': 22,
741
   'created': '02/07/2018 à 17:24:41',
741
   'created': '02/07/2018 à 17:24:41',
742
   'slug': 'woot4',
742
   'slug': 'woot4',
743
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
743
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
744
-  'content_type': 'html-documents',
744
+  'content_type': 'html-document',
745
   'workspace_id': 1,
745
   'workspace_id': 1,
746
   'revision_id': 71,
746
   'revision_id': 71,
747
   'label': 'woot4',
747
   'label': 'woot4',
751
 }, {
751
 }, {
752
   'is_deleted': false,
752
   'is_deleted': false,
753
   'is_archived': false,
753
   'is_archived': false,
754
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
754
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
755
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>',
755
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>',
756
   'status': 'open',
756
   'status': 'open',
757
   'content_id': 22,
757
   'content_id': 22,
761
   'created': '02/07/2018 à 17:43:28',
761
   'created': '02/07/2018 à 17:43:28',
762
   'slug': 'woot5',
762
   'slug': 'woot5',
763
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
763
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
764
-  'content_type': 'html-documents',
764
+  'content_type': 'html-document',
765
   'workspace_id': 1,
765
   'workspace_id': 1,
766
   'revision_id': 72,
766
   'revision_id': 72,
767
   'label': 'woot5',
767
   'label': 'woot5',
786
 }, {
786
 }, {
787
   'is_deleted': false,
787
   'is_deleted': false,
788
   'is_archived': false,
788
   'is_archived': false,
789
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
789
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
790
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>&nbsp;</p>',
790
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>&nbsp;</p>',
791
   'status': 'open',
791
   'status': 'open',
792
   'content_id': 22,
792
   'content_id': 22,
796
   'created': '04/07/2018 à 11:02:27',
796
   'created': '04/07/2018 à 11:02:27',
797
   'slug': 'woot5',
797
   'slug': 'woot5',
798
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
798
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
799
-  'content_type': 'html-documents',
799
+  'content_type': 'html-document',
800
   'workspace_id': 1,
800
   'workspace_id': 1,
801
   'revision_id': 76,
801
   'revision_id': 76,
802
   'label': 'woot5',
802
   'label': 'woot5',
806
 }, {
806
 }, {
807
   'is_deleted': false,
807
   'is_deleted': false,
808
   'is_archived': false,
808
   'is_archived': false,
809
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
809
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
810
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>',
810
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>',
811
   'status': 'open',
811
   'status': 'open',
812
   'content_id': 22,
812
   'content_id': 22,
816
   'created': '04/07/2018 à 11:03:53',
816
   'created': '04/07/2018 à 11:03:53',
817
   'slug': 'woot5',
817
   'slug': 'woot5',
818
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
818
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
819
-  'content_type': 'html-documents',
819
+  'content_type': 'html-document',
820
   'workspace_id': 1,
820
   'workspace_id': 1,
821
   'revision_id': 77,
821
   'revision_id': 77,
822
   'label': 'woot5',
822
   'label': 'woot5',
826
 }, {
826
 }, {
827
   'is_deleted': false,
827
   'is_deleted': false,
828
   'is_archived': false,
828
   'is_archived': false,
829
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
829
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
830
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>',
830
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>',
831
   'status': 'open',
831
   'status': 'open',
832
   'content_id': 22,
832
   'content_id': 22,
836
   'created': '04/07/2018 à 11:04:19',
836
   'created': '04/07/2018 à 11:04:19',
837
   'slug': 'woot5',
837
   'slug': 'woot5',
838
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
838
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
839
-  'content_type': 'html-documents',
839
+  'content_type': 'html-document',
840
   'workspace_id': 1,
840
   'workspace_id': 1,
841
   'revision_id': 78,
841
   'revision_id': 78,
842
   'label': 'woot5',
842
   'label': 'woot5',
861
 }, {
861
 }, {
862
   'is_deleted': false,
862
   'is_deleted': false,
863
   'is_archived': false,
863
   'is_archived': false,
864
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
864
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
865
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
865
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
866
   'status': 'open',
866
   'status': 'open',
867
   'content_id': 22,
867
   'content_id': 22,
871
   'created': '04/07/2018 à 11:04:39',
871
   'created': '04/07/2018 à 11:04:39',
872
   'slug': 'woot5',
872
   'slug': 'woot5',
873
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
873
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
874
-  'content_type': 'html-documents',
874
+  'content_type': 'html-document',
875
   'workspace_id': 1,
875
   'workspace_id': 1,
876
   'revision_id': 80,
876
   'revision_id': 80,
877
   'label': 'woot5',
877
   'label': 'woot5',
881
 }, {
881
 }, {
882
   'is_deleted': false,
882
   'is_deleted': false,
883
   'is_archived': false,
883
   'is_archived': false,
884
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
884
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
885
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
885
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
886
   'status': 'closed-validated',
886
   'status': 'closed-validated',
887
   'content_id': 22,
887
   'content_id': 22,
891
   'created': '04/07/2018 à 11:05:13',
891
   'created': '04/07/2018 à 11:05:13',
892
   'slug': 'woot5',
892
   'slug': 'woot5',
893
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
893
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
894
-  'content_type': 'html-documents',
894
+  'content_type': 'html-document',
895
   'workspace_id': 1,
895
   'workspace_id': 1,
896
   'revision_id': 81,
896
   'revision_id': 81,
897
   'label': 'woot5',
897
   'label': 'woot5',
901
 }, {
901
 }, {
902
   'is_deleted': false,
902
   'is_deleted': false,
903
   'is_archived': false,
903
   'is_archived': false,
904
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
904
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
905
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
905
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
906
   'status': 'open',
906
   'status': 'open',
907
   'content_id': 22,
907
   'content_id': 22,
911
   'created': '04/07/2018 à 11:05:19',
911
   'created': '04/07/2018 à 11:05:19',
912
   'slug': 'woot5',
912
   'slug': 'woot5',
913
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
913
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
914
-  'content_type': 'html-documents',
914
+  'content_type': 'html-document',
915
   'workspace_id': 1,
915
   'workspace_id': 1,
916
   'revision_id': 82,
916
   'revision_id': 82,
917
   'label': 'woot5',
917
   'label': 'woot5',
921
 }, {
921
 }, {
922
   'is_deleted': false,
922
   'is_deleted': false,
923
   'is_archived': false,
923
   'is_archived': false,
924
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
924
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
925
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
925
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
926
   'status': 'closed-validated',
926
   'status': 'closed-validated',
927
   'content_id': 22,
927
   'content_id': 22,
931
   'created': '04/07/2018 à 13:11:50',
931
   'created': '04/07/2018 à 13:11:50',
932
   'slug': 'woot5',
932
   'slug': 'woot5',
933
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
933
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
934
-  'content_type': 'html-documents',
934
+  'content_type': 'html-document',
935
   'workspace_id': 1,
935
   'workspace_id': 1,
936
   'revision_id': 83,
936
   'revision_id': 83,
937
   'label': 'woot5',
937
   'label': 'woot5',
941
 }, {
941
 }, {
942
   'is_deleted': false,
942
   'is_deleted': false,
943
   'is_archived': false,
943
   'is_archived': false,
944
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
944
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
945
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
945
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
946
   'status': 'open',
946
   'status': 'open',
947
   'content_id': 22,
947
   'content_id': 22,
951
   'created': '04/07/2018 à 14:26:43',
951
   'created': '04/07/2018 à 14:26:43',
952
   'slug': 'woot5',
952
   'slug': 'woot5',
953
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
953
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
954
-  'content_type': 'html-documents',
954
+  'content_type': 'html-document',
955
   'workspace_id': 1,
955
   'workspace_id': 1,
956
   'revision_id': 84,
956
   'revision_id': 84,
957
   'label': 'woot5',
957
   'label': 'woot5',
1021
 }, {
1021
 }, {
1022
   'is_deleted': false,
1022
   'is_deleted': false,
1023
   'is_archived': false,
1023
   'is_archived': false,
1024
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
1024
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
1025
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1025
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1026
   'status': 'closed-validated',
1026
   'status': 'closed-validated',
1027
   'content_id': 22,
1027
   'content_id': 22,
1031
   'created': '06/07/2018 à 16:21:52',
1031
   'created': '06/07/2018 à 16:21:52',
1032
   'slug': 'woot5',
1032
   'slug': 'woot5',
1033
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1033
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1034
-  'content_type': 'html-documents',
1034
+  'content_type': 'html-document',
1035
   'workspace_id': 1,
1035
   'workspace_id': 1,
1036
   'revision_id': 109,
1036
   'revision_id': 109,
1037
   'label': 'woot5',
1037
   'label': 'woot5',
1071
 }, {
1071
 }, {
1072
   'is_deleted': false,
1072
   'is_deleted': false,
1073
   'is_archived': false,
1073
   'is_archived': false,
1074
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
1074
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
1075
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1075
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1076
   'status': 'closed-validated',
1076
   'status': 'closed-validated',
1077
   'content_id': 22,
1077
   'content_id': 22,
1081
   'created': '06/07/2018 à 18:15:04',
1081
   'created': '06/07/2018 à 18:15:04',
1082
   'slug': 'woot55',
1082
   'slug': 'woot55',
1083
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1083
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1084
-  'content_type': 'html-documents',
1084
+  'content_type': 'html-document',
1085
   'workspace_id': 1,
1085
   'workspace_id': 1,
1086
   'revision_id': 115,
1086
   'revision_id': 115,
1087
   'label': 'woot55',
1087
   'label': 'woot55',
1091
 }, {
1091
 }, {
1092
   'is_deleted': false,
1092
   'is_deleted': false,
1093
   'is_archived': false,
1093
   'is_archived': false,
1094
-  'sub_content_types': ['thread', 'file', 'folder', 'html-documents'],
1094
+  'sub_content_types': ['thread', 'file', 'folder', 'html-document'],
1095
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1095
   'raw_content': '<p>bonjour.</p>\n<p>merci.</p>\n<p>bravo.12</p>\n<p>gni</p>\n<p>avec un commentaire ?</p>\n<p>fail.</p>',
1096
   'status': 'open',
1096
   'status': 'open',
1097
   'content_id': 22,
1097
   'content_id': 22,
1101
   'created': '06/07/2018 à 18:15:20',
1101
   'created': '06/07/2018 à 18:15:20',
1102
   'slug': 'woot55',
1102
   'slug': 'woot55',
1103
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1103
   'author': {'user_id': 1, 'avatar_url': null, 'public_name': 'Global manager'},
1104
-  'content_type': 'html-documents',
1104
+  'content_type': 'html-document',
1105
   'workspace_id': 1,
1105
   'workspace_id': 1,
1106
   'revision_id': 116,
1106
   'revision_id': 116,
1107
   'label': 'woot55',
1107
   'label': 'woot55',

+ 8 - 0
frontend_lib/src/helper.js View File

20
       return new Promise((resolve, reject) => reject(fetchResult)) // @TODO : handle errors from api result
20
       return new Promise((resolve, reject) => reject(fetchResult)) // @TODO : handle errors from api result
21
   }
21
   }
22
 }
22
 }
23
+
24
+export const libAddAllResourceI18n = (i18n, translation) => {
25
+  Object.keys(translation).forEach(lang =>
26
+    Object.keys(translation[lang]).forEach(namespace =>
27
+      i18n.addResources(lang, namespace, translation[lang][namespace])
28
+    )
29
+  )
30
+}

+ 5 - 1
frontend_lib/src/index.js View File

1
-import { libHandleFetchResult } from './helper.js'
1
+import { libAddAllResourceI18n, libHandleFetchResult } from './helper.js'
2
+
3
+// fr and en are deprecated
2
 import fr from './translate/fr.js'
4
 import fr from './translate/fr.js'
3
 import en from './translate/en.js'
5
 import en from './translate/en.js'
4
 
6
 
24
 export const langFr = fr
26
 export const langFr = fr
25
 export const langEn = en
27
 export const langEn = en
26
 
28
 
29
+export const addAllResourceI18n = libAddAllResourceI18n
30
+
27
 export const handleFetchResult = libHandleFetchResult
31
 export const handleFetchResult = libHandleFetchResult
28
 
32
 
29
 export const PopinFixed = libPopinFixed
33
 export const PopinFixed = libPopinFixed

+ 40 - 0
i18next.option.js View File

1
+module.exports = {
2
+  debug: true,
3
+  removeUnusedKeys: true,
4
+  func: {
5
+    list: ['t', 'props.t', 'this.props.t'],
6
+    extensions: ['.js', '.jsx']
7
+  },
8
+  lngs: ['en', 'fr'],
9
+  defaultLng: 'en',
10
+  keySeparator: false, // false means "keyBasedFallback"
11
+  nsSeparator: false, // false means "keyBasedFallback"
12
+  fallbackLng: false,
13
+
14
+  ns: ['translation'], // namespace
15
+  defaultNS: 'translation',
16
+
17
+  // @param {string} lng The language currently used.
18
+  // @param {string} ns The namespace currently used.
19
+  // @param {string} key The translation key.
20
+  // @return {string} Returns a default value for the translation key.
21
+  // Return key as the default value for English language. Otherwise, returns '__NOT_TRANSLATED__'
22
+  defaultValue: (lng, ns, key) => lng === 'en' ? key : '__NOT_TRANSLATED__',
23
+
24
+  react: {wait: true},
25
+
26
+  resource: {
27
+    // The path where resources get loaded from.
28
+    // /!\ /!\ /!\ Relative to CURRENT working directory. /!\
29
+    loadPath: 'i18next.scanner/{{lng}}/{{ns}}.json',
30
+    // The path to store resources.
31
+    // /!\ /!\ /!\ Relative to the path specified by `vfs.dest('./i18next.scanner')`. /!\
32
+    savePath: '{{lng}}/{{ns}}.json',
33
+    jsonIndent: 2,
34
+    lineEnding: '\n'
35
+  },
36
+  // interpolation: {
37
+  //   escapeValue: false, // not needed for react!!
38
+  // },
39
+  trans: false,
40
+}

+ 16 - 0
install_frontend_dependencies.sh View File

2
 
2
 
3
 . bash_library.sh # source bash_library.sh
3
 . bash_library.sh # source bash_library.sh
4
 
4
 
5
+# install Tracim Lib
6
+
5
 log "cd frontend_lib"
7
 log "cd frontend_lib"
6
 cd frontend_lib
8
 cd frontend_lib
7
 log "npm i"
9
 log "npm i"
10
 sudo npm link
12
 sudo npm link
11
 cd -
13
 cd -
12
 
14
 
15
+# install app Html Document
16
+
13
 log "cd frontend_app_html-document"
17
 log "cd frontend_app_html-document"
14
 cd frontend_app_html-document
18
 cd frontend_app_html-document
15
 log "npm i"
19
 log "npm i"
18
 npm link tracim_frontend_lib
22
 npm link tracim_frontend_lib
19
 cd -
23
 cd -
20
 
24
 
25
+# install app Thread
26
+
27
+log "cd frontend_app_thread"
28
+cd frontend_app_thread
29
+log "npm i"
30
+npm i
31
+log "npm link tracim_frontend_lib"
32
+npm link tracim_frontend_lib
33
+cd -
34
+
35
+# install Tracim Frontend
36
+
21
 log "cd frontend"
37
 log "cd frontend"
22
 cd frontend
38
 cd frontend
23
 log "npm i"
39
 log "npm i"