Browse Source

Merge branch 'develop' into feature/650_folder_missing_endpoint_about_edit

inkhey 6 years ago
parent
commit
58a917f247
No account linked to committer's email
61 changed files with 2039 additions and 1261 deletions
  1. 0 1
      .travis.yml
  2. 67 0
      backend/tests_configs.ini
  3. 3 1
      backend/tracim_backend/config.py
  4. 3 1
      backend/tracim_backend/exceptions.py
  5. 40 2
      backend/tracim_backend/lib/core/user.py
  6. 24 0
      backend/tracim_backend/lib/utils/utils.py
  7. 14 2
      backend/tracim_backend/models/applications.py
  8. 6 4
      backend/tracim_backend/models/contents.py
  9. 8 0
      backend/tracim_backend/models/context_models.py
  10. 8 11
      backend/tracim_backend/tests/__init__.py
  11. 24 72
      backend/tracim_backend/tests/functional/test_system.py
  12. 412 15
      backend/tracim_backend/tests/functional/test_user.py
  13. 98 27
      backend/tracim_backend/tests/functional/test_workspaces.py
  14. 183 0
      backend/tracim_backend/tests/library/test_user_api.py
  15. 14 0
      backend/tracim_backend/tests/library/tests_utils.py
  16. 6 0
      backend/tracim_backend/views/contents_api/comment_controller.py
  17. 28 0
      backend/tracim_backend/views/contents_api/file_controller.py
  18. 8 0
      backend/tracim_backend/views/contents_api/html_document_controller.py
  19. 8 0
      backend/tracim_backend/views/contents_api/threads_controller.py
  20. 13 0
      backend/tracim_backend/views/core_api/schemas.py
  21. 57 0
      backend/tracim_backend/views/core_api/user_controller.py
  22. 17 2
      backend/tracim_backend/views/core_api/workspace_controller.py
  23. 1 1
      bash_library.sh
  24. 51 54
      build_full_frontend.sh
  25. 1 0
      frontend/dist/appInterface.js
  26. 0 1
      frontend/dist/index.html
  27. 1 0
      frontend/dist/tinymceInit.js
  28. 6 1
      frontend/i18next.scanner/en/translation.json
  29. 6 1
      frontend/i18next.scanner/fr/translation.json
  30. 34 16
      frontend/src/action-creator.async.js
  31. 9 1
      frontend/src/action-creator.sync.js
  32. 1 1
      frontend/src/component/Account/Notification.jsx
  33. 41 0
      frontend/src/component/Dashboard/ContentTypeBtn.jsx
  34. 16 0
      frontend/src/component/Dashboard/ContentTypeBtn.styl
  35. 162 0
      frontend/src/component/Dashboard/MemberList.jsx
  36. 117 0
      frontend/src/component/Dashboard/MemberList.styl
  37. 50 0
      frontend/src/component/Dashboard/RecentActivity.jsx
  38. 2 5
      frontend/src/component/Workspace/OpenContentApp.jsx
  39. 1 4
      frontend/src/container/Account.jsx
  40. 138 0
      frontend/src/container/AdminWorkspacePage.jsx
  41. 1 4
      frontend/src/container/AppFullscreenManager.jsx
  42. 76 193
      frontend/src/container/Dashboard.jsx
  43. 0 571
      frontend/src/container/Dashboard_old.jsx
  44. 5 5
      frontend/src/container/Login.jsx
  45. 18 6
      frontend/src/container/Sidebar.jsx
  46. 53 35
      frontend/src/container/Tracim.jsx
  47. 3 6
      frontend/src/container/WorkspaceContent.jsx
  48. 11 0
      frontend/src/css/AdminWorkspacePage.styl
  49. 5 121
      frontend/src/css/Dashboard.styl
  50. 1 18
      frontend/src/css/Generic.styl
  51. 0 1
      frontend/src/css/Workspace.styl
  52. 19 17
      frontend/src/helper.js
  53. 39 6
      frontend/src/reducer/currentWorkspace.js
  54. 1 1
      frontend/src/reducer/workspaceContentList.js
  55. 1 1
      frontend_lib/dist/index.html
  56. 0 15
      frontend_lib/dist/tracim_lib.js
  57. 2 2
      frontend_lib/package.json
  58. 68 0
      frontend_lib/src/component/Input/Checkbox.jsx
  59. 7 0
      frontend_lib/src/index.dev.js
  60. 6 8
      frontend_lib/src/index.js
  61. 45 28
      install_frontend_dependencies.sh

+ 0 - 1
.travis.yml View File

3
     - sudo: false
3
     - sudo: false
4
       language: python
4
       language: python
5
       python:
5
       python:
6
-        - "3.4"
7
         - "3.5"
6
         - "3.5"
8
         - "3.6"
7
         - "3.6"
9
 
8
 

+ 67 - 0
backend/tests_configs.ini View File

63
 email.notification.smtp.port = 1025
63
 email.notification.smtp.port = 1025
64
 email.notification.smtp.user = test_user
64
 email.notification.smtp.user = test_user
65
 email.notification.smtp.password = just_a_password
65
 email.notification.smtp.password = just_a_password
66
+
67
+[functional_test]
68
+sqlalchemy.url = sqlite:///tracim_test.sqlite
69
+depot_storage_name = test
70
+depot_storage_dir = /tmp/test/depot
71
+user.auth_token.validity = 604800
72
+preview_cache_dir = /tmp/test/preview_cache
73
+preview.jpg.restricted_dims = True
74
+email.notification.activated = false
75
+
76
+[functional_test_no_db]
77
+sqlalchemy.url = sqlite://
78
+depot_storage_name = test
79
+depot_storage_dir = /tmp/test/depot
80
+user.auth_token.validity = 604800
81
+preview_cache_dir = /tmp/test/preview_cache
82
+preview.jpg.restricted_dims = True
83
+email.notification.activated = false
84
+
85
+[functional_test_with_mail_test_sync]
86
+sqlalchemy.url = sqlite:///tracim_test.sqlite
87
+depot_storage_name = test
88
+depot_storage_dir = /tmp/test/depot
89
+user.auth_token.validity = 604800
90
+preview_cache_dir = /tmp/test/preview_cache
91
+preview.jpg.restricted_dims = True
92
+email.notification.activated = true
93
+email.notification.from.email = test_user_from+{user_id}@localhost
94
+email.notification.from.default_label = Tracim Notifications
95
+email.notification.reply_to.email = test_user_reply+{content_id}@localhost
96
+email.notification.references.email = test_user_refs+{content_id}@localhost
97
+email.notification.content_update.template.html = %(here)s/tracim_backend/templates/mail/content_update_body_html.mak
98
+email.notification.content_update.template.text = %(here)s/tracim_backend/templates/mail/content_update_body_text.mak
99
+email.notification.created_account.template.html = %(here)s/tracim_backend/templates/mail/created_account_body_html.mak
100
+email.notification.created_account.template.text = %(here)s/tracim_backend/templates/mail/created_account_body_text.mak
101
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
102
+email.notification.created_account.subject = [{website_title}] Created account
103
+email.notification.processing_mode = sync
104
+email.notification.smtp.server = 127.0.0.1
105
+email.notification.smtp.port = 1025
106
+email.notification.smtp.user = test_user
107
+email.notification.smtp.password = just_a_password
108
+
109
+
110
+[functional_test_with_mail_test_async]
111
+sqlalchemy.url = sqlite:///tracim_test.sqlite
112
+depot_storage_name = test
113
+depot_storage_dir = /tmp/test/depot
114
+user.auth_token.validity = 604800
115
+preview_cache_dir = /tmp/test/preview_cache
116
+preview.jpg.restricted_dims = True
117
+email.notification.activated = true
118
+email.notification.from.email = test_user_from+{user_id}@localhost
119
+email.notification.from.default_label = Tracim Notifications
120
+email.notification.reply_to.email = test_user_reply+{content_id}@localhost
121
+email.notification.references.email = test_user_refs+{content_id}@localhost
122
+email.notification.content_update.template.html = %(here)s/tracim_backend/templates/mail/content_update_body_html.mak
123
+email.notification.content_update.template.text = %(here)s/tracim_backend/templates/mail/content_update_body_text.mak
124
+email.notification.created_account.template.html = %(here)s/tracim_backend/templates/mail/created_account_body_html.mak
125
+email.notification.created_account.template.text = %(here)s/tracim_backend/templates/mail/created_account_body_text.mak
126
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
127
+email.notification.created_account.subject = [{website_title}] Created account
128
+email.notification.processing_mode = async
129
+email.notification.smtp.server = 127.0.0.1
130
+email.notification.smtp.port = 1025
131
+email.notification.smtp.user = test_user
132
+email.notification.smtp.password = just_a_password

+ 3 - 1
backend/tracim_backend/config.py View File

161
 
161
 
162
         self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
162
         self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
163
             'email.notification.from.email',
163
             'email.notification.from.email',
164
+            'noreply+{user_id}@trac.im'
164
         )
165
         )
165
         self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
166
         self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
166
-            'email.notification.from.default_label'
167
+            'email.notification.from.default_label',
168
+            'Tracim Notifications'
167
         )
169
         )
168
         self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
170
         self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
169
             'email.notification.reply_to.email',
171
             'email.notification.reply_to.email',

+ 3 - 1
backend/tracim_backend/exceptions.py View File

204
 class PreviewDimNotAllowed(TracimException):
204
 class PreviewDimNotAllowed(TracimException):
205
     pass
205
     pass
206
 
206
 
207
-
208
 class UnallowedSubContent(TracimException):
207
 class UnallowedSubContent(TracimException):
209
     pass
208
     pass
209
+
210
+class TooShortAutocompleteString(TracimException):
211
+    pass

+ 40 - 2
backend/tracim_backend/lib/core/user.py View File

3
 
3
 
4
 import transaction
4
 import transaction
5
 import typing as typing
5
 import typing as typing
6
+
7
+from sqlalchemy import or_
6
 from sqlalchemy.orm import Session
8
 from sqlalchemy.orm import Session
9
+from sqlalchemy.orm import Query
7
 from sqlalchemy.orm.exc import NoResultFound
10
 from sqlalchemy.orm.exc import NoResultFound
8
 
11
 
9
 from tracim_backend import CFG
12
 from tracim_backend import CFG
10
 from tracim_backend.models.auth import User
13
 from tracim_backend.models.auth import User
11
 from tracim_backend.models.auth import Group
14
 from tracim_backend.models.auth import Group
12
 from tracim_backend.exceptions import NoUserSetted
15
 from tracim_backend.exceptions import NoUserSetted
16
+from tracim_backend.exceptions import TooShortAutocompleteString
13
 from tracim_backend.exceptions import PasswordDoNotMatch
17
 from tracim_backend.exceptions import PasswordDoNotMatch
14
 from tracim_backend.exceptions import EmailValidationFailed
18
 from tracim_backend.exceptions import EmailValidationFailed
15
 from tracim_backend.exceptions import UserDoesNotExist
19
 from tracim_backend.exceptions import UserDoesNotExist
20
 from tracim_backend.models.context_models import UserInContext
24
 from tracim_backend.models.context_models import UserInContext
21
 from tracim_backend.lib.mail_notifier.notifier import get_email_manager
25
 from tracim_backend.lib.mail_notifier.notifier import get_email_manager
22
 from tracim_backend.models.context_models import TypeUser
26
 from tracim_backend.models.context_models import TypeUser
27
+from tracim_backend.models.data import UserRoleInWorkspace
23
 
28
 
24
 
29
 
25
 class UserApi(object):
30
 class UserApi(object):
94
             raise UserDoesNotExist('There is no current user')
99
             raise UserDoesNotExist('There is no current user')
95
         return self._user
100
         return self._user
96
 
101
 
102
+    def _get_all_query(self) -> Query:
103
+        return self._session.query(User).order_by(User.display_name)
104
+
97
     def get_all(self) -> typing.Iterable[User]:
105
     def get_all(self) -> typing.Iterable[User]:
98
-        return self._session.query(User).order_by(User.display_name).all()
106
+        return self._get_all_query().all()
107
+
108
+    def get_known_user(
109
+            self,
110
+            acp: str,
111
+    ) -> typing.Iterable[User]:
112
+        """
113
+        Return list of know user by current UserApi user.
114
+        :param acp: autocomplete filter by name/email
115
+        :return: List of found users
116
+        """
117
+        if len(acp) < 2:
118
+            raise TooShortAutocompleteString(
119
+                '"{acp}" is a too short string, acp string need to have more than one character'.format(acp=acp)  # nopep8
120
+            )
121
+        query = self._get_all_query()
122
+        query = query.filter(or_(User.display_name.ilike('%{}%'.format(acp)), User.email.ilike('%{}%'.format(acp))))  # nopep8
123
+
124
+        # INFO - G.M - 2018-07-27 - if user is set and is simple user, we
125
+        # should show only user in same workspace as user
126
+        if self._user and self._user.profile.id <= Group.TIM_USER:
127
+            user_workspaces_id_query = self._session.\
128
+                query(UserRoleInWorkspace.workspace_id).\
129
+                distinct(UserRoleInWorkspace.workspace_id).\
130
+                filter(UserRoleInWorkspace.user_id == self._user.user_id)
131
+            users_in_workspaces = self._session.\
132
+                query(UserRoleInWorkspace.user_id).\
133
+                distinct(UserRoleInWorkspace.user_id).\
134
+                filter(UserRoleInWorkspace.workspace_id.in_(user_workspaces_id_query.subquery())).subquery()  # nopep8
135
+            query = query.filter(User.user_id.in_(users_in_workspaces))
136
+        return query.all()
99
 
137
 
100
     def find(
138
     def find(
101
             self,
139
             self,
196
         )
234
         )
197
         if do_save:
235
         if do_save:
198
             # TODO - G.M - 2018-07-24 - Check why commit is needed here
236
             # TODO - G.M - 2018-07-24 - Check why commit is needed here
199
-            transaction.commit()
237
+            self.save(user)
200
         return user
238
         return user
201
 
239
 
202
     def set_email(
240
     def set_email(

+ 24 - 0
backend/tracim_backend/lib/utils/utils.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import datetime
2
 import datetime
3
+import random
4
+import string
3
 from redis import Redis
5
 from redis import Redis
4
 from rq import Queue
6
 from rq import Queue
5
 
7
 
72
     # webdav utils, it may cause trouble. So, it should be replaced to
74
     # webdav utils, it may cause trouble. So, it should be replaced to
73
     # a character which will not change in bdd.
75
     # a character which will not change in bdd.
74
     return datetime.datetime.now().isoformat().replace(':', '.')
76
     return datetime.datetime.now().isoformat().replace(':', '.')
77
+
78
+# INFO - G.M - 2018-08-02 - Simple password generator, inspired by
79
+# https://gist.github.com/23maverick23/4131896
80
+
81
+
82
+ALLOWED_AUTOGEN_PASSWORD_CHAR = string.ascii_letters + \
83
+                                string.digits + \
84
+                                string.punctuation
85
+
86
+DEFAULT_PASSWORD_GEN_CHAR_LENGTH = 12
87
+
88
+
89
+def password_generator(
90
+        length: int=DEFAULT_PASSWORD_GEN_CHAR_LENGTH,
91
+        chars: str=ALLOWED_AUTOGEN_PASSWORD_CHAR
92
+) -> str:
93
+    """
94
+    :param length: length of the new password
95
+    :param chars: characters allowed
96
+    :return: password as string
97
+    """
98
+    return ''.join(random.choice(chars) for char_number in range(length))

+ 14 - 2
backend/tracim_backend/models/applications.py View File

59
 
59
 
60
 )
60
 )
61
 
61
 
62
+folder = Application(
63
+    label='Folder',
64
+    slug='contents/folder',
65
+    fa_icon='folder-open-o',
66
+    hexcolor='#252525',
67
+    is_active=True,
68
+    config={},
69
+    main_route='',
70
+)
71
+
62
 _file = Application(
72
 _file = Application(
63
     label='Files',
73
     label='Files',
64
     slug='contents/file',
74
     slug='contents/file',
92
 # List of applications
102
 # List of applications
93
 applications = [
103
 applications = [
94
     html_documents,
104
     html_documents,
95
-    markdownpluspage,
105
+    # TODO - G.M - 2018-08-02 - Restore markdownpage app
106
+    # markdownpluspage,
96
     _file,
107
     _file,
97
     thread,
108
     thread,
98
-    calendar,
109
+    folder,
110
+    # calendar,
99
 ]
111
 ]

+ 6 - 4
backend/tracim_backend/models/contents.py View File

6
 from tracim_backend.exceptions import ContentStatusNotExist
6
 from tracim_backend.exceptions import ContentStatusNotExist
7
 from tracim_backend.models.applications import html_documents
7
 from tracim_backend.models.applications import html_documents
8
 from tracim_backend.models.applications import _file
8
 from tracim_backend.models.applications import _file
9
+from tracim_backend.models.applications import folder
9
 from tracim_backend.models.applications import thread
10
 from tracim_backend.models.applications import thread
10
 from tracim_backend.models.applications import markdownpluspage
11
 from tracim_backend.models.applications import markdownpluspage
11
 
12
 
173
 # TODO - G.M - 31-05-2018 - Set Better folder params
174
 # TODO - G.M - 31-05-2018 - Set Better folder params
174
 folder_type = ContentType(
175
 folder_type = ContentType(
175
     slug='folder',
176
     slug='folder',
176
-    fa_icon=thread.fa_icon,
177
-    hexcolor=thread.hexcolor,
177
+    fa_icon=folder.fa_icon,
178
+    hexcolor=folder.hexcolor,
178
     label='Folder',
179
     label='Folder',
179
-    creation_label='Create collection of any documents',
180
+    creation_label='Create a folder',
180
     available_statuses=CONTENT_STATUS.get_all(),
181
     available_statuses=CONTENT_STATUS.get_all(),
181
     allow_sub_content=True,
182
     allow_sub_content=True,
182
 )
183
 )
283
     [
284
     [
284
         thread_type,
285
         thread_type,
285
         file_type,
286
         file_type,
286
-        markdownpluspage_type,
287
+        # TODO - G.M - 2018-08-02 - Restore markdown page content
288
+        #    markdownpluspage_type,
287
         html_documents_type,
289
         html_documents_type,
288
     ]
290
     ]
289
 )
291
 )

+ 8 - 0
backend/tracim_backend/models/context_models.py View File

186
         self.comment_id = comment_id
186
         self.comment_id = comment_id
187
 
187
 
188
 
188
 
189
+class AutocompleteQuery(object):
190
+    """
191
+    Autocomplete query model
192
+    """
193
+    def __init__(self, acp: str):
194
+        self.acp = acp
195
+
196
+
189
 class PageQuery(object):
197
 class PageQuery(object):
190
     """
198
     """
191
     Page query model
199
     Page query model

+ 8 - 11
backend/tracim_backend/tests/__init__.py View File

68
 class FunctionalTest(unittest.TestCase):
68
 class FunctionalTest(unittest.TestCase):
69
 
69
 
70
     fixtures = [BaseFixture]
70
     fixtures = [BaseFixture]
71
-    sqlalchemy_url = 'sqlite:///tracim_test.sqlite'
71
+    config_uri = 'tests_configs.ini'
72
+    config_section = 'functional_test'
72
 
73
 
73
     def setUp(self):
74
     def setUp(self):
74
         logger._logger.setLevel('WARNING')
75
         logger._logger.setLevel('WARNING')
76
+
75
         DepotManager._clear()
77
         DepotManager._clear()
76
-        self.settings = {
77
-            'sqlalchemy.url': self.sqlalchemy_url,
78
-            'user.auth_token.validity': '604800',
79
-            'depot_storage_dir': '/tmp/test/depot',
80
-            'depot_storage_name': 'test',
81
-            'preview_cache_dir': '/tmp/test/preview_cache',
82
-            'preview.jpg.restricted_dims': True,
83
-            'email.notification.activated': 'false',
84
-        }
78
+        self.settings = plaster.get_settings(
79
+            self.config_uri,
80
+            self.config_section
81
+        )
85
         hapic.reset_context()
82
         hapic.reset_context()
86
         self.engine = get_engine(self.settings)
83
         self.engine = get_engine(self.settings)
87
         DeclarativeBase.metadata.create_all(self.engine)
84
         DeclarativeBase.metadata.create_all(self.engine)
127
 
124
 
128
 
125
 
129
 class FunctionalTestNoDB(FunctionalTest):
126
 class FunctionalTestNoDB(FunctionalTest):
130
-    sqlalchemy_url = 'sqlite://'
127
+    config_section = 'functional_test_no_db'
131
 
128
 
132
     def init_database(self, settings):
129
     def init_database(self, settings):
133
         self.engine = get_engine(settings)
130
         self.engine = get_engine(settings)

+ 24 - 72
backend/tracim_backend/tests/functional/test_system.py View File

1
 # coding=utf-8
1
 # coding=utf-8
2
+from tracim_backend.models.contents import CONTENT_TYPES
2
 from tracim_backend.tests import FunctionalTest
3
 from tracim_backend.tests import FunctionalTest
4
+from tracim_backend.models.applications import applications
3
 
5
 
4
 """
6
 """
5
 Tests for /api/v2/system subpath endpoints.
7
 Tests for /api/v2/system subpath endpoints.
24
         )
26
         )
25
         res = self.testapp.get('/api/v2/system/applications', status=200)
27
         res = self.testapp.get('/api/v2/system/applications', status=200)
26
         res = res.json_body
28
         res = res.json_body
27
-        application = res[0]
28
-        assert application['label'] == "Text Documents"
29
-        assert application['slug'] == 'contents/html-document'
30
-        assert application['fa_icon'] == 'file-text-o'
31
-        assert application['hexcolor'] == '#3f52e3'
32
-        assert application['is_active'] is True
33
-        assert 'config' in application
34
-        application = res[1]
35
-        assert application['label'] == "Markdown Plus Documents"
36
-        assert application['slug'] == 'contents/markdownpluspage'
37
-        assert application['fa_icon'] == 'file-code-o'
38
-        assert application['hexcolor'] == '#f12d2d'
39
-        assert application['is_active'] is True
40
-        assert 'config' in application
41
-        application = res[2]
42
-        assert application['label'] == "Files"
43
-        assert application['slug'] == 'contents/file'
44
-        assert application['fa_icon'] == 'paperclip'
45
-        assert application['hexcolor'] == '#FF9900'
46
-        assert application['is_active'] is True
47
-        assert 'config' in application
48
-        application = res[3]
49
-        assert application['label'] == "Threads"
50
-        assert application['slug'] == 'contents/thread'
51
-        assert application['fa_icon'] == 'comments-o'
52
-        assert application['hexcolor'] == '#ad4cf9'
53
-        assert application['is_active'] is True
54
-        assert 'config' in application
55
-        application = res[4]
56
-        assert application['label'] == "Calendar"
57
-        assert application['slug'] == 'calendar'
58
-        assert application['fa_icon'] == 'calendar'
59
-        assert application['hexcolor'] == '#757575'
60
-        assert application['is_active'] is True
61
-        assert 'config' in application
29
+        assert len(res) == len(applications)
30
+        for counter, application in enumerate(applications):
31
+            assert res[counter]['label'] == application.label
32
+            assert res[counter]['slug'] == application.slug
33
+            assert res[counter]['fa_icon'] == application.fa_icon
34
+            assert res[counter]['hexcolor'] == application.hexcolor
35
+            assert res[counter]['is_active'] == application.is_active
36
+            assert res[counter]['config'] == application.config
62
 
37
 
63
     def test_api__get_applications__err_401__unregistered_user(self):
38
     def test_api__get_applications__err_401__unregistered_user(self):
64
         """
39
         """
96
         )
71
         )
97
         res = self.testapp.get('/api/v2/system/content_types', status=200)
72
         res = self.testapp.get('/api/v2/system/content_types', status=200)
98
         res = res.json_body
73
         res = res.json_body
74
+        assert len(res) == len(CONTENT_TYPES.endpoint_allowed_types_slug())
75
+        content_types = CONTENT_TYPES.endpoint_allowed_types_slug()
99
 
76
 
100
-        content_type = res[1]
101
-        assert content_type['slug'] == 'thread'
102
-        assert content_type['fa_icon'] == 'comments-o'
103
-        assert content_type['hexcolor'] == '#ad4cf9'
104
-        assert content_type['label'] == 'Thread'
105
-        assert content_type['creation_label'] == 'Discuss about a topic'
106
-        assert 'available_statuses' in content_type
107
-        assert len(content_type['available_statuses']) == 4
108
-
109
-        content_type = res[2]
110
-        assert content_type['slug'] == 'file'
111
-        assert content_type['fa_icon'] == 'paperclip'
112
-        assert content_type['hexcolor'] == '#FF9900'
113
-        assert content_type['label'] == 'File'
114
-        assert content_type['creation_label'] == 'Upload a file'
115
-        assert 'available_statuses' in content_type
116
-        assert len(content_type['available_statuses']) == 4
117
-
118
-        content_type = res[3]
119
-        assert content_type['slug'] == 'markdownpage'
120
-        assert content_type['fa_icon'] == 'file-code-o'
121
-        assert content_type['hexcolor'] == '#f12d2d'
122
-        assert content_type['label'] == 'Rich Markdown File'
123
-        assert content_type['creation_label'] == 'Create a Markdown document'
124
-        assert 'available_statuses' in content_type
125
-        assert len(content_type['available_statuses']) == 4
126
-
127
-        content_type = res[4]
128
-        assert content_type['slug'] == 'html-document'
129
-        assert content_type['fa_icon'] == 'file-text-o'
130
-        assert content_type['hexcolor'] == '#3f52e3'
131
-        assert content_type['label'] == 'Text Document'
132
-        assert content_type['creation_label'] == 'Write a document'
133
-        assert 'available_statuses' in content_type
134
-        assert len(content_type['available_statuses']) == 4
135
-        # TODO - G.M - 31-05-2018 - Check Folder type
136
-        # TODO - G.M - 29-05-2018 - Better check for available_statuses
77
+        for counter, content_type_slug in enumerate(content_types):
78
+            content_type = CONTENT_TYPES.get_one_by_slug(content_type_slug)
79
+            assert res[counter]['slug'] == content_type.slug
80
+            assert res[counter]['fa_icon'] == content_type.fa_icon
81
+            assert res[counter]['hexcolor'] == content_type.hexcolor
82
+            assert res[counter]['label'] == content_type.label
83
+            assert res[counter]['creation_label'] == content_type.creation_label
84
+            for status_counter, status in enumerate(content_type.available_statuses):
85
+                assert res[counter]['available_statuses'][status_counter]['fa_icon'] == status.fa_icon  # nopep8
86
+                assert res[counter]['available_statuses'][status_counter]['global_status'] == status.global_status  # nopep8
87
+                assert res[counter]['available_statuses'][status_counter]['slug'] == status.slug  # nopep8
88
+                assert res[counter]['available_statuses'][status_counter]['hexcolor'] == status.hexcolor  # nopep8
137
 
89
 
138
     def test_api__get_content_types__err_401__unregistered_user(self):
90
     def test_api__get_content_types__err_401__unregistered_user(self):
139
         """
91
         """

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

2382
         assert workspace['workspace_id'] == 1
2382
         assert workspace['workspace_id'] == 1
2383
         assert workspace['label'] == 'Business'
2383
         assert workspace['label'] == 'Business'
2384
         assert workspace['slug'] == 'business'
2384
         assert workspace['slug'] == 'business'
2385
-        assert len(workspace['sidebar_entries']) == 7
2385
+        assert len(workspace['sidebar_entries']) == 5
2386
 
2386
 
2387
+        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
2388
+        # not fixed on active application/content-file
2387
         sidebar_entry = workspace['sidebar_entries'][0]
2389
         sidebar_entry = workspace['sidebar_entries'][0]
2388
         assert sidebar_entry['slug'] == 'dashboard'
2390
         assert sidebar_entry['slug'] == 'dashboard'
2389
         assert sidebar_entry['label'] == 'Dashboard'
2391
         assert sidebar_entry['label'] == 'Dashboard'
2406
         assert sidebar_entry['fa_icon'] == "file-text-o"
2408
         assert sidebar_entry['fa_icon'] == "file-text-o"
2407
 
2409
 
2408
         sidebar_entry = workspace['sidebar_entries'][3]
2410
         sidebar_entry = workspace['sidebar_entries'][3]
2409
-        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
2410
-        assert sidebar_entry['label'] == 'Markdown Plus Documents'
2411
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
2412
-        assert sidebar_entry['hexcolor'] == "#f12d2d"
2413
-        assert sidebar_entry['fa_icon'] == "file-code-o"
2414
-
2415
-        sidebar_entry = workspace['sidebar_entries'][4]
2416
         assert sidebar_entry['slug'] == 'contents/file'
2411
         assert sidebar_entry['slug'] == 'contents/file'
2417
         assert sidebar_entry['label'] == 'Files'
2412
         assert sidebar_entry['label'] == 'Files'
2418
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2413
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2419
         assert sidebar_entry['hexcolor'] == "#FF9900"
2414
         assert sidebar_entry['hexcolor'] == "#FF9900"
2420
         assert sidebar_entry['fa_icon'] == "paperclip"
2415
         assert sidebar_entry['fa_icon'] == "paperclip"
2421
 
2416
 
2422
-        sidebar_entry = workspace['sidebar_entries'][5]
2417
+        sidebar_entry = workspace['sidebar_entries'][4]
2423
         assert sidebar_entry['slug'] == 'contents/thread'
2418
         assert sidebar_entry['slug'] == 'contents/thread'
2424
         assert sidebar_entry['label'] == 'Threads'
2419
         assert sidebar_entry['label'] == 'Threads'
2425
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2420
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2426
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2421
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2427
         assert sidebar_entry['fa_icon'] == "comments-o"
2422
         assert sidebar_entry['fa_icon'] == "comments-o"
2428
 
2423
 
2429
-        sidebar_entry = workspace['sidebar_entries'][6]
2430
-        assert sidebar_entry['slug'] == 'calendar'
2431
-        assert sidebar_entry['label'] == 'Calendar'
2432
-        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
2433
-        assert sidebar_entry['hexcolor'] == "#757575"
2434
-        assert sidebar_entry['fa_icon'] == "calendar"
2435
 
2424
 
2436
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2425
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2437
         """
2426
         """
2646
         )
2635
         )
2647
 
2636
 
2648
 
2637
 
2638
+class TestUsersEndpoint(FunctionalTest):
2639
+    # -*- coding: utf-8 -*-
2640
+    """
2641
+    Tests for GET /api/v2/users/{user_id}
2642
+    """
2643
+    fixtures = [BaseFixture]
2644
+
2645
+    def test_api__get_user__ok_200__admin(self):
2646
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2647
+        admin = dbsession.query(models.User) \
2648
+            .filter(models.User.email == 'admin@admin.admin') \
2649
+            .one()
2650
+        uapi = UserApi(
2651
+            current_user=admin,
2652
+            session=dbsession,
2653
+            config=self.app_config,
2654
+        )
2655
+        gapi = GroupApi(
2656
+            current_user=admin,
2657
+            session=dbsession,
2658
+            config=self.app_config,
2659
+        )
2660
+        groups = [gapi.get_one_with_name('users')]
2661
+        test_user = uapi.create_user(
2662
+            email='test@test.test',
2663
+            password='pass',
2664
+            name='bob',
2665
+            groups=groups,
2666
+            timezone='Europe/Paris',
2667
+            do_save=True,
2668
+            do_notify=False,
2669
+        )
2670
+        uapi.save(test_user)
2671
+        transaction.commit()
2672
+        user_id = int(test_user.user_id)
2673
+
2674
+        self.testapp.authorization = (
2675
+            'Basic',
2676
+            (
2677
+                'admin@admin.admin',
2678
+                'admin@admin.admin'
2679
+            )
2680
+        )
2681
+        res = self.testapp.get(
2682
+            '/api/v2/users',
2683
+            status=200
2684
+        )
2685
+        res = res.json_body
2686
+        assert len(res) == 2
2687
+        assert res[0]['user_id'] == admin.user_id
2688
+        assert res[0]['public_name'] == admin.display_name
2689
+        assert res[0]['avatar_url'] is None
2690
+
2691
+        assert res[1]['user_id'] == test_user.user_id
2692
+        assert res[1]['public_name'] == test_user.display_name
2693
+        assert res[1]['avatar_url'] is None
2694
+
2695
+    def test_api__get_user__err_403__normal_user(self):
2696
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2697
+        admin = dbsession.query(models.User) \
2698
+            .filter(models.User.email == 'admin@admin.admin') \
2699
+            .one()
2700
+        uapi = UserApi(
2701
+            current_user=admin,
2702
+            session=dbsession,
2703
+            config=self.app_config,
2704
+        )
2705
+        gapi = GroupApi(
2706
+            current_user=admin,
2707
+            session=dbsession,
2708
+            config=self.app_config,
2709
+        )
2710
+        groups = [gapi.get_one_with_name('users')]
2711
+        test_user = uapi.create_user(
2712
+            email='test@test.test',
2713
+            password='pass',
2714
+            name='bob',
2715
+            groups=groups,
2716
+            timezone='Europe/Paris',
2717
+            do_save=True,
2718
+            do_notify=False,
2719
+        )
2720
+        uapi.save(test_user)
2721
+        transaction.commit()
2722
+        user_id = int(test_user.user_id)
2723
+
2724
+        self.testapp.authorization = (
2725
+            'Basic',
2726
+            (
2727
+                'test@test.test',
2728
+                'pass'
2729
+            )
2730
+        )
2731
+        self.testapp.get(
2732
+            '/api/v2/users',
2733
+            status=403
2734
+        )
2735
+
2736
+
2737
+class TestKnownMembersEndpoint(FunctionalTest):
2738
+    # -*- coding: utf-8 -*-
2739
+    """
2740
+    Tests for GET /api/v2/users/{user_id}
2741
+    """
2742
+    fixtures = [BaseFixture]
2743
+
2744
+    def test_api__get_user__ok_200__admin__by_name(self):
2745
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2746
+        admin = dbsession.query(models.User) \
2747
+            .filter(models.User.email == 'admin@admin.admin') \
2748
+            .one()
2749
+        uapi = UserApi(
2750
+            current_user=admin,
2751
+            session=dbsession,
2752
+            config=self.app_config,
2753
+        )
2754
+        gapi = GroupApi(
2755
+            current_user=admin,
2756
+            session=dbsession,
2757
+            config=self.app_config,
2758
+        )
2759
+        groups = [gapi.get_one_with_name('users')]
2760
+        test_user = uapi.create_user(
2761
+            email='test@test.test',
2762
+            password='pass',
2763
+            name='bob',
2764
+            groups=groups,
2765
+            timezone='Europe/Paris',
2766
+            do_save=True,
2767
+            do_notify=False,
2768
+        )
2769
+        test_user2 = uapi.create_user(
2770
+            email='test2@test2.test2',
2771
+            password='pass',
2772
+            name='bob2',
2773
+            groups=groups,
2774
+            timezone='Europe/Paris',
2775
+            do_save=True,
2776
+            do_notify=False,
2777
+        )
2778
+        uapi.save(test_user)
2779
+        uapi.save(test_user2)
2780
+        transaction.commit()
2781
+        user_id = int(admin.user_id)
2782
+
2783
+        self.testapp.authorization = (
2784
+            'Basic',
2785
+            (
2786
+                'admin@admin.admin',
2787
+                'admin@admin.admin'
2788
+            )
2789
+        )
2790
+        params = {
2791
+            'acp': 'bob',
2792
+        }
2793
+        res = self.testapp.get(
2794
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2795
+            status=200,
2796
+            params=params,
2797
+        )
2798
+        res = res.json_body
2799
+        assert len(res) == 2
2800
+        assert res[0]['user_id'] == test_user.user_id
2801
+        assert res[0]['public_name'] == test_user.display_name
2802
+        assert res[0]['avatar_url'] is None
2803
+
2804
+        assert res[1]['user_id'] == test_user2.user_id
2805
+        assert res[1]['public_name'] == test_user2.display_name
2806
+        assert res[1]['avatar_url'] is None
2807
+
2808
+    def test_api__get_user__ok_200__admin__by_email(self):
2809
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2810
+        admin = dbsession.query(models.User) \
2811
+            .filter(models.User.email == 'admin@admin.admin') \
2812
+            .one()
2813
+        uapi = UserApi(
2814
+            current_user=admin,
2815
+            session=dbsession,
2816
+            config=self.app_config,
2817
+        )
2818
+        gapi = GroupApi(
2819
+            current_user=admin,
2820
+            session=dbsession,
2821
+            config=self.app_config,
2822
+        )
2823
+        groups = [gapi.get_one_with_name('users')]
2824
+        test_user = uapi.create_user(
2825
+            email='test@test.test',
2826
+            password='pass',
2827
+            name='bob',
2828
+            groups=groups,
2829
+            timezone='Europe/Paris',
2830
+            do_save=True,
2831
+            do_notify=False,
2832
+        )
2833
+        test_user2 = uapi.create_user(
2834
+            email='test2@test2.test2',
2835
+            password='pass',
2836
+            name='bob2',
2837
+            groups=groups,
2838
+            timezone='Europe/Paris',
2839
+            do_save=True,
2840
+            do_notify=False,
2841
+        )
2842
+        uapi.save(test_user)
2843
+        uapi.save(test_user2)
2844
+        transaction.commit()
2845
+        user_id = int(admin.user_id)
2846
+
2847
+        self.testapp.authorization = (
2848
+            'Basic',
2849
+            (
2850
+                'admin@admin.admin',
2851
+                'admin@admin.admin'
2852
+            )
2853
+        )
2854
+        params = {
2855
+            'acp': 'test',
2856
+        }
2857
+        res = self.testapp.get(
2858
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2859
+            status=200,
2860
+            params=params,
2861
+        )
2862
+        res = res.json_body
2863
+        assert len(res) == 2
2864
+        assert res[0]['user_id'] == test_user.user_id
2865
+        assert res[0]['public_name'] == test_user.display_name
2866
+        assert res[0]['avatar_url'] is None
2867
+
2868
+        assert res[1]['user_id'] == test_user2.user_id
2869
+        assert res[1]['public_name'] == test_user2.display_name
2870
+        assert res[1]['avatar_url'] is None
2871
+
2872
+    def test_api__get_user__err_403__admin__too_small_acp(self):
2873
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2874
+        admin = dbsession.query(models.User) \
2875
+            .filter(models.User.email == 'admin@admin.admin') \
2876
+            .one()
2877
+        uapi = UserApi(
2878
+            current_user=admin,
2879
+            session=dbsession,
2880
+            config=self.app_config,
2881
+        )
2882
+        gapi = GroupApi(
2883
+            current_user=admin,
2884
+            session=dbsession,
2885
+            config=self.app_config,
2886
+        )
2887
+        groups = [gapi.get_one_with_name('users')]
2888
+        test_user = uapi.create_user(
2889
+            email='test@test.test',
2890
+            password='pass',
2891
+            name='bob',
2892
+            groups=groups,
2893
+            timezone='Europe/Paris',
2894
+            do_save=True,
2895
+            do_notify=False,
2896
+        )
2897
+        test_user2 = uapi.create_user(
2898
+            email='test2@test2.test2',
2899
+            password='pass',
2900
+            name='bob2',
2901
+            groups=groups,
2902
+            timezone='Europe/Paris',
2903
+            do_save=True,
2904
+            do_notify=False,
2905
+        )
2906
+        uapi.save(test_user)
2907
+        transaction.commit()
2908
+        user_id = int(admin.user_id)
2909
+
2910
+        self.testapp.authorization = (
2911
+            'Basic',
2912
+            (
2913
+                'admin@admin.admin',
2914
+                'admin@admin.admin'
2915
+            )
2916
+        )
2917
+        params = {
2918
+            'acp': 't',
2919
+        }
2920
+        res = self.testapp.get(
2921
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2922
+            status=400,
2923
+            params=params
2924
+        )
2925
+
2926
+    def test_api__get_user__ok_200__normal_user_by_email(self):
2927
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2928
+        admin = dbsession.query(models.User) \
2929
+            .filter(models.User.email == 'admin@admin.admin') \
2930
+            .one()
2931
+        uapi = UserApi(
2932
+            current_user=admin,
2933
+            session=dbsession,
2934
+            config=self.app_config,
2935
+        )
2936
+        gapi = GroupApi(
2937
+            current_user=admin,
2938
+            session=dbsession,
2939
+            config=self.app_config,
2940
+        )
2941
+        groups = [gapi.get_one_with_name('users')]
2942
+        test_user = uapi.create_user(
2943
+            email='test@test.test',
2944
+            password='pass',
2945
+            name='bob',
2946
+            groups=groups,
2947
+            timezone='Europe/Paris',
2948
+            do_save=True,
2949
+            do_notify=False,
2950
+        )
2951
+        test_user2 = uapi.create_user(
2952
+            email='test2@test2.test2',
2953
+            password='pass',
2954
+            name='bob2',
2955
+            groups=groups,
2956
+            timezone='Europe/Paris',
2957
+            do_save=True,
2958
+            do_notify=False,
2959
+        )
2960
+        test_user3 = uapi.create_user(
2961
+            email='test3@test3.test3',
2962
+            password='pass',
2963
+            name='bob3',
2964
+            groups=groups,
2965
+            timezone='Europe/Paris',
2966
+            do_save=True,
2967
+            do_notify=False,
2968
+        )
2969
+        uapi.save(test_user)
2970
+        uapi.save(test_user2)
2971
+        uapi.save(test_user3)
2972
+        workspace_api = WorkspaceApi(
2973
+            current_user=admin,
2974
+            session=dbsession,
2975
+            config=self.app_config
2976
+
2977
+        )
2978
+        workspace = WorkspaceApi(
2979
+            current_user=admin,
2980
+            session=dbsession,
2981
+            config=self.app_config,
2982
+        ).create_workspace(
2983
+            'test workspace',
2984
+            save_now=True
2985
+        )
2986
+        role_api = RoleApi(
2987
+            current_user=admin,
2988
+            session=dbsession,
2989
+            config=self.app_config,
2990
+        )
2991
+        role_api.create_one(test_user, workspace, UserRoleInWorkspace.READER, False)
2992
+        role_api.create_one(test_user2, workspace, UserRoleInWorkspace.READER, False)
2993
+        transaction.commit()
2994
+        user_id = int(test_user.user_id)
2995
+
2996
+        self.testapp.authorization = (
2997
+            'Basic',
2998
+            (
2999
+                'test@test.test',
3000
+                'pass'
3001
+            )
3002
+        )
3003
+        params = {
3004
+            'acp': 'test',
3005
+        }
3006
+        res = self.testapp.get(
3007
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
3008
+            status=200,
3009
+            params=params
3010
+        )
3011
+        res = res.json_body
3012
+        assert len(res) == 2
3013
+        assert res[0]['user_id'] == test_user.user_id
3014
+        assert res[0]['public_name'] == test_user.display_name
3015
+        assert res[0]['avatar_url'] is None
3016
+
3017
+        assert res[1]['user_id'] == test_user2.user_id
3018
+        assert res[1]['public_name'] == test_user2.display_name
3019
+        assert res[1]['avatar_url'] is None
3020
+
3021
+
2649
 class TestSetEmailEndpoint(FunctionalTest):
3022
 class TestSetEmailEndpoint(FunctionalTest):
2650
     # -*- coding: utf-8 -*-
3023
     # -*- coding: utf-8 -*-
2651
     """
3024
     """
3025
             status=204,
3398
             status=204,
3026
         )
3399
         )
3027
         # Check After
3400
         # Check After
3401
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3402
+        uapi = UserApi(
3403
+            current_user=admin,
3404
+            session=dbsession,
3405
+            config=self.app_config,
3406
+        )
3028
         user = uapi.get_one(user_id)
3407
         user = uapi.get_one(user_id)
3029
         assert not user.validate_password('pass')
3408
         assert not user.validate_password('pass')
3030
         assert user.validate_password('mynewpassword')
3409
         assert user.validate_password('mynewpassword')
3080
             params=params,
3459
             params=params,
3081
             status=403,
3460
             status=403,
3082
         )
3461
         )
3462
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3463
+        uapi = UserApi(
3464
+            current_user=admin,
3465
+            session=dbsession,
3466
+            config=self.app_config,
3467
+        )
3083
         # Check After
3468
         # Check After
3084
         user = uapi.get_one(user_id)
3469
         user = uapi.get_one(user_id)
3085
         assert user.validate_password('pass')
3470
         assert user.validate_password('pass')
3138
             status=400,
3523
             status=400,
3139
         )
3524
         )
3140
         # Check After
3525
         # Check After
3526
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3527
+        uapi = UserApi(
3528
+            current_user=admin,
3529
+            session=dbsession,
3530
+            config=self.app_config,
3531
+        )
3141
         user = uapi.get_one(user_id)
3532
         user = uapi.get_one(user_id)
3142
         assert user.validate_password('pass')
3533
         assert user.validate_password('pass')
3143
         assert not user.validate_password('mynewpassword')
3534
         assert not user.validate_password('mynewpassword')
3195
             status=204,
3586
             status=204,
3196
         )
3587
         )
3197
         # Check After
3588
         # Check After
3589
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3590
+        uapi = UserApi(
3591
+            current_user=admin,
3592
+            session=dbsession,
3593
+            config=self.app_config,
3594
+        )
3198
         user = uapi.get_one(user_id)
3595
         user = uapi.get_one(user_id)
3199
         assert not user.validate_password('pass')
3596
         assert not user.validate_password('pass')
3200
         assert user.validate_password('mynewpassword')
3597
         assert user.validate_password('mynewpassword')

+ 98 - 27
backend/tracim_backend/tests/functional/test_workspaces.py View File

2
 """
2
 """
3
 Tests for /api/v2/workspaces subpath endpoints.
3
 Tests for /api/v2/workspaces subpath endpoints.
4
 """
4
 """
5
-
5
+import requests
6
 import transaction
6
 import transaction
7
 from depot.io.utils import FileIntent
7
 from depot.io.utils import FileIntent
8
 
8
 
41
         assert workspace['slug'] == 'business'
41
         assert workspace['slug'] == 'business'
42
         assert workspace['label'] == 'Business'
42
         assert workspace['label'] == 'Business'
43
         assert workspace['description'] == 'All importants documents'
43
         assert workspace['description'] == 'All importants documents'
44
-        assert len(workspace['sidebar_entries']) == 7
44
+        assert len(workspace['sidebar_entries']) == 5
45
 
45
 
46
+        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
47
+        # not fixed on active application/content-file
46
         sidebar_entry = workspace['sidebar_entries'][0]
48
         sidebar_entry = workspace['sidebar_entries'][0]
47
         assert sidebar_entry['slug'] == 'dashboard'
49
         assert sidebar_entry['slug'] == 'dashboard'
48
         assert sidebar_entry['label'] == 'Dashboard'
50
         assert sidebar_entry['label'] == 'Dashboard'
65
         assert sidebar_entry['fa_icon'] == "file-text-o"
67
         assert sidebar_entry['fa_icon'] == "file-text-o"
66
 
68
 
67
         sidebar_entry = workspace['sidebar_entries'][3]
69
         sidebar_entry = workspace['sidebar_entries'][3]
68
-        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
69
-        assert sidebar_entry['label'] == 'Markdown Plus Documents'
70
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
71
-        assert sidebar_entry['hexcolor'] == "#f12d2d"
72
-        assert sidebar_entry['fa_icon'] == "file-code-o"
73
-
74
-        sidebar_entry = workspace['sidebar_entries'][4]
75
         assert sidebar_entry['slug'] == 'contents/file'
70
         assert sidebar_entry['slug'] == 'contents/file'
76
         assert sidebar_entry['label'] == 'Files'
71
         assert sidebar_entry['label'] == 'Files'
77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
72
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78
         assert sidebar_entry['hexcolor'] == "#FF9900"
73
         assert sidebar_entry['hexcolor'] == "#FF9900"
79
         assert sidebar_entry['fa_icon'] == "paperclip"
74
         assert sidebar_entry['fa_icon'] == "paperclip"
80
 
75
 
81
-        sidebar_entry = workspace['sidebar_entries'][5]
76
+        sidebar_entry = workspace['sidebar_entries'][4]
82
         assert sidebar_entry['slug'] == 'contents/thread'
77
         assert sidebar_entry['slug'] == 'contents/thread'
83
         assert sidebar_entry['label'] == 'Threads'
78
         assert sidebar_entry['label'] == 'Threads'
84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
79
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
80
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
86
         assert sidebar_entry['fa_icon'] == "comments-o"
81
         assert sidebar_entry['fa_icon'] == "comments-o"
87
 
82
 
88
-        sidebar_entry = workspace['sidebar_entries'][6]
89
-        assert sidebar_entry['slug'] == 'calendar'
90
-        assert sidebar_entry['label'] == 'Calendar'
91
-        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
92
-        assert sidebar_entry['hexcolor'] == "#757575"
93
-        assert sidebar_entry['fa_icon'] == "calendar"
94
-
95
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
83
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
96
         """
84
         """
97
         Test update workspace
85
         Test update workspace
118
         assert workspace['slug'] == 'business'
106
         assert workspace['slug'] == 'business'
119
         assert workspace['label'] == 'Business'
107
         assert workspace['label'] == 'Business'
120
         assert workspace['description'] == 'All importants documents'
108
         assert workspace['description'] == 'All importants documents'
121
-        assert len(workspace['sidebar_entries']) == 7
109
+        assert len(workspace['sidebar_entries']) == 5
122
 
110
 
123
         # modify workspace
111
         # modify workspace
124
         res = self.testapp.put_json(
112
         res = self.testapp.put_json(
132
         assert workspace['slug'] == 'superworkspace'
120
         assert workspace['slug'] == 'superworkspace'
133
         assert workspace['label'] == 'superworkspace'
121
         assert workspace['label'] == 'superworkspace'
134
         assert workspace['description'] == 'mysuperdescription'
122
         assert workspace['description'] == 'mysuperdescription'
135
-        assert len(workspace['sidebar_entries']) == 7
123
+        assert len(workspace['sidebar_entries']) == 5
136
 
124
 
137
         # after
125
         # after
138
         res = self.testapp.get(
126
         res = self.testapp.get(
145
         assert workspace['slug'] == 'superworkspace'
133
         assert workspace['slug'] == 'superworkspace'
146
         assert workspace['label'] == 'superworkspace'
134
         assert workspace['label'] == 'superworkspace'
147
         assert workspace['description'] == 'mysuperdescription'
135
         assert workspace['description'] == 'mysuperdescription'
148
-        assert len(workspace['sidebar_entries']) == 7
136
+        assert len(workspace['sidebar_entries']) == 5
149
 
137
 
150
     def test_api__update_workspace__err_400__empty_label(self) -> None:
138
     def test_api__update_workspace__err_400__empty_label(self) -> None:
151
         """
139
         """
612
         assert user_role['workspace_id'] == 1
600
         assert user_role['workspace_id'] == 1
613
 
601
 
614
 
602
 
603
+class TestUserInvitationWithMailActivatedSync(FunctionalTest):
604
+
605
+    fixtures = [BaseFixture, ContentFixtures]
606
+    config_section = 'functional_test_with_mail_test_sync'
607
+
608
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
609
+        """
610
+        Create workspace member role
611
+        :return:
612
+        """
613
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
614
+        self.testapp.authorization = (
615
+            'Basic',
616
+            (
617
+                'admin@admin.admin',
618
+                'admin@admin.admin'
619
+            )
620
+        )
621
+        # create workspace role
622
+        params = {
623
+            'user_id': None,
624
+            'user_email_or_public_name': 'bob@bob.bob',
625
+            'role': 'content-manager',
626
+        }
627
+        res = self.testapp.post_json(
628
+            '/api/v2/workspaces/1/members',
629
+            status=200,
630
+            params=params,
631
+        )
632
+        user_role_found = res.json_body
633
+        assert user_role_found['role'] == 'content-manager'
634
+        assert user_role_found['user_id']
635
+        user_id = user_role_found['user_id']
636
+        assert user_role_found['workspace_id'] == 1
637
+        assert user_role_found['newly_created'] is True
638
+        assert user_role_found['email_sent'] is True
639
+
640
+        # check mail received
641
+        response = requests.get('http://127.0.0.1:8025/api/v1/messages')
642
+        response = response.json()
643
+        assert len(response) == 1
644
+        headers = response[0]['Content']['Headers']
645
+        assert headers['From'][0] == 'Tracim Notifications <test_user_from+0@localhost>'  # nopep8
646
+        assert headers['To'][0] == 'bob <bob@bob.bob>'
647
+        assert headers['Subject'][0] == '[TRACIM] Created account'
648
+
649
+        # TODO - G.M - 2018-08-02 - Place cleanup outside of the test
650
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
651
+
652
+
653
+class TestUserInvitationWithMailActivatedASync(FunctionalTest):
654
+
655
+    fixtures = [BaseFixture, ContentFixtures]
656
+    config_section = 'functional_test_with_mail_test_async'
657
+
658
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
659
+        """
660
+        Create workspace member role
661
+        :return:
662
+        """
663
+        self.testapp.authorization = (
664
+            'Basic',
665
+            (
666
+                'admin@admin.admin',
667
+                'admin@admin.admin'
668
+            )
669
+        )
670
+        # create workspace role
671
+        params = {
672
+            'user_id': None,
673
+            'user_email_or_public_name': 'bob@bob.bob',
674
+            'role': 'content-manager',
675
+        }
676
+        res = self.testapp.post_json(
677
+            '/api/v2/workspaces/1/members',
678
+            status=200,
679
+            params=params,
680
+        )
681
+        user_role_found = res.json_body
682
+        assert user_role_found['newly_created'] is True
683
+        assert user_role_found['email_sent'] is False
684
+
685
+
615
 class TestWorkspaceContents(FunctionalTest):
686
 class TestWorkspaceContents(FunctionalTest):
616
     """
687
     """
617
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
688
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
1442
         params = {
1513
         params = {
1443
             'parent_id': None,
1514
             'parent_id': None,
1444
             'label': 'GenericCreatedContent',
1515
             'label': 'GenericCreatedContent',
1445
-            'content_type': 'markdownpage',
1516
+            'content_type': 'html-document',
1446
         }
1517
         }
1447
         res = self.testapp.post_json(
1518
         res = self.testapp.post_json(
1448
             '/api/v2/workspaces/1/contents',
1519
             '/api/v2/workspaces/1/contents',
1453
         assert res.json_body
1524
         assert res.json_body
1454
         assert res.json_body['status'] == 'open'
1525
         assert res.json_body['status'] == 'open'
1455
         assert res.json_body['content_id']
1526
         assert res.json_body['content_id']
1456
-        assert res.json_body['content_type'] == 'markdownpage'
1527
+        assert res.json_body['content_type'] == 'html-document'
1457
         assert res.json_body['is_archived'] is False
1528
         assert res.json_body['is_archived'] is False
1458
         assert res.json_body['is_deleted'] is False
1529
         assert res.json_body['is_deleted'] is False
1459
         assert res.json_body['workspace_id'] == 1
1530
         assert res.json_body['workspace_id'] == 1
1484
         )
1555
         )
1485
         params = {
1556
         params = {
1486
             'label': 'GenericCreatedContent',
1557
             'label': 'GenericCreatedContent',
1487
-            'content_type': 'markdownpage',
1558
+            'content_type': 'html-document',
1488
         }
1559
         }
1489
         res = self.testapp.post_json(
1560
         res = self.testapp.post_json(
1490
             '/api/v2/workspaces/1/contents',
1561
             '/api/v2/workspaces/1/contents',
1495
         assert res.json_body
1566
         assert res.json_body
1496
         assert res.json_body['status'] == 'open'
1567
         assert res.json_body['status'] == 'open'
1497
         assert res.json_body['content_id']
1568
         assert res.json_body['content_id']
1498
-        assert res.json_body['content_type'] == 'markdownpage'
1569
+        assert res.json_body['content_type'] == 'html-document'
1499
         assert res.json_body['is_archived'] is False
1570
         assert res.json_body['is_archived'] is False
1500
         assert res.json_body['is_deleted'] is False
1571
         assert res.json_body['is_deleted'] is False
1501
         assert res.json_body['workspace_id'] == 1
1572
         assert res.json_body['workspace_id'] == 1
1548
         )
1619
         )
1549
         params = {
1620
         params = {
1550
             'label': 'GenericCreatedContent',
1621
             'label': 'GenericCreatedContent',
1551
-            'content_type': 'markdownpage',
1622
+            'content_type': 'html-document',
1552
             'parent_id': 10,
1623
             'parent_id': 10,
1553
         }
1624
         }
1554
         res = self.testapp.post_json(
1625
         res = self.testapp.post_json(
1560
         assert res.json_body
1631
         assert res.json_body
1561
         assert res.json_body['status'] == 'open'
1632
         assert res.json_body['status'] == 'open'
1562
         assert res.json_body['content_id']
1633
         assert res.json_body['content_id']
1563
-        assert res.json_body['content_type'] == 'markdownpage'
1634
+        assert res.json_body['content_type'] == 'html-document'
1564
         assert res.json_body['is_archived'] is False
1635
         assert res.json_body['is_archived'] is False
1565
         assert res.json_body['is_deleted'] is False
1636
         assert res.json_body['is_deleted'] is False
1566
         assert res.json_body['workspace_id'] == 1
1637
         assert res.json_body['workspace_id'] == 1
1591
         )
1662
         )
1592
         params = {
1663
         params = {
1593
             'label': '',
1664
             'label': '',
1594
-            'content_type': 'markdownpage',
1665
+            'content_type': 'html-document',
1595
         }
1666
         }
1596
         res = self.testapp.post_json(
1667
         res = self.testapp.post_json(
1597
             '/api/v2/workspaces/1/contents',
1668
             '/api/v2/workspaces/1/contents',

+ 183 - 0
backend/tracim_backend/tests/library/test_user_api.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import pytest
2
 import pytest
3
 import transaction
3
 import transaction
4
+from tracim_backend import models
4
 
5
 
5
 from tracim_backend.exceptions import AuthenticationFailed
6
 from tracim_backend.exceptions import AuthenticationFailed
7
+from tracim_backend.exceptions import TooShortAutocompleteString
6
 from tracim_backend.exceptions import UserDoesNotExist
8
 from tracim_backend.exceptions import UserDoesNotExist
7
 from tracim_backend.exceptions import UserNotActive
9
 from tracim_backend.exceptions import UserNotActive
8
 from tracim_backend.lib.core.group import GroupApi
10
 from tracim_backend.lib.core.group import GroupApi
9
 from tracim_backend.lib.core.user import UserApi
11
 from tracim_backend.lib.core.user import UserApi
12
+from tracim_backend.lib.core.userworkspace import RoleApi
13
+from tracim_backend.lib.core.workspace import WorkspaceApi
10
 from tracim_backend.models import User
14
 from tracim_backend.models import User
11
 from tracim_backend.models.context_models import UserInContext
15
 from tracim_backend.models.context_models import UserInContext
16
+from tracim_backend.models.data import UserRoleInWorkspace
12
 from tracim_backend.tests import DefaultTest
17
 from tracim_backend.tests import DefaultTest
13
 from tracim_backend.tests import eq_
18
 from tracim_backend.tests import eq_
14
 
19
 
107
         # u1 + Admin user from BaseFixture
112
         # u1 + Admin user from BaseFixture
108
         assert 2 == len(users)
113
         assert 2 == len(users)
109
 
114
 
115
+    def test_unit__get_known__user__admin__too_short_acp_str(self):
116
+        api = UserApi(
117
+            current_user=None,
118
+            session=self.session,
119
+            config=self.config,
120
+        )
121
+        u1 = api.create_user(
122
+            email='email@email',
123
+            name='name',
124
+            do_notify=False,
125
+            do_save=True,
126
+        )
127
+        with pytest.raises(TooShortAutocompleteString):
128
+            api.get_known_user('e')
129
+
130
+    def test_unit__get_known__user__admin__by_email(self):
131
+        api = UserApi(
132
+            current_user=None,
133
+            session=self.session,
134
+            config=self.config,
135
+        )
136
+        u1 = api.create_user(
137
+            email='email@email',
138
+            name='name',
139
+            do_notify=False,
140
+            do_save=True,
141
+        )
142
+
143
+        users = api.get_known_user('email')
144
+        assert len(users) == 1
145
+        assert users[0] == u1
146
+
147
+    def test_unit__get_known__user__user__no_workspace_empty_known_user(self):
148
+        admin = self.session.query(models.User) \
149
+            .filter(models.User.email == 'admin@admin.admin') \
150
+            .one()
151
+        api = UserApi(
152
+            current_user=admin,
153
+            session=self.session,
154
+            config=self.config,
155
+        )
156
+        u1 = api.create_user(
157
+            email='email@email',
158
+            name='name',
159
+            do_notify=False,
160
+            do_save=True,
161
+        )
162
+        api2 = UserApi(
163
+            current_user=u1,
164
+            session=self.session,
165
+            config=self.config,
166
+        )
167
+        users = api2.get_known_user('email')
168
+        assert len(users) == 0
169
+
170
+    def test_unit__get_known__user__same_workspaces_users_by_name(self):
171
+        admin = self.session.query(models.User) \
172
+            .filter(models.User.email == 'admin@admin.admin') \
173
+            .one()
174
+        api = UserApi(
175
+            current_user=None,
176
+            session=self.session,
177
+            config=self.config,
178
+        )
179
+        u1 = api.create_user(
180
+            email='email@email',
181
+            name='name',
182
+            do_notify=False,
183
+            do_save=True,
184
+        )
185
+        u2 = api.create_user(
186
+            email='email2@email2',
187
+            name='name2',
188
+            do_notify=False,
189
+            do_save=True,
190
+        )
191
+        u3 = api.create_user(
192
+            email='notfound@notfound',
193
+            name='notfound',
194
+            do_notify=False,
195
+            do_save=True,
196
+        )
197
+        wapi = WorkspaceApi(
198
+            current_user=admin,
199
+            session=self.session,
200
+            config=self.app_config,
201
+        )
202
+        workspace = wapi.create_workspace(
203
+            'test workspace n°1',
204
+            save_now=True)
205
+        role_api = RoleApi(
206
+            current_user=admin,
207
+            session=self.session,
208
+            config=self.app_config,
209
+        )
210
+        role_api.create_one(u1, workspace, UserRoleInWorkspace.READER, False)
211
+        role_api.create_one(u2, workspace, UserRoleInWorkspace.READER, False)
212
+        role_api.create_one(u3, workspace, UserRoleInWorkspace.READER, False)
213
+        api2 = UserApi(
214
+            current_user=u1,
215
+            session=self.session,
216
+            config=self.config,
217
+        )
218
+        users = api2.get_known_user('name')
219
+        assert len(users) == 2
220
+        assert users[0] == u1
221
+        assert users[1] == u2
222
+
223
+    def test_unit__get_known__user__same_workspaces_users_by_email(self):
224
+        admin = self.session.query(models.User) \
225
+            .filter(models.User.email == 'admin@admin.admin') \
226
+            .one()
227
+        api = UserApi(
228
+            current_user=None,
229
+            session=self.session,
230
+            config=self.config,
231
+        )
232
+        u1 = api.create_user(
233
+            email='email@email',
234
+            name='name',
235
+            do_notify=False,
236
+            do_save=True,
237
+        )
238
+        u2 = api.create_user(
239
+            email='email2@email2',
240
+            name='name2',
241
+            do_notify=False,
242
+            do_save=True,
243
+        )
244
+        u3 = api.create_user(
245
+            email='notfound@notfound',
246
+            name='notfound',
247
+            do_notify=False,
248
+            do_save=True,
249
+        )
250
+        wapi = WorkspaceApi(
251
+            current_user=admin,
252
+            session=self.session,
253
+            config=self.app_config,
254
+        )
255
+        workspace = wapi.create_workspace(
256
+            'test workspace n°1',
257
+            save_now=True)
258
+        role_api = RoleApi(
259
+            current_user=admin,
260
+            session=self.session,
261
+            config=self.app_config,
262
+        )
263
+        role_api.create_one(u1, workspace, UserRoleInWorkspace.READER, False)
264
+        role_api.create_one(u2, workspace, UserRoleInWorkspace.READER, False)
265
+        role_api.create_one(u3, workspace, UserRoleInWorkspace.READER, False)
266
+        api2 = UserApi(
267
+            current_user=u1,
268
+            session=self.session,
269
+            config=self.config,
270
+        )
271
+        users = api2.get_known_user('email')
272
+        assert len(users) == 2
273
+        assert users[0] == u1
274
+        assert users[1] == u2
275
+
276
+    def test_unit__get_known__user__admin__by_name(self):
277
+        api = UserApi(
278
+            current_user=None,
279
+            session=self.session,
280
+            config=self.config,
281
+        )
282
+        u1 = api.create_user(
283
+            email='email@email',
284
+            name='name',
285
+            do_notify=False,
286
+            do_save=True,
287
+        )
288
+
289
+        users = api.get_known_user('nam')
290
+        assert len(users) == 1
291
+        assert users[0] == u1
292
+
110
     def test_unit__get_one__ok__nominal_case(self):
293
     def test_unit__get_one__ok__nominal_case(self):
111
         api = UserApi(
294
         api = UserApi(
112
             current_user=None,
295
             current_user=None,

+ 14 - 0
backend/tracim_backend/tests/library/tests_utils.py View File

1
+import string
2
+
3
+from tracim_backend.lib.utils.utils import password_generator
4
+from tracim_backend.lib.utils.utils import ALLOWED_AUTOGEN_PASSWORD_CHAR
5
+from tracim_backend.lib.utils.utils import DEFAULT_PASSWORD_GEN_CHAR_LENGTH
6
+
7
+
8
+class TestPasswordGenerator(object):
9
+
10
+    def test_password_generator_ok_nominal_case(self):
11
+        password = password_generator()
12
+        assert len(password) == DEFAULT_PASSWORD_GEN_CHAR_LENGTH
13
+        for char in password:
14
+            assert char in ALLOWED_AUTOGEN_PASSWORD_CHAR

+ 6 - 0
backend/tracim_backend/views/contents_api/comment_controller.py View File

41
         # login = hapic_data.body
41
         # login = hapic_data.body
42
         app_config = request.registry.settings['CFG']
42
         app_config = request.registry.settings['CFG']
43
         api = ContentApi(
43
         api = ContentApi(
44
+            show_archived=True,
45
+            show_deleted=True,
44
             current_user=request.current_user,
46
             current_user=request.current_user,
45
             session=request.dbsession,
47
             session=request.dbsession,
46
             config=app_config,
48
             config=app_config,
68
         # login = hapic_data.body
70
         # login = hapic_data.body
69
         app_config = request.registry.settings['CFG']
71
         app_config = request.registry.settings['CFG']
70
         api = ContentApi(
72
         api = ContentApi(
73
+            show_archived=True,
74
+            show_deleted=True,
71
             current_user=request.current_user,
75
             current_user=request.current_user,
72
             session=request.dbsession,
76
             session=request.dbsession,
73
             config=app_config,
77
             config=app_config,
97
         """
101
         """
98
         app_config = request.registry.settings['CFG']
102
         app_config = request.registry.settings['CFG']
99
         api = ContentApi(
103
         api = ContentApi(
104
+            show_archived=True,
105
+            show_deleted=True,
100
             current_user=request.current_user,
106
             current_user=request.current_user,
101
             session=request.dbsession,
107
             session=request.dbsession,
102
             config=app_config,
108
             config=app_config,

+ 28 - 0
backend/tracim_backend/views/contents_api/file_controller.py View File

61
         """
61
         """
62
         app_config = request.registry.settings['CFG']
62
         app_config = request.registry.settings['CFG']
63
         api = ContentApi(
63
         api = ContentApi(
64
+            show_archived=True,
65
+            show_deleted=True,
64
             current_user=request.current_user,
66
             current_user=request.current_user,
65
             session=request.dbsession,
67
             session=request.dbsession,
66
             config=app_config,
68
             config=app_config,
95
         """
97
         """
96
         app_config = request.registry.settings['CFG']
98
         app_config = request.registry.settings['CFG']
97
         api = ContentApi(
99
         api = ContentApi(
100
+            show_archived=True,
101
+            show_deleted=True,
98
             current_user=request.current_user,
102
             current_user=request.current_user,
99
             session=request.dbsession,
103
             session=request.dbsession,
100
             config=app_config,
104
             config=app_config,
120
         """
124
         """
121
         app_config = request.registry.settings['CFG']
125
         app_config = request.registry.settings['CFG']
122
         api = ContentApi(
126
         api = ContentApi(
127
+            show_archived=True,
128
+            show_deleted=True,
123
             current_user=request.current_user,
129
             current_user=request.current_user,
124
             session=request.dbsession,
130
             session=request.dbsession,
125
             config=app_config,
131
             config=app_config,
154
         """
160
         """
155
         app_config = request.registry.settings['CFG']
161
         app_config = request.registry.settings['CFG']
156
         api = ContentApi(
162
         api = ContentApi(
163
+            show_archived=True,
164
+            show_deleted=True,
157
             current_user=request.current_user,
165
             current_user=request.current_user,
158
             session=request.dbsession,
166
             session=request.dbsession,
159
             config=app_config,
167
             config=app_config,
181
         """
189
         """
182
         app_config = request.registry.settings['CFG']
190
         app_config = request.registry.settings['CFG']
183
         api = ContentApi(
191
         api = ContentApi(
192
+            show_archived=True,
193
+            show_deleted=True,
184
             current_user=request.current_user,
194
             current_user=request.current_user,
185
             session=request.dbsession,
195
             session=request.dbsession,
186
             config=app_config,
196
             config=app_config,
205
         """
215
         """
206
         app_config = request.registry.settings['CFG']
216
         app_config = request.registry.settings['CFG']
207
         api = ContentApi(
217
         api = ContentApi(
218
+            show_archived=True,
219
+            show_deleted=True,
208
             current_user=request.current_user,
220
             current_user=request.current_user,
209
             session=request.dbsession,
221
             session=request.dbsession,
210
             config=app_config,
222
             config=app_config,
238
         """
250
         """
239
         app_config = request.registry.settings['CFG']
251
         app_config = request.registry.settings['CFG']
240
         api = ContentApi(
252
         api = ContentApi(
253
+            show_archived=True,
254
+            show_deleted=True,
241
             current_user=request.current_user,
255
             current_user=request.current_user,
242
             session=request.dbsession,
256
             session=request.dbsession,
243
             config=app_config,
257
             config=app_config,
270
         """
284
         """
271
         app_config = request.registry.settings['CFG']
285
         app_config = request.registry.settings['CFG']
272
         api = ContentApi(
286
         api = ContentApi(
287
+            show_archived=True,
288
+            show_deleted=True,
273
             current_user=request.current_user,
289
             current_user=request.current_user,
274
             session=request.dbsession,
290
             session=request.dbsession,
275
             config=app_config,
291
             config=app_config,
301
         """
317
         """
302
         app_config = request.registry.settings['CFG']
318
         app_config = request.registry.settings['CFG']
303
         api = ContentApi(
319
         api = ContentApi(
320
+            show_archived=True,
321
+            show_deleted=True,
304
             current_user=request.current_user,
322
             current_user=request.current_user,
305
             session=request.dbsession,
323
             session=request.dbsession,
306
             config=app_config,
324
             config=app_config,
334
         """
352
         """
335
         app_config = request.registry.settings['CFG']
353
         app_config = request.registry.settings['CFG']
336
         api = ContentApi(
354
         api = ContentApi(
355
+            show_archived=True,
356
+            show_deleted=True,
337
             current_user=request.current_user,
357
             current_user=request.current_user,
338
             session=request.dbsession,
358
             session=request.dbsession,
339
             config=app_config,
359
             config=app_config,
352
         """
372
         """
353
         app_config = request.registry.settings['CFG']
373
         app_config = request.registry.settings['CFG']
354
         api = ContentApi(
374
         api = ContentApi(
375
+            show_archived=True,
376
+            show_deleted=True,
355
             current_user=request.current_user,
377
             current_user=request.current_user,
356
             session=request.dbsession,
378
             session=request.dbsession,
357
             config=app_config,
379
             config=app_config,
375
         """
397
         """
376
         app_config = request.registry.settings['CFG']
398
         app_config = request.registry.settings['CFG']
377
         api = ContentApi(
399
         api = ContentApi(
400
+            show_archived=True,
401
+            show_deleted=True,
378
             current_user=request.current_user,
402
             current_user=request.current_user,
379
             session=request.dbsession,
403
             session=request.dbsession,
380
             config=app_config,
404
             config=app_config,
413
         """
437
         """
414
         app_config = request.registry.settings['CFG']
438
         app_config = request.registry.settings['CFG']
415
         api = ContentApi(
439
         api = ContentApi(
440
+            show_archived=True,
441
+            show_deleted=True,
416
             current_user=request.current_user,
442
             current_user=request.current_user,
417
             session=request.dbsession,
443
             session=request.dbsession,
418
             config=app_config,
444
             config=app_config,
440
         """
466
         """
441
         app_config = request.registry.settings['CFG']
467
         app_config = request.registry.settings['CFG']
442
         api = ContentApi(
468
         api = ContentApi(
469
+            show_archived=True,
470
+            show_deleted=True,
443
             current_user=request.current_user,
471
             current_user=request.current_user,
444
             session=request.dbsession,
472
             session=request.dbsession,
445
             config=app_config,
473
             config=app_config,

+ 8 - 0
backend/tracim_backend/views/contents_api/html_document_controller.py View File

46
         """
46
         """
47
         app_config = request.registry.settings['CFG']
47
         app_config = request.registry.settings['CFG']
48
         api = ContentApi(
48
         api = ContentApi(
49
+            show_archived=True,
50
+            show_deleted=True,
49
             current_user=request.current_user,
51
             current_user=request.current_user,
50
             session=request.dbsession,
52
             session=request.dbsession,
51
             config=app_config,
53
             config=app_config,
69
         """
71
         """
70
         app_config = request.registry.settings['CFG']
72
         app_config = request.registry.settings['CFG']
71
         api = ContentApi(
73
         api = ContentApi(
74
+            show_archived=True,
75
+            show_deleted=True,
72
             current_user=request.current_user,
76
             current_user=request.current_user,
73
             session=request.dbsession,
77
             session=request.dbsession,
74
             config=app_config,
78
             config=app_config,
107
         """
111
         """
108
         app_config = request.registry.settings['CFG']
112
         app_config = request.registry.settings['CFG']
109
         api = ContentApi(
113
         api = ContentApi(
114
+            show_archived=True,
115
+            show_deleted=True,
110
             current_user=request.current_user,
116
             current_user=request.current_user,
111
             session=request.dbsession,
117
             session=request.dbsession,
112
             config=app_config,
118
             config=app_config,
138
         """
144
         """
139
         app_config = request.registry.settings['CFG']
145
         app_config = request.registry.settings['CFG']
140
         api = ContentApi(
146
         api = ContentApi(
147
+            show_archived=True,
148
+            show_deleted=True,
141
             current_user=request.current_user,
149
             current_user=request.current_user,
142
             session=request.dbsession,
150
             session=request.dbsession,
143
             config=app_config,
151
             config=app_config,

+ 8 - 0
backend/tracim_backend/views/contents_api/threads_controller.py View File

45
         """
45
         """
46
         app_config = request.registry.settings['CFG']
46
         app_config = request.registry.settings['CFG']
47
         api = ContentApi(
47
         api = ContentApi(
48
+            show_archived=True,
49
+            show_deleted=True,
48
             current_user=request.current_user,
50
             current_user=request.current_user,
49
             session=request.dbsession,
51
             session=request.dbsession,
50
             config=app_config,
52
             config=app_config,
68
         """
70
         """
69
         app_config = request.registry.settings['CFG']
71
         app_config = request.registry.settings['CFG']
70
         api = ContentApi(
72
         api = ContentApi(
73
+            show_archived=True,
74
+            show_deleted=True,
71
             current_user=request.current_user,
75
             current_user=request.current_user,
72
             session=request.dbsession,
76
             session=request.dbsession,
73
             config=app_config,
77
             config=app_config,
106
         """
110
         """
107
         app_config = request.registry.settings['CFG']
111
         app_config = request.registry.settings['CFG']
108
         api = ContentApi(
112
         api = ContentApi(
113
+            show_archived=True,
114
+            show_deleted=True,
109
             current_user=request.current_user,
115
             current_user=request.current_user,
110
             session=request.dbsession,
116
             session=request.dbsession,
111
             config=app_config,
117
             config=app_config,
132
         """
138
         """
133
         app_config = request.registry.settings['CFG']
139
         app_config = request.registry.settings['CFG']
134
         api = ContentApi(
140
         api = ContentApi(
141
+            show_archived=True,
142
+            show_deleted=True,
135
             current_user=request.current_user,
143
             current_user=request.current_user,
136
             session=request.dbsession,
144
             session=request.dbsession,
137
             config=app_config,
145
             config=app_config,

+ 13 - 0
backend/tracim_backend/views/core_api/schemas.py View File

2
 import marshmallow
2
 import marshmallow
3
 from marshmallow import post_load
3
 from marshmallow import post_load
4
 from marshmallow.validate import OneOf
4
 from marshmallow.validate import OneOf
5
+from marshmallow.validate import Length
5
 from marshmallow.validate import Range
6
 from marshmallow.validate import Range
6
 
7
 
7
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
12
 from tracim_backend.models.contents import open_status
13
 from tracim_backend.models.contents import open_status
13
 from tracim_backend.models.context_models import ActiveContentFilter
14
 from tracim_backend.models.context_models import ActiveContentFilter
14
 from tracim_backend.models.context_models import FolderContentUpdate
15
 from tracim_backend.models.context_models import FolderContentUpdate
16
+from tracim_backend.models.context_models import AutocompleteQuery
15
 from tracim_backend.models.context_models import ContentIdsQuery
17
 from tracim_backend.models.context_models import ContentIdsQuery
16
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
18
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
17
 from tracim_backend.models.context_models import ContentCreation
19
 from tracim_backend.models.context_models import ContentCreation
293
         return CommentPath(**data)
295
         return CommentPath(**data)
294
 
296
 
295
 
297
 
298
+class AutocompleteQuerySchema(marshmallow.Schema):
299
+    acp = marshmallow.fields.Str(
300
+        example='test',
301
+        description='search text to query',
302
+        validate=Length(min=2),
303
+    )
304
+    @post_load
305
+    def make_autocomplete(self, data):
306
+        return AutocompleteQuery(**data)
307
+
308
+
296
 class PageQuerySchema(marshmallow.Schema):
309
 class PageQuerySchema(marshmallow.Schema):
297
     page = marshmallow.fields.Int(
310
     page = marshmallow.fields.Int(
298
         example=2,
311
         example=2,

+ 57 - 0
backend/tracim_backend/views/core_api/user_controller.py View File

18
 from tracim_backend.exceptions import WrongUserPassword
18
 from tracim_backend.exceptions import WrongUserPassword
19
 from tracim_backend.exceptions import PasswordDoNotMatch
19
 from tracim_backend.exceptions import PasswordDoNotMatch
20
 from tracim_backend.views.core_api.schemas import UserSchema
20
 from tracim_backend.views.core_api.schemas import UserSchema
21
+from tracim_backend.views.core_api.schemas import AutocompleteQuerySchema
22
+from tracim_backend.views.core_api.schemas import UserDigestSchema
21
 from tracim_backend.views.core_api.schemas import SetEmailSchema
23
 from tracim_backend.views.core_api.schemas import SetEmailSchema
22
 from tracim_backend.views.core_api.schemas import SetPasswordSchema
24
 from tracim_backend.views.core_api.schemas import SetPasswordSchema
23
 from tracim_backend.views.core_api.schemas import UserInfosSchema
25
 from tracim_backend.views.core_api.schemas import UserInfosSchema
37
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
38
 
40
 
39
 
41
 
42
+
40
 class UserController(Controller):
43
 class UserController(Controller):
41
 
44
 
42
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
45
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
77
         return uapi.get_user_with_context(request.candidate_user)
80
         return uapi.get_user_with_context(request.candidate_user)
78
 
81
 
79
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
82
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
83
+    @require_profile(Group.TIM_ADMIN)
84
+    @hapic.output_body(UserDigestSchema(many=True))
85
+    def users(self, context, request: TracimRequest, hapic_data=None):
86
+        """
87
+        Get all users
88
+        """
89
+        app_config = request.registry.settings['CFG']
90
+        uapi = UserApi(
91
+            current_user=request.current_user,  # User
92
+            session=request.dbsession,
93
+            config=app_config,
94
+        )
95
+        users = uapi.get_all()
96
+        context_users = [
97
+            uapi.get_user_with_context(user) for user in users
98
+        ]
99
+        return context_users
100
+
101
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
102
+    @require_same_user_or_profile(Group.TIM_MANAGER)
103
+    @hapic.input_path(UserIdPathSchema())
104
+    @hapic.input_query(AutocompleteQuerySchema())
105
+    @hapic.output_body(UserDigestSchema(many=True))
106
+    def known_members(self, context, request: TracimRequest, hapic_data=None):
107
+        """
108
+        Get known users list
109
+        """
110
+        app_config = request.registry.settings['CFG']
111
+        uapi = UserApi(
112
+            current_user=request.candidate_user,  # User
113
+            session=request.dbsession,
114
+            config=app_config,
115
+        )
116
+        users = uapi.get_known_user(acp=hapic_data.query.acp)
117
+        context_users = [
118
+            uapi.get_user_with_context(user) for user in users
119
+        ]
120
+        return context_users
121
+
122
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
80
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
123
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
81
     @require_same_user_or_profile(Group.TIM_ADMIN)
124
     @require_same_user_or_profile(Group.TIM_ADMIN)
82
     @hapic.input_body(SetEmailSchema())
125
     @hapic.input_body(SetEmailSchema())
329
         """
372
         """
330
         app_config = request.registry.settings['CFG']
373
         app_config = request.registry.settings['CFG']
331
         api = ContentApi(
374
         api = ContentApi(
375
+            show_archived=True,
376
+            show_deleted=True,
332
             current_user=request.candidate_user,
377
             current_user=request.candidate_user,
333
             session=request.dbsession,
378
             session=request.dbsession,
334
             config=app_config,
379
             config=app_config,
346
         """
391
         """
347
         app_config = request.registry.settings['CFG']
392
         app_config = request.registry.settings['CFG']
348
         api = ContentApi(
393
         api = ContentApi(
394
+            show_archived=True,
395
+            show_deleted=True,
349
             current_user=request.candidate_user,
396
             current_user=request.candidate_user,
350
             session=request.dbsession,
397
             session=request.dbsession,
351
             config=app_config,
398
             config=app_config,
363
         """
410
         """
364
         app_config = request.registry.settings['CFG']
411
         app_config = request.registry.settings['CFG']
365
         api = ContentApi(
412
         api = ContentApi(
413
+            show_archived=True,
414
+            show_deleted=True,
366
             current_user=request.candidate_user,
415
             current_user=request.candidate_user,
367
             session=request.dbsession,
416
             session=request.dbsession,
368
             config=app_config,
417
             config=app_config,
384
         configurator.add_route('user', '/users/{user_id}', request_method='GET')  # nopep8
433
         configurator.add_route('user', '/users/{user_id}', request_method='GET')  # nopep8
385
         configurator.add_view(self.user, route_name='user')
434
         configurator.add_view(self.user, route_name='user')
386
 
435
 
436
+        # users lists
437
+        configurator.add_route('users', '/users', request_method='GET')  # nopep8
438
+        configurator.add_view(self.users, route_name='users')
439
+
440
+        # known members lists
441
+        configurator.add_route('known_members', '/users/{user_id}/known_members', request_method='GET')  # nopep8
442
+        configurator.add_view(self.known_members, route_name='known_members')
443
+
387
         # set user email
444
         # set user email
388
         configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT')  # nopep8
445
         configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT')  # nopep8
389
         configurator.add_view(self.set_user_email, route_name='set_user_email')
446
         configurator.add_view(self.set_user_email, route_name='set_user_email')

+ 17 - 2
backend/tracim_backend/views/core_api/workspace_controller.py View File

31
 from tracim_backend.exceptions import WorkspacesDoNotMatch
31
 from tracim_backend.exceptions import WorkspacesDoNotMatch
32
 from tracim_backend.exceptions import ParentNotFound
32
 from tracim_backend.exceptions import ParentNotFound
33
 from tracim_backend.views.controllers import Controller
33
 from tracim_backend.views.controllers import Controller
34
+from tracim_backend.lib.utils.utils import password_generator
34
 from tracim_backend.views.core_api.schemas import FilterContentQuerySchema
35
 from tracim_backend.views.core_api.schemas import FilterContentQuerySchema
35
 from tracim_backend.views.core_api.schemas import WorkspaceMemberCreationSchema
36
 from tracim_backend.views.core_api.schemas import WorkspaceMemberCreationSchema
36
 from tracim_backend.views.core_api.schemas import WorkspaceMemberInviteSchema
37
 from tracim_backend.views.core_api.schemas import WorkspaceMemberInviteSchema
213
                 # TODO - G.M - 2018-07-05 - [UserCreation] Reenable email
214
                 # TODO - G.M - 2018-07-05 - [UserCreation] Reenable email
214
                 # notification for creation
215
                 # notification for creation
215
                 user = uapi.create_user(
216
                 user = uapi.create_user(
216
-                    hapic_data.body.user_email_or_public_name,
217
-                    do_notify=False
217
+                    email=hapic_data.body.user_email_or_public_name,
218
+                    password= password_generator(),
219
+                    do_notify=True
218
                 )  # nopep8
220
                 )  # nopep8
219
                 newly_created = True
221
                 newly_created = True
222
+                if app_config.EMAIL_NOTIFICATION_ACTIVATED and \
223
+                        app_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower() == 'sync':
224
+                    email_sent = True
225
+
220
             except EmailValidationFailed:
226
             except EmailValidationFailed:
221
                 raise UserCreationFailed('no valid mail given')
227
                 raise UserCreationFailed('no valid mail given')
228
+
222
         role = rapi.create_one(
229
         role = rapi.create_one(
223
             user=user,
230
             user=user,
224
             workspace=request.current_workspace,
231
             workspace=request.current_workspace,
328
         move_data = hapic_data.body
335
         move_data = hapic_data.body
329
 
336
 
330
         api = ContentApi(
337
         api = ContentApi(
338
+            show_archived=True,
339
+            show_deleted=True,
331
             current_user=request.current_user,
340
             current_user=request.current_user,
332
             session=request.dbsession,
341
             session=request.dbsession,
333
             config=app_config,
342
             config=app_config,
375
         app_config = request.registry.settings['CFG']
384
         app_config = request.registry.settings['CFG']
376
         path_data = hapic_data.path
385
         path_data = hapic_data.path
377
         api = ContentApi(
386
         api = ContentApi(
387
+            show_archived=True,
388
+            show_deleted=True,
378
             current_user=request.current_user,
389
             current_user=request.current_user,
379
             session=request.dbsession,
390
             session=request.dbsession,
380
             config=app_config,
391
             config=app_config,
411
             session=request.dbsession,
422
             session=request.dbsession,
412
             config=app_config,
423
             config=app_config,
413
             show_deleted=True,
424
             show_deleted=True,
425
+            show_archived=True,
414
         )
426
         )
415
         content = api.get_one(
427
         content = api.get_one(
416
             path_data.content_id,
428
             path_data.content_id,
440
         app_config = request.registry.settings['CFG']
452
         app_config = request.registry.settings['CFG']
441
         path_data = hapic_data.path
453
         path_data = hapic_data.path
442
         api = ContentApi(
454
         api = ContentApi(
455
+            show_archived=True,
456
+            show_deleted=True,
443
             current_user=request.current_user,
457
             current_user=request.current_user,
444
             session=request.dbsession,
458
             session=request.dbsession,
445
             config=app_config,
459
             config=app_config,
473
             session=request.dbsession,
487
             session=request.dbsession,
474
             config=app_config,
488
             config=app_config,
475
             show_archived=True,
489
             show_archived=True,
490
+            show_deleted=True,
476
         )
491
         )
477
         content = api.get_one(
492
         content = api.get_one(
478
             path_data.content_id,
493
             path_data.content_id,

+ 1 - 1
bash_library.sh View File

5
 NC='\033[0m' # No Color
5
 NC='\033[0m' # No Color
6
 
6
 
7
 function log {
7
 function log {
8
-    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}\n"
8
+    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}"
9
 }
9
 }

+ 51 - 54
build_full_frontend.sh View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
+# shellcheck disable=SC1091
3
 . bash_library.sh # source bash_library.sh
4
 . bash_library.sh # source bash_library.sh
4
 
5
 
5
 windoz=""
6
 windoz=""
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
 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
 
12
 
12
 # Tracim Lib
13
 # Tracim Lib
14
+log "build frontend_lib"
15
+(
16
+  cd frontend_lib || exit
17
+  npm run buildtracimlib$windoz
18
+)
13
 
19
 
14
-log "cd frontend_lib"
15
-cd frontend_lib
16
-log "npm run buildtracimlib$windoz"
17
-npm run buildtracimlib$windoz
18
-cd -
19
 
20
 
20
 # app Html Document
21
 # app Html Document
22
+log "build frontend_app_html-document"
23
+(
24
+  cd frontend_app_html-document || exit
25
+  npm run build$windoz
26
+)
21
 
27
 
22
-log "cd frontend_app_html-document"
23
-cd frontend_app_html-document
28
+log "copying built file to frontend/"
29
+cp frontend_app_html-document/dist/html-document.app.js frontend/dist/app/
24
 
30
 
25
-log "npm run build$windoz # for frontend_app_html-document"
26
-npm run build$windoz
31
+log "copying en translation.json"
32
+cp frontend_app_html-document/i18next.scanner/en/translation.json frontend/dist/app/html-document_en_translation.json
27
 
33
 
28
-log "cp dist/html-document.app.js"
29
-cp dist/html-document.app.js ../frontend/dist/app
34
+log "copying fr translation.json"
35
+cp frontend_app_html-document/i18next.scanner/fr/translation.json frontend/dist/app/html-document_fr_translation.json
30
 
36
 
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
36
-cd -
37
 
37
 
38
 # app Thread
38
 # app Thread
39
+log "build frontend_app_thread"
40
+(
41
+  cd frontend_app_thread || exit
42
+  npm run build$windoz
43
+)
39
 
44
 
40
-log "cd frontend_app_thread"
41
-cd frontend_app_thread
42
-
43
-log "npm run build$windoz # for frontend_app_thread"
44
-npm run build$windoz
45
+log "copying built file to frontend/"
46
+cp frontend_app_thread/dist/thread.app.js frontend/dist/app/
45
 
47
 
46
-log "cp dist/thread.app.js"
47
-cp dist/thread.app.js ../frontend/dist/app
48
+log "copying Thread en translation.json"
49
+cp frontend_app_thread/i18next.scanner/en/translation.json frontend/dist/app/thread_en_translation.json
48
 
50
 
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
+log "copying Thread fr translation.json"
52
+cp frontend_app_thread/i18next.scanner/fr/translation.json frontend/dist/app/thread_fr_translation.json
51
 
53
 
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 -
55
 
54
 
56
 # app Workspace
55
 # app Workspace
56
+log "build frontend_app_workspace"
57
+(
58
+  cd frontend_app_workspace || exit
59
+  npm run build$windoz
60
+)
57
 
61
 
58
-log "cd frontend_app_workspace"
59
-cd frontend_app_workspace
60
-
61
-log "npm run build$windoz # for frontend_app_workspace"
62
-npm run build$windoz
62
+log "copying built file to frontend/"
63
+cp frontend_app_workspace/dist/workspace.app.js frontend/dist/app/
63
 
64
 
64
-log "cp dist/workspace.app.js"
65
-cp dist/workspace.app.js ../frontend/dist/app
65
+log "copying Thread en translation.json"
66
+cp frontend_app_workspace/i18next.scanner/en/translation.json frontend/dist/app/workspace_en_translation.json
66
 
67
 
67
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json"
68
-cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json
69
-
70
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json"
71
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json
72
-cd -
68
+log "copying Thread fr translation.json"
69
+cp frontend_app_workspace/i18next.scanner/fr/translation.json frontend/dist/app/workspace_fr_translation.json
73
 
70
 
74
 # app Admin Workspace User
71
 # app Admin Workspace User
72
+log "build frontend_app_admin_workspace_user"
73
+(
74
+  cd frontend_app_admin_workspace_user || exit
75
+  npm run build$windoz
76
+)
75
 
77
 
76
-log "cd frontend_app_admin_workspace_user"
77
-cd frontend_app_admin_workspace_user
78
-
79
-log "npm run build$windoz # for frontend_app_thread"
80
-npm run build$windoz
78
+log "copying built file to frontend/"
79
+cp frontend_app_admin_workspace_user/dist/admin_workspace_user.app.js frontend/dist/app/
81
 
80
 
82
-log "cp dist/admin_workspace_user.app.js"
83
-cp dist/admin_workspace_user.app.js ../frontend/dist/app
81
+log "copying Thread en translation.json"
82
+cp frontend_app_admin_workspace_user/i18next.scanner/en/translation.json frontend/dist/app/admin_workspace_user_en_translation.json
84
 
83
 
85
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json"
86
-cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
84
+log "copying Thread fr translation.json"
85
+cp frontend_app_admin_workspace_user/i18next.scanner/fr/translation.json frontend/dist/app/admin_workspace_user_fr_translation.json
87
 
86
 
88
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json"
89
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json
90
-cd -
87
+log "frontend fully built"

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

76
   GLOBAL_eventReducer = ({detail: {type, data}}) => {
76
   GLOBAL_eventReducer = ({detail: {type, data}}) => {
77
     switch (type) {
77
     switch (type) {
78
       case 'hide_popupCreateContent':
78
       case 'hide_popupCreateContent':
79
+      case 'hide_popupCreateWorkspace':
79
         console.log('%cGLOBAL_eventReducer Custom Event', 'color: #28a745', type, data)
80
         console.log('%cGLOBAL_eventReducer Custom Event', 'color: #28a745', type, data)
80
         getSelectedApp(data.name).unmountApp('popupCreateContentContainer')
81
         getSelectedApp(data.name).unmountApp('popupCreateContentContainer')
81
         break
82
         break

+ 0 - 1
frontend/dist/index.html View File

36
       .primaryColorBorder { border-color: #7d4e24; }
36
       .primaryColorBorder { border-color: #7d4e24; }
37
       .primaryColorBorderDarken { border-color: #572800; }
37
       .primaryColorBorderDarken { border-color: #572800; }
38
       .primaryColorBorderLighten { border-color: #a3744a; }
38
       .primaryColorBorderLighten { border-color: #a3744a; }
39
-      .whiteColorBorder { border-color: #fdfdfd; }
40
 
39
 
41
       .primaryColorBorderHover:hover { border-color: #7d4e24; }
40
       .primaryColorBorderHover:hover { border-color: #7d4e24; }
42
       .primaryColorBorderDarkenHover:hover { border-color: #572800; }
41
       .primaryColorBorderDarkenHover:hover { border-color: #572800; }

+ 1 - 0
frontend/dist/tinymceInit.js View File

32
     });
32
     });
33
 
33
 
34
     tinymce.init({
34
     tinymce.init({
35
+      forced_root_block : "",
35
       selector: selector,
36
       selector: selector,
36
       menubar: false,
37
       menubar: false,
37
       resize: false,
38
       resize: false,

+ 6 - 1
frontend/i18next.scanner/en/translation.json View File

64
   "Old password": "Old password",
64
   "Old password": "Old password",
65
   "New password": "New password",
65
   "New password": "New password",
66
   "Name:": "Name:",
66
   "Name:": "Name:",
67
-  "Email Adress:": "Email Adress:"
67
+  "Email Adress:": "Email Adress:",
68
+  "An error has happened": "An error has happened",
69
+  "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
70
+  "Connection": "Connection",
71
+  "Forgotten password ?": "Forgotten password ?",
72
+  "currently, you are ": "currently, you are "
68
 }
73
 }

+ 6 - 1
frontend/i18next.scanner/fr/translation.json View File

64
   "Old password": "Ancien mot de passe",
64
   "Old password": "Ancien mot de passe",
65
   "New password": "Nouveau mot de passe",
65
   "New password": "Nouveau mot de passe",
66
   "Name:": "Nom :",
66
   "Name:": "Nom :",
67
-  "Email Adress:": "Adresse mail :"
67
+  "Email Adress:": "Adresse mail :",
68
+  "An error has happened": "__NOT_TRANSLATED__",
69
+  "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
70
+  "Connection": "Connexion",
71
+  "Forgotten password ?": "Mot de passe oublié ?",
72
+  "currently, you are ": "actuellement, vous êtes "
68
 }
73
 }

+ 34 - 16
frontend/src/action-creator.async.js View File

2
 import {
2
 import {
3
   TIMEZONE,
3
   TIMEZONE,
4
   setTimezone,
4
   setTimezone,
5
-  LANG,
6
-  updateLangList,
7
   USER_LOGIN,
5
   USER_LOGIN,
8
   USER_LOGOUT,
6
   USER_LOGOUT,
9
   USER_ROLE,
7
   USER_ROLE,
16
   FOLDER,
14
   FOLDER,
17
   setFolderData,
15
   setFolderData,
18
   APP_LIST,
16
   APP_LIST,
19
-  CONTENT_TYPE_LIST
17
+  CONTENT_TYPE_LIST,
18
+  WORKSPACE_RECENT_ACTIVITY,
19
+  WORKSPACE_READ_STATUS
20
 } from './action-creator.sync.js'
20
 } from './action-creator.sync.js'
21
 
21
 
22
 /*
22
 /*
36
  * This function create a http async request using whatwg-fetch while dispatching a PENDING and a SUCCESS redux action.
36
  * This function create a http async request using whatwg-fetch while dispatching a PENDING and a SUCCESS redux action.
37
  * It also adds, to the Response of the fetch request, the json value so that the redux action have access to the status and the data
37
  * It also adds, to the Response of the fetch request, the json value so that the redux action have access to the status and the data
38
  */
38
  */
39
+// Côme - 2018/08/02 - fetchWrapper should come from tracim_lib so that all apps uses the same
39
 const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) => {
40
 const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) => {
40
   dispatch({type: `${param.method}/${actionName}/PENDING`})
41
   dispatch({type: `${param.method}/${actionName}/PENDING`})
41
 
42
 
77
   return fetchResult
78
   return fetchResult
78
 }
79
 }
79
 
80
 
80
-export const getLangList = () => async dispatch => {
81
-  const fetchGetLangList = await fetchWrapper({
82
-    url: `${FETCH_CONFIG.apiUrl}/lang`,
83
-    param: {
84
-      headers: {...FETCH_CONFIG.headers},
85
-      method: 'GET'
86
-    },
87
-    actionName: LANG,
88
-    dispatch
89
-  })
90
-  if (fetchGetLangList.status === 200) dispatch(updateLangList(fetchGetLangList.json))
91
-}
92
-
93
 export const getTimezone = () => async dispatch => {
81
 export const getTimezone = () => async dispatch => {
94
   const fetchGetTimezone = await fetchWrapper({
82
   const fetchGetTimezone = await fetchWrapper({
95
     url: `${FETCH_CONFIG.apiUrl}/timezone`,
83
     url: `${FETCH_CONFIG.apiUrl}/timezone`,
220
   })
208
   })
221
 }
209
 }
222
 
210
 
211
+export const getWorkspaceRecentActivityList = (user, idWorkspace) => dispatch => {
212
+  return fetchWrapper({
213
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces/${idWorkspace}/contents/recently_active?limit=10`,
214
+    param: {
215
+      headers: {
216
+        ...FETCH_CONFIG.headers,
217
+        'Authorization': 'Basic ' + user.auth
218
+      },
219
+      method: 'GET'
220
+    },
221
+    actionName: WORKSPACE_RECENT_ACTIVITY,
222
+    dispatch
223
+  })
224
+}
225
+
226
+export const getWorkspaceReadStatusList = (user, idWorkspace) => dispatch => {
227
+  return fetchWrapper({
228
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces/${idWorkspace}/contents/read_status`,
229
+    param: {
230
+      headers: {
231
+        ...FETCH_CONFIG.headers,
232
+        'Authorization': 'Basic ' + user.auth
233
+      },
234
+      method: 'GET'
235
+    },
236
+    actionName: WORKSPACE_READ_STATUS,
237
+    dispatch
238
+  })
239
+}
240
+
223
 export const getFolderContent = (idWorkspace, idFolder) => async dispatch => {
241
 export const getFolderContent = (idWorkspace, idFolder) => async dispatch => {
224
   const fetchGetFolderContent = await fetchWrapper({
242
   const fetchGetFolderContent = await fetchWrapper({
225
     url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/?parent_id=${idFolder}`,
243
     url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/?parent_id=${idFolder}`,

+ 9 - 1
frontend/src/action-creator.sync.js View File

32
 
32
 
33
 export const WORKSPACE = 'Workspace'
33
 export const WORKSPACE = 'Workspace'
34
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
34
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
35
-export const setWorkspaceContentList = (workspaceContentList, filterStr = '') => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList, filterStr })
35
+export const setWorkspaceContentList = workspaceContentList => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList })
36
 export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
36
 export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
37
 
37
 
38
 export const WORKSPACE_LIST = `${WORKSPACE}/List`
38
 export const WORKSPACE_LIST = `${WORKSPACE}/List`
46
 export const WORKSPACE_MEMBER_LIST = `${WORKSPACE_MEMBER}/List`
46
 export const WORKSPACE_MEMBER_LIST = `${WORKSPACE_MEMBER}/List`
47
 export const setWorkspaceMemberList = workspaceMemberList => ({ type: `${SET}/${WORKSPACE_MEMBER_LIST}`, workspaceMemberList })
47
 export const setWorkspaceMemberList = workspaceMemberList => ({ type: `${SET}/${WORKSPACE_MEMBER_LIST}`, workspaceMemberList })
48
 
48
 
49
+export const WORKSPACE_RECENT_ACTIVITY = `${WORKSPACE}/RecentActivity/List`
50
+export const WORKSPACE_RECENT_ACTIVITY_LIST = `${WORKSPACE_RECENT_ACTIVITY}/List`
51
+export const setWorkspaceRecentActivityList = workspaceRecentActivityList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_LIST}`, workspaceRecentActivityList })
52
+
53
+export const WORKSPACE_READ_STATUS = `${WORKSPACE}/ReadStatus`
54
+export const WORKSPACE_READ_STATUS_LIST = `${WORKSPACE_READ_STATUS}/List`
55
+export const setWorkspaceReadStatusList = workspaceReadStatusList => ({ type: `${SET}/${WORKSPACE_READ_STATUS_LIST}`, workspaceReadStatusList })
56
+
49
 export const FOLDER = 'Folder'
57
 export const FOLDER = 'Folder'
50
 export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
58
 export const setFolderData = (folderId, content) => ({ type: `${SET}/${WORKSPACE}/${FOLDER}/Content`, folderId, content })
51
 
59
 

+ 1 - 1
frontend/src/component/Account/Notification.jsx View File

4
 import { ROLE } from '../../helper.js'
4
 import { ROLE } from '../../helper.js'
5
 
5
 
6
 export const Notification = props => {
6
 export const Notification = props => {
7
-  const getRole = role => ROLE.find(r => r.name === role)
7
+  const getRole = role => ROLE.find(r => r.slug === role)
8
 
8
 
9
   return (
9
   return (
10
     <div className='account__userpreference__setting__notification'>
10
     <div className='account__userpreference__setting__notification'>

+ 41 - 0
frontend/src/component/Dashboard/ContentTypeBtn.jsx View File

1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import Radium from 'radium'
4
+import color from 'color'
5
+import classnames from 'classnames'
6
+
7
+require('./ContentTypeBtn.styl')
8
+
9
+export const ContentTypeBtn = props =>
10
+  <div
11
+    className={classnames(`${props.customClass}`, 'contentTypeBtn')}
12
+    style={{
13
+      backgroundColor: props.hexcolor,
14
+      ':hover': {
15
+        backgroundColor: color(props.hexcolor).darken(0.15).hexString()
16
+      }
17
+    }}
18
+  >
19
+    <div className={classnames(`${props.customClass}__text`)}>
20
+      <div className={classnames(`${props.customClass}__text__icon`)}>
21
+        <i className={`fa fa-${props.faIcon}`} />
22
+      </div>
23
+      <div className={classnames(`${props.customClass}__text__title`)}>
24
+        {props.creationLabel}
25
+      </div>
26
+    </div>
27
+  </div>
28
+
29
+export default Radium(ContentTypeBtn)
30
+
31
+ContentTypeBtn.propTypes = {
32
+  hexcolor: PropTypes.string.isRequired,
33
+  label: PropTypes.string.isRequired,
34
+  faIcon: PropTypes.string.isRequired,
35
+  creationLabel: PropTypes.string.isRequired,
36
+  customClass: PropTypes.string
37
+}
38
+
39
+ContentTypeBtn.defaultProps = {
40
+  customClass: ''
41
+}

+ 16 - 0
frontend/src/component/Dashboard/ContentTypeBtn.styl View File

1
+.contentTypeBtn
2
+  display flex
3
+  flex-direction column
4
+  justify-content center
5
+  margin 0 15px
6
+  border-radius 10px
7
+  padding 15px
8
+  width 230px
9
+  height 200px
10
+  box-shadow shadow-all
11
+  text-align center
12
+  cursor pointer
13
+  &:nth-child(1)
14
+    margin-left 0
15
+  &:nth-last-child
16
+    margin-right 0

+ 162 - 0
frontend/src/component/Dashboard/MemberList.jsx View File

1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import { Checkbox } from 'tracim_frontend_lib'
4
+
5
+require('./MemberList.styl')
6
+
7
+export class MemberList extends React.Component {
8
+  constructor (props) {
9
+    super(props)
10
+
11
+    this.state = {
12
+      displayNewMemberList: true,
13
+      createAccountCheckbox: false
14
+    }
15
+  }
16
+
17
+  handleClickAddMemberBtn = () => this.setState({displayNewMemberList: false})
18
+
19
+  handleClickCloseAddMemberBtn = () => this.setState({displayNewMemberList: true})
20
+
21
+  handleClickCheckboxCreateAccount = e => {
22
+    e.preventDefault()
23
+    e.stopPropagation()
24
+    this.setState(prev => ({createAccountCheckbox: !prev.createAccountCheckbox}))
25
+  }
26
+
27
+  render () {
28
+    const { props, state } = this
29
+
30
+    return (
31
+      <div className='memberlist'>
32
+
33
+        <div className='memberlist__title subTitle'>
34
+          {props.t('Member List')}
35
+        </div>
36
+
37
+        <div className='memberlist__wrapper'>
38
+          {state.displayNewMemberList
39
+            ? (
40
+              <div>
41
+                <ul className='memberlist__list'>
42
+                  {props.memberList.map(m =>
43
+                    <li className='memberlist__list__item primaryColorBgLightenHover' key={m.id}>
44
+                      <div className='memberlist__list__item__avatar'>
45
+                        {m.avatarUrl ? <img src={m.avatarUrl} /> : <img src='NYI' />}
46
+                      </div>
47
+
48
+                      <div className='memberlist__list__item__info mr-auto'>
49
+                        <div className='memberlist__list__item__info__name'>
50
+                          {m.publicName}
51
+                        </div>
52
+
53
+                        <div className='memberlist__list__item__info__role'>
54
+                          {props.roleList.find(r => r.slug === m.role).label}
55
+                        </div>
56
+                      </div>
57
+
58
+                      <div className='memberlist__list__item__delete'>
59
+                        <i className='fa fa-trash-o' />
60
+                      </div>
61
+                    </li>
62
+                  )}
63
+                </ul>
64
+
65
+                <div className='memberlist__btnadd' onClick={this.handleClickAddMemberBtn}>
66
+                  <div className='memberlist__btnadd__button'>
67
+                    <div className='memberlist__btnadd__button__avatar'>
68
+                      <div className='memberlist__btnadd__button__avatar__icon'>
69
+                        <i className='fa fa-plus' />
70
+                      </div>
71
+                    </div>
72
+
73
+                    <div className='memberlist__btnadd__button__text'>
74
+                      {props.t('Add a member')}
75
+                    </div>
76
+                  </div>
77
+                </div>
78
+              </div>
79
+            )
80
+            : (
81
+              <form className='memberlist__form'>
82
+                <div className='memberlist__form__close d-flex justify-content-end'>
83
+                  <i className='fa fa-times' onClick={this.handleClickCloseAddMemberBtn} />
84
+                </div>
85
+
86
+                <div className='memberlist__form__member'>
87
+                  <div className='memberlist__form__member__name'>
88
+                    <label className='name__label' htmlFor='addmember'>
89
+                      {props.t('Enter the name or email of the member')}
90
+                    </label>
91
+
92
+                    <input
93
+                      type='text'
94
+                      className='name__input form-control'
95
+                      id='addmember'
96
+                      placeholder='Nom ou Email'
97
+                      onChange={props.onChangeName}
98
+                    />
99
+                  </div>
100
+
101
+                  <div className='memberlist__form__member__create'>
102
+                    <div className='memberlist__form__member__create__checkbox mr-3'>
103
+                      <Checkbox
104
+                        name='createAccountCheckbox'
105
+                        onClickCheckbox={e => this.handleClickCheckboxCreateAccount(e)}
106
+                        checked={state.createAccountCheckbox}
107
+                      />
108
+                    </div>
109
+
110
+                    <div className='create__text'>
111
+                      {props.t('Create an account')}
112
+                    </div>
113
+                  </div>
114
+                </div>
115
+
116
+                <div className='memberlist__form__role'>
117
+                  <div className='memberlist__form__role__text'>
118
+                    {props.t('Choose the role of the member')}
119
+                  </div>
120
+
121
+                  <ul className='memberlist__form__role__list'>
122
+                    {props.roleList.map(r =>
123
+                      <li className='memberlist__form__role__list__item' key={r.slug}>
124
+                        <div className='item__radiobtn mr-3'>
125
+                          <input type='radio' name='role' value={r.slug} />
126
+                        </div>
127
+
128
+                        <div className='item__text'>
129
+                          <div className='item_text_icon mr-2' style={{color: r.hexcolor}}>
130
+                            <i className={`fa fa-${r.faIcon}`} />
131
+                          </div>
132
+
133
+                          <div className='item__text__name'>
134
+                            {r.label}
135
+                          </div>
136
+                        </div>
137
+                      </li>
138
+                    )}
139
+
140
+                  </ul>
141
+                </div>
142
+
143
+                <div className='memberlist__form__submitbtn'>
144
+                  <button className='btn btn-outline-primary'>
145
+                    {props.t('Validate')}
146
+                  </button>
147
+                </div>
148
+              </form>
149
+            )
150
+          }
151
+        </div>
152
+      </div>
153
+    )
154
+  }
155
+}
156
+
157
+export default MemberList
158
+
159
+MemberList.propTypes = {
160
+  memberList: PropTypes.array.isRequired,
161
+  onChangeName: PropTypes.func
162
+}

+ 117 - 0
frontend/src/component/Dashboard/MemberList.styl View File

1
+.memberlist
2
+  margin 0 0 50px 0
3
+  width 35%
4
+  &__title
5
+    margin-bottom 20px
6
+    padding 6px
7
+    height 45px
8
+  &__wrapper
9
+    position relative
10
+    border 1px solid grey
11
+    height 480px
12
+  &__list
13
+    margin 0
14
+    padding 0
15
+    list-style none
16
+    height 400px
17
+    overflow-Y scroll
18
+    &__item
19
+      display flex
20
+      border-bottom 1px solid grey
21
+      padding 10px 15px
22
+      &:hover
23
+        background-color fourthColor
24
+      &:nth-last-child(1)
25
+        border-bottom 0
26
+      &:nth-child(even)
27
+        background-color grey-hover
28
+        &:hover
29
+          background-color fourthColor
30
+      &__avatar
31
+        margin-right 20px
32
+        & > img
33
+          width 50px
34
+          height 50px
35
+      &__info
36
+        &__name
37
+          font-size 20px
38
+        &__role
39
+          font-size 18px
40
+      &__delete
41
+        font-size 20px
42
+        color darkGrey
43
+        cursor pointer
44
+  &__btnadd
45
+    border-top 1px solid grey
46
+    padding 15px
47
+    &__button
48
+      display flex
49
+      align-items center
50
+      &__avatar
51
+        display flex
52
+        justify-content center
53
+        align-items center
54
+        margin-right 20px
55
+        border 2px dashed grey
56
+        border-radius 50%
57
+        width 50px
58
+        height 50px
59
+        cursor pointer
60
+        &__icon
61
+          color grey
62
+          font-size 25px
63
+      &__text
64
+        font-size 18px
65
+        color fontColor
66
+        cursor pointer
67
+  &__form
68
+    padding 15px
69
+    flex-direction column
70
+    height 100%
71
+    width 100%
72
+    background-color off-white
73
+    &__close
74
+      font-size 20px
75
+      & > i
76
+        cursor pointer
77
+    &__member
78
+      &__name
79
+        .name__label
80
+          margin 30px 0 20px 0
81
+          label()
82
+      .name__input
83
+        margin-bottom 20px
84
+        border 1px solid grey
85
+        border-radius 10px
86
+        padding 10px
87
+        width 300px
88
+      &__create
89
+        display flex
90
+        align-items center
91
+        margin 15px 0
92
+        line-height 23px
93
+        &__checkbox
94
+          padding-top 6px
95
+    &__role
96
+      margin-bottom 15px
97
+      coloricon()
98
+      &__text
99
+        margin 15px 0
100
+        label()
101
+      &__list
102
+        margin 0
103
+        padding 0
104
+        list-style none
105
+        &__item
106
+          display flex
107
+          align-items center
108
+          margin 10px 25px 10px 0
109
+          .item
110
+            &__text
111
+              display flex
112
+    &__submitbtn
113
+      display flex
114
+      justify-content flex-end
115
+      & > button
116
+        padding 8px 30px
117
+        cursor pointer

+ 50 - 0
frontend/src/component/Dashboard/RecentActivity.jsx View File

1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import classnames from 'classnames'
4
+
5
+export const RecentActivity = props =>
6
+  <div className={props.customClass}>
7
+    <div className={`${props.customClass}__header`}>
8
+      <div className={classnames(`${props.customClass}__header__title`, 'subTitle')}>
9
+        {props.t('Recent activity')}
10
+      </div>
11
+
12
+      <div className={classnames(`${props.customClass}__header__allread`, 'btn btn-outline-primary')}>
13
+        {props.t('Mark everything as read')}
14
+      </div>
15
+    </div>
16
+
17
+    <div className={`${props.customClass}__wrapper`}>
18
+      {props.recentActivityFilteredForUser.map(content => {
19
+        const contentType = props.contentTypeList.find(ct => ct.slug === content.type)
20
+        return (
21
+          <div className={`${props.customClass}__workspace`} key={content.id}>
22
+            <div className={`${props.customClass}__workspace__icon`} style={{color: contentType.hexcolor}}>
23
+              <i className={`fa fa-${contentType.faIcon}`} />
24
+            </div>
25
+            <div className={`${props.customClass}__workspace__name`}>
26
+              {content.label}
27
+            </div>
28
+          </div>
29
+        )
30
+      })}
31
+
32
+      <div className={classnames(`${props.customClass}__more`, 'd-flex flex-row-reverse')}>
33
+        <div
34
+          className={classnames(`${props.customClass}__more__btn`, 'btn btn-outline-primary')}
35
+          onClick={props.onClickSeeMore}
36
+        >
37
+          {props.t('See more')}
38
+        </div>
39
+      </div>
40
+    </div>
41
+  </div>
42
+
43
+export default RecentActivity
44
+
45
+RecentActivity.propTypes = {
46
+  t: PropTypes.func.isRequired,
47
+  recentActivityFilteredForUser: PropTypes.array.isRequired,
48
+  contentTypeList: PropTypes.array.isRequired,
49
+  onClickSeeMore: PropTypes.func.isRequired
50
+}

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

22
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
22
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
23
 
23
 
24
       if (appOpenedType === contentToOpen.type) { // app already open
24
       if (appOpenedType === contentToOpen.type) { // app already open
25
-        dispatchCustomEvent({
26
-          type: `${contentToOpen.type}_reloadContent`, // handled by html-document:src/container/AdminWorkspaceUser.jsx
27
-          data: contentToOpen
28
-        })
25
+        dispatchCustomEvent(`${contentToOpen.type}_reloadContent`, contentToOpen)
29
       } else { // open another app
26
       } else { // open another app
30
         // if another app is already visible, hide it
27
         // if another app is already visible, hide it
31
-        if (appOpenedType !== false) dispatchCustomEvent({type: `${appOpenedType}_hideApp`})
28
+        if (appOpenedType !== false) dispatchCustomEvent(`${appOpenedType}_hideApp`, {})
32
         // open app
29
         // open app
33
         renderAppFeature(
30
         renderAppFeature(
34
           contentType.find(ct => ct.slug === contentToOpen.type),
31
           contentType.find(ct => ct.slug === contentToOpen.type),

+ 1 - 4
frontend/src/container/Account.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 Sidebar from './Sidebar.jsx'
4
 import UserInfo from '../component/Account/UserInfo.jsx'
3
 import UserInfo from '../component/Account/UserInfo.jsx'
5
 import MenuSubComponent from '../component/Account/MenuSubComponent.jsx'
4
 import MenuSubComponent from '../component/Account/MenuSubComponent.jsx'
6
 import PersonalData from '../component/Account/PersonalData.jsx'
5
 import PersonalData from '../component/Account/PersonalData.jsx'
102
     })()
101
     })()
103
 
102
 
104
     return (
103
     return (
105
-      <div className='sidebarpagecontainer'>
106
-        <Sidebar />
107
-
104
+      <div className='Account'>
108
         <PageWrapper customClass='account'>
105
         <PageWrapper customClass='account'>
109
           <PageTitle
106
           <PageTitle
110
             parentClass={'account'}
107
             parentClass={'account'}

+ 138 - 0
frontend/src/container/AdminWorkspacePage.jsx View File

1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import Sidebar from './Sidebar.jsx'
4
+import {
5
+  Delimiter,
6
+  PageWrapper,
7
+  PageTitle,
8
+  PageContent
9
+} from 'tracim_frontend_lib'
10
+import { translate } from 'react-i18next'
11
+
12
+class AdminWorkspacePage extends React.Component {
13
+  render () {
14
+    return (
15
+      <div className='sidebarpagecontainer'>
16
+        <Sidebar />
17
+
18
+        <PageWrapper customClass='adminWorkspacePage'>
19
+          <PageTitle
20
+            parentClass={'adminWorkspacePage'}
21
+            title={'Workspace management'}
22
+          />
23
+
24
+          <PageContent parentClass='adminWorkspacePage'>
25
+
26
+            <div className='adminWorkspacePage__description'>
27
+              This page informs all workspaces of the instances
28
+            </div>
29
+
30
+            <div className='adminWorkspacePage__createworkspace'>
31
+              <button className='adminWorkspacePage__createworkspace__btncreate btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover'>
32
+                {this.props.t('Create a workspace')}
33
+              </button>
34
+            </div>
35
+
36
+            <Delimiter customClass={'adminWorkspacePage__delimiter'} />
37
+
38
+            <div className='adminWorkspacePage__workspaceTable'>
39
+
40
+              <table class='table'>
41
+                <thead>
42
+                  <tr>
43
+                    <th scope='col'># ID</th>
44
+                    <th scope='col'>Workspace</th>
45
+                    <th scope='col'>Description</th>
46
+                    <th scope='col'>Users</th>
47
+                    <th scope='col'>Calendar</th>
48
+                  </tr>
49
+                </thead>
50
+                <tbody>
51
+                  <tr>
52
+                    <th>#1</th>
53
+                    <td>Design v_2</td>
54
+                    <td>Workspace about tracim v2 design</td>
55
+                    <td>8 users</td>
56
+                    <td className='d-flex align-items-center flex-wrap'>
57
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
58
+                        <i className='fa fa-fw fa-check-square-o' />
59
+                      </div>
60
+                      Enable
61
+                    </td>
62
+                  </tr>
63
+                  <tr>
64
+                    <th>#2</th>
65
+                    <td>New features</td>
66
+                    <td>Add a new features : Annotated files</td>
67
+                    <td>5 users</td>
68
+                    <td className='d-flex align-items-center flex-wrap'>
69
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
70
+                        <i className='fa fa-fw fa-square-o' />
71
+                      </div>
72
+                      Disable
73
+                    </td>
74
+                  </tr>
75
+                  <tr>
76
+                    <th>#3</th>
77
+                    <td>Fix Backend</td>
78
+                    <td>workspace referring to multiple issues on the backend </td>
79
+                    <td>3 users</td>
80
+                    <td className='d-flex align-items-center flex-wrap'>
81
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
82
+                        <i className='fa fa-fw fa-check-square-o' />
83
+                      </div>
84
+                      Enable
85
+                    </td>
86
+                  </tr>
87
+                  <tr>
88
+                    <th>#4</th>
89
+                    <td>Design v_2</td>
90
+                    <td>Workspace about tracim v2 design</td>
91
+                    <td>8 users</td>
92
+                    <td className='d-flex align-items-center flex-wrap'>
93
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
94
+                        <i className='fa fa-fw fa-square-o' />
95
+                      </div>
96
+                      Disable
97
+                    </td>
98
+                  </tr>
99
+                  <tr>
100
+                    <th>#5</th>
101
+                    <td>New features</td>
102
+                    <td>Add a new features : Annotated files</td>
103
+                    <td>5 users</td>
104
+                    <td className='d-flex align-items-center flex-wrap'>
105
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
106
+                        <i className='fa fa-fw fa-square-o' />
107
+                      </div>
108
+                      Disable
109
+                    </td>
110
+                  </tr>
111
+                  <tr>
112
+                    <th>#6</th>
113
+                    <td>Fix Backend</td>
114
+                    <td>workspace referring to multiple issues on the backend </td>
115
+                    <td>3 users</td>
116
+                    <td className='d-flex align-items-center flex-wrap'>
117
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
118
+                        <i className='fa fa-fw fa-check-square-o' />
119
+                      </div>
120
+                      Enable
121
+                    </td>
122
+                  </tr>
123
+                </tbody>
124
+              </table>
125
+            </div>
126
+
127
+          </PageContent>
128
+        </PageWrapper>
129
+      </div>
130
+    )
131
+  }
132
+}
133
+
134
+AdminWorkspacePage.propTypes = {
135
+  availableApp: PropTypes.array.isRequired
136
+}
137
+
138
+export default translate()(AdminWorkspacePage)

+ 1 - 4
frontend/src/container/AppFullscreenManager.jsx View File

4
 import { Route } from 'react-router-dom'
4
 import { Route } from 'react-router-dom'
5
 import { PAGE } from '../helper.js'
5
 import { PAGE } from '../helper.js'
6
 import appFactory from '../appFactory.js'
6
 import appFactory from '../appFactory.js'
7
-import Sidebar from './Sidebar.jsx'
8
 
7
 
9
 class AppFullscreenManager extends React.Component {
8
 class AppFullscreenManager extends React.Component {
10
   constructor (props) {
9
   constructor (props) {
20
     const { props } = this
19
     const { props } = this
21
 
20
 
22
     return (
21
     return (
23
-      <div className='sidebarpagecontainer'>
24
-        <Sidebar />
25
-
22
+      <div className='AppFullScreenManager'>
26
         <div id='appFullscreenContainer' />
23
         <div id='appFullscreenContainer' />
27
 
24
 
28
         {this.state.AmIMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
25
         {this.state.AmIMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM

+ 76 - 193
frontend/src/container/Dashboard.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 Sidebar from './Sidebar.jsx'
4
-import imgProfil from '../img/imgProfil.png'
5
 import { translate } from 'react-i18next'
3
 import { translate } from 'react-i18next'
6
-import Radium from 'radium'
7
-import color from 'color'
8
 import {
4
 import {
9
   PageWrapper,
5
   PageWrapper,
10
   PageTitle,
6
   PageTitle,
12
 } from 'tracim_frontend_lib'
8
 } from 'tracim_frontend_lib'
13
 import {
9
 import {
14
   getWorkspaceDetail,
10
   getWorkspaceDetail,
15
-  getWorkspaceMemberList
11
+  getWorkspaceMemberList,
12
+  getWorkspaceRecentActivityList,
13
+  getWorkspaceReadStatusList
16
 } from '../action-creator.async.js'
14
 } from '../action-creator.async.js'
17
 import {
15
 import {
18
-  addFlashMessage,
16
+  newFlashMessage,
19
   setWorkspaceDetail,
17
   setWorkspaceDetail,
20
-  setWorkspaceMemberList
18
+  setWorkspaceMemberList,
19
+  setWorkspaceRecentActivityList,
20
+  setWorkspaceReadStatusList
21
 } from '../action-creator.sync.js'
21
 } from '../action-creator.sync.js'
22
+import { ROLE } from '../helper.js'
23
+import ContentTypeBtn from '../component/Dashboard/ContentTypeBtn.jsx'
24
+import RecentActivity from '../component/Dashboard/RecentActivity.jsx'
25
+import MemberList from '../component/Dashboard/MemberList.jsx'
22
 
26
 
23
 class Dashboard extends React.Component {
27
 class Dashboard extends React.Component {
24
   constructor (props) {
28
   constructor (props) {
38
     const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
42
     const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
39
     switch (fetchWorkspaceDetail.status) {
43
     switch (fetchWorkspaceDetail.status) {
40
       case 200:
44
       case 200:
41
-        props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json))
42
-        break
43
-      case 400:
44
-      case 500:
45
-        props.dispatch(addFlashMessage(props.t('An error has happened'), 'warning'))
46
-        break
45
+        props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
46
+      default:
47
+        props.dispatch(newFlashMessage(props.t('An error has happened when fetching workspace detail'), 'warning')); break
47
     }
48
     }
48
 
49
 
49
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
50
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
50
     switch (fetchWorkspaceMemberList.status) {
51
     switch (fetchWorkspaceMemberList.status) {
51
       case 200:
52
       case 200:
52
-        props.dispatch(setWorkspaceMemberList(fetchWorkspaceMemberList.json))
53
-        break
54
-      case 400:
55
-      case 500:
56
-        props.dispatch(addFlashMessage(props.t('An error has happened'), 'warning'))
57
-        break
53
+        props.dispatch(setWorkspaceMemberList(fetchWorkspaceMemberList.json)); break
54
+      default:
55
+        props.dispatch(newFlashMessage(props.t('An error has happened while fetching member list'), 'warning')); break
56
+    }
57
+
58
+    const fetchWorkspaceRecentActivityList = await props.dispatch(getWorkspaceRecentActivityList(props.user, state.workspaceIdInUrl))
59
+    switch (fetchWorkspaceRecentActivityList.status) {
60
+      case 200:
61
+        props.dispatch(setWorkspaceRecentActivityList(fetchWorkspaceRecentActivityList.json)); break
62
+      default:
63
+        props.dispatch(newFlashMessage(props.t('An error has happened while fetching recent activity list'), 'warning')); break
64
+    }
65
+
66
+    const fetchWorkspaceReadStatusList = await props.dispatch(getWorkspaceReadStatusList(props.user, state.workspaceIdInUrl))
67
+    switch (fetchWorkspaceReadStatusList.status) {
68
+      case 200:
69
+        props.dispatch(setWorkspaceReadStatusList(fetchWorkspaceReadStatusList.json)); break
70
+      default:
71
+        props.dispatch(newFlashMessage(props.t('An error has happened while fetching read status list'), 'warning')); break
58
     }
72
     }
59
   }
73
   }
60
 
74
 
78
     const { props, state } = this
92
     const { props, state } = this
79
 
93
 
80
     return (
94
     return (
81
-      <div className='sidebarpagecontainer'>
82
-        <Sidebar />
83
-
95
+      <div className='Dashboard' style={{width: '100%'}}>
84
         <PageWrapper customeClass='dashboard'>
96
         <PageWrapper customeClass='dashboard'>
85
           <PageTitle
97
           <PageTitle
86
             parentClass='dashboard__header'
98
             parentClass='dashboard__header'
109
               <div className='dashboard__userstatut'>
121
               <div className='dashboard__userstatut'>
110
                 <div className='dashboard__userstatut__role'>
122
                 <div className='dashboard__userstatut__role'>
111
                   <div className='dashboard__userstatut__role__msg'>
123
                   <div className='dashboard__userstatut__role__msg'>
112
-                    {props.t(`Hi ! ${props.user.public_name}, vous êtes actuellement`)}
124
+                    {props.t(`Hi ! ${props.user.public_name} `)}{props.t('currently, you are ')}
113
                   </div>
125
                   </div>
114
 
126
 
115
-                  <div className='dashboard__userstatut__role__definition'>
116
-                    <div className='dashboard__userstatut__role__definition__icon'>
117
-                      <i className='fa fa-graduation-cap' />
118
-                    </div>
127
+                  {(() => {
128
+                    const myself = props.curWs.memberList.find(m => m.id === props.user.user_id)
129
+                    if (myself === undefined) return
119
 
130
 
120
-                    <div className='dashboard__userstatut__role__definition__text'>
121
-                      {(member => member ? member.role : '')(props.curWs.member.find(m => m.id === props.user.user_id))}
122
-                    </div>
123
-                  </div>
131
+                    const myRole = ROLE.find(r => r.slug === myself.role)
132
+
133
+                    return (
134
+                      <div className='dashboard__userstatut__role__definition'>
135
+                        <div className='dashboard__userstatut__role__definition__icon'>
136
+                          <i className={`fa fa-${myRole.faIcon}`} />
137
+                        </div>
138
+
139
+                        <div className='dashboard__userstatut__role__definition__text'>
140
+                          {myRole.label}
141
+                        </div>
142
+                      </div>
143
+                    )
144
+                  })()}
124
                 </div>
145
                 </div>
125
 
146
 
126
                 <div className='dashboard__userstatut__notification'>
147
                 <div className='dashboard__userstatut__notification'>
139
                           aria-haspopup='true'
160
                           aria-haspopup='true'
140
                           aria-expanded='false'
161
                           aria-expanded='false'
141
                         >
162
                         >
142
-                          Abonné(e)
163
+                          {props.t('subscriber')}
143
                         </button>
164
                         </button>
144
 
165
 
145
                         <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
166
                         <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
167
 
188
 
168
             <div className='dashboard__calltoaction justify-content-xl-center'>
189
             <div className='dashboard__calltoaction justify-content-xl-center'>
169
               {props.contentType.map(ct =>
190
               {props.contentType.map(ct =>
170
-                <div
171
-                  className='dashboard__calltoaction__button btnaction'
172
-                  style={{
173
-                    backgroundColor: ct.hexcolor,
174
-                    ':hover': {
175
-                      backgroundColor: color(ct.hexcolor).darken(0.15).hexString()
176
-                    }
177
-                  }}
191
+                <ContentTypeBtn
192
+                  customClass='dashboard__calltoaction__button'
193
+                  hexcolor={ct.hexcolor}
194
+                  label={ct.label}
195
+                  faIcon={ct.faIcon}
196
+                  creationLabel={ct.creationLabel}
178
                   key={ct.label}
197
                   key={ct.label}
179
-                >
180
-                  <div className='dashboard__calltoaction__button__text'>
181
-                    <div className='dashboard__calltoaction__button__text__icon'>
182
-                      <i className={`fa fa-${ct.faIcon}`} />
183
-                    </div>
184
-                    <div className='dashboard__calltoaction__button__text__title'>
185
-                      {ct.creationLabel}
186
-                    </div>
187
-                  </div>
188
-                </div>
198
+                />
189
               )}
199
               )}
190
             </div>
200
             </div>
191
 
201
 
192
-            <div className='dashboard__wksinfo'>
193
-              <div className='dashboard__activity'>
194
-                <div className='dashboard__activity__header'>
195
-                  <div className='dashboard__activity__header__title subTitle'>
196
-                    {this.props.t('Recent activity')}
197
-                  </div>
198
-
199
-                  <div className='dashboard__activity__header__allread btn btn-outline-primary'>
200
-                    {this.props.t('Mark everything as read')}
201
-                  </div>
202
-                </div>
203
-                <div className='dashboard__activity__wrapper'>
204
-
205
-                  <div className='dashboard__activity__workspace'>
206
-                    <div className='dashboard__activity__workspace__icon'>
207
-                      <i className='fa fa-comments-o' />
208
-                    </div>
209
-                    <div className='dashboard__activity__workspace__name'>
210
-                      <span>Développement Tracim</span>
211
-                    </div>
212
-                  </div>
213
-
214
-                  <div className='dashboard__activity__more d-flex flex-row-reverse'>
215
-                    <div className='dashboard__activity__more__btn btn btn-outline-primary'>
216
-                      {this.props.t('See more')}
217
-                    </div>
218
-                  </div>
219
-                </div>
220
-              </div>
221
-
222
-              <div className='dashboard__memberlist'>
223
-
224
-                <div className='dashboard__memberlist__title subTitle'>
225
-                  {this.props.t('Member List')}
226
-                </div>
227
-
228
-                <div className='dashboard__memberlist__wrapper'>
229
-                  {this.state.displayNewMemberDashboard === false &&
230
-                    <div>
231
-                      <ul className='dashboard__memberlist__list'>
232
-
233
-                        <li className='dashboard__memberlist__list__item'>
234
-                          <div className='dashboard__memberlist__list__item__avatar'>
235
-                            <img src={imgProfil} alt='avatar' />
236
-                          </div>
237
-                          <div className='dashboard__memberlist__list__item__info mr-auto'>
238
-                            <div className='dashboard__memberlist__list__item__info__name'>
239
-                              Jean Dupont
240
-                            </div>
241
-                            <div className='dashboard__memberlist__list__item__info__role'>
242
-                              Responsable
243
-                            </div>
244
-                          </div>
245
-                          <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
246
-                            <i className='fa fa-trash-o' />
247
-                          </div>
248
-                        </li>
249
-
250
-                      </ul>
251
-
252
-                      <div
253
-                        className='dashboard__memberlist__btnadd'
254
-                        onClick={this.handleToggleNewMemberDashboard}
255
-                      >
256
-                        <div className='dashboard__memberlist__btnadd__button'>
257
-                          <div className='dashboard__memberlist__btnadd__button__avatar'>
258
-                            <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
259
-                              <i className='fa fa-plus' />
260
-                            </div>
261
-                          </div>
262
-                          <div
263
-                            className='dashboard__memberlist__btnadd__button__text'
264
-                          >
265
-                            {this.props.t('Add a member')}
266
-                          </div>
267
-                        </div>
268
-                      </div>
269
-                    </div>
270
-                  }
271
-
272
-                  {this.state.displayNewMemberDashboard === true &&
273
-                  <form className='dashboard__memberlist__form'>
274
-                    <div
275
-                      className='dashboard__memberlist__form__close d-flex justify-content-end'
276
-                    >
277
-                      <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
278
-                    </div>
279
-
280
-                    <div className='dashboard__memberlist__form__member'>
281
-                      <div className='dashboard__memberlist__form__member__name'>
282
-                        <label className='name__label' htmlFor='addmember'>
283
-                          {this.props.t('Enter the name or email of the member')}
284
-                        </label>
285
-                        <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
286
-                      </div>
287
-
288
-                      <div className='dashboard__memberlist__form__member__create'>
289
-                        <div className='create__radiobtn mr-3'>
290
-                          <input type='radio' />
291
-                        </div>
292
-
293
-                        <div className='create__text'>
294
-                          {this.props.t('Create an account')}
295
-                        </div>
296
-                      </div>
297
-                    </div>
298
-
299
-                    <div className='dashboard__memberlist__form__role'>
300
-                      <div className='dashboard__memberlist__form__role__text'>
301
-                        {this.props.t('Choose the role of the member')}
302
-                      </div>
303
-
304
-                      <ul className='dashboard__memberlist__form__role__list'>
305
-
306
-                        <li className='dashboard__memberlist__form__role__list__item'>
307
-                          <div className='item__radiobtn mr-3'>
308
-                            <input type='radio' name='role' value='responsable' />
309
-                          </div>
310
-
311
-                          <div className='item__text'>
312
-                            <div className='item_text_icon mr-2'>
313
-                              <i className='fa fa-gavel' />
314
-                            </div>
315
-
316
-                            <div className='item__text__name'>
317
-                              {this.props.t('Supervisor')}
318
-                            </div>
319
-                          </div>
320
-                        </li>
321
-
322
-                      </ul>
323
-                    </div>
324
-
325
-                    <div className='dashboard__memberlist__form__submitbtn'>
326
-                      <button className='btn btn-outline-primary'>
327
-                        {this.props.t('Validate')}
328
-                      </button>
329
-                    </div>
330
-                  </form>
331
-                  }
332
-                </div>
333
-              </div>
202
+            <div className='dashboard__workspaceInfo'>
203
+              <RecentActivity
204
+                customClass='dashboard__activity'
205
+                recentActivityFilteredForUser={props.curWs.recentActivityList.filter(content => !props.curWs.contentReadStatusList.includes(content.id))}
206
+                contentTypeList={props.contentType}
207
+                onClickSeeMore={() => {}}
208
+                t={props.t}
209
+              />
210
+
211
+              <MemberList
212
+                customClass='dashboard__memberlist'
213
+                memberList={props.curWs.memberList}
214
+                roleList={ROLE}
215
+                t={props.t}
216
+              />
334
             </div>
217
             </div>
335
 
218
 
336
             <div className='dashboard__moreinfo'>
219
             <div className='dashboard__moreinfo'>
401
 }
284
 }
402
 
285
 
403
 const mapStateToProps = ({ user, contentType, currentWorkspace }) => ({ user, contentType, curWs: currentWorkspace })
286
 const mapStateToProps = ({ user, contentType, currentWorkspace }) => ({ user, contentType, curWs: currentWorkspace })
404
-export default connect(mapStateToProps)(translate()(Radium(Dashboard)))
287
+export default connect(mapStateToProps)(translate()(Dashboard))

+ 0 - 571
frontend/src/container/Dashboard_old.jsx View File

1
-import React from 'react'
2
-import { connect } from 'react-redux'
3
-import Sidebar from './Sidebar.jsx'
4
-import imgProfil from '../img/imgProfil.png'
5
-import { translate } from 'react-i18next'
6
-
7
-class Dashboard extends React.Component {
8
-  constructor (props) {
9
-    super(props)
10
-    this.state = {
11
-      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt everytime
12
-      displayNewMemberDashboard: false,
13
-      displayNotifBtn: false,
14
-      displayWebdavBtn: false,
15
-      displayCalendarBtn: false
16
-    }
17
-  }
18
-
19
-  handleToggleNewMemberDashboard = () => this.setState(prevState => ({
20
-    displayNewMemberDashboard: !prevState.displayNewMemberDashboard
21
-  }))
22
-
23
-  handleToggleNotifBtn = () => this.setState(prevState => ({
24
-    displayNotifBtn: !prevState.displayNotifBtn
25
-  }))
26
-
27
-  handleToggleWebdavBtn = () => this.setState(prevState => ({
28
-    displayWebdavBtn: !prevState.displayWebdavBtn
29
-  }))
30
-
31
-  handleToggleCalendarBtn = () => this.setState(prevState => ({
32
-    displayCalendarBtn: !prevState.displayCalendarBtn
33
-  }))
34
-
35
-  render () {
36
-    return (
37
-      <div className='sidebarpagecontainer'>
38
-        <Sidebar />
39
-
40
-        <div className='dashboard'>
41
-          <div className='container-fluid nopadding'>
42
-            <div className='dashboard__header mb-5'>
43
-              <div className='pageTitleGeneric dashboard__header__title d-flex align-items-center'>
44
-                <div className='pageTitleGeneric__title dashboard__header__title__text mr-3'>
45
-                  {this.props.t('Dashboard')}
46
-                </div>
47
-                <div className='dashboard__header__acces' />
48
-              </div>
49
-
50
-              <div className='dashboard__header__advancedmode mr-3'>
51
-                <button type='button' className='btn btn-primary'>
52
-                  {this.props.t('Active advanced Dashboard')}
53
-                </button>
54
-              </div>
55
-            </div>
56
-
57
-            <div className='dashboard__workspace-wrapper'>
58
-              <div className='dashboard__workspace'>
59
-                <div className='dashboard__workspace__title'>
60
-                  Développement tracim
61
-                </div>
62
-
63
-                <div className='dashboard__workspace__detail'>
64
-                  Ligne directive pour le prochain design de Tracim et des futurs fonctionnalités.
65
-                </div>
66
-              </div>
67
-
68
-              <div className='dashboard__userstatut'>
69
-
70
-                <div className='dashboard__userstatut__role'>
71
-                  <div className='dashboard__userstatut__role__text'>
72
-                    Hi ! Alexi, vous êtes actuellement
73
-                  </div>
74
-                  <div className='dashboard__userstatut__role__rank'>
75
-                    <div className='dashboard__userstatut__role__rank__icon'>
76
-                      <i className='fa fa-graduation-cap' />
77
-                    </div>
78
-                    <div className='dashboard__userstatut__role__rank__rolename'>
79
-                      Gestionnaire de projet
80
-                    </div>
81
-                  </div>
82
-                </div>
83
-
84
-                <div className='dashboard__userstatut__notification'>
85
-                  <div className='dashboard__userstatut__notification__text'>
86
-                    Vous êtes abonné(e) aux notifications de ce workspace
87
-                  </div>
88
-                  {this.state.displayNotifBtn === false &&
89
-                  <div
90
-                    className='dashboard__userstatut__notification__btn btn btn-outline-primary'
91
-                    onClick={this.handleToggleNotifBtn}
92
-                  >
93
-                    {this.props.t('Change your status')}
94
-                  </div>
95
-                  }
96
-
97
-                  {this.state.displayNotifBtn === true &&
98
-                  <div className='dashboard__userstatut__notification__subscribe dropdown'>
99
-                    <button className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
100
-                      Abonné(e)
101
-                    </button>
102
-                    <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
103
-                      <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
104
-                        {this.props.t('subscriber')}
105
-                      </div>
106
-                      <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
107
-                        {this.props.t('unsubscribed')}
108
-                      </div>
109
-                    </div>
110
-                  </div>
111
-                  }
112
-                </div>
113
-              </div>
114
-            </div>
115
-
116
-            <div className='dashboard__calltoaction justify-content-xl-center'>
117
-              <div className='dashboard__calltoaction__button btnaction btnthread'>
118
-                <div className='dashboard__calltoaction__button__text'>
119
-                  <div className='dashboard__calltoaction__button__text__icon'>
120
-                    <i className='fa fa-comments-o' />
121
-                  </div>
122
-                  <div className='dashboard__calltoaction__button__text__title'>
123
-                    {this.props.t('Start a new Thread')}
124
-                  </div>
125
-                </div>
126
-              </div>
127
-
128
-              <div className='dashboard__calltoaction__button btnaction writefile'>
129
-                <div className='dashboard__calltoaction__button__text'>
130
-                  <div className='dashboard__calltoaction__button__text__icon'>
131
-                    <i className='fa fa-file-text-o' />
132
-                  </div>
133
-                  <div className='dashboard__calltoaction__button__text__title'>
134
-                    {this.props.t('Writing a document')}
135
-                  </div>
136
-                </div>
137
-              </div>
138
-
139
-              <div className='dashboard__calltoaction__button btnaction importfile'>
140
-                <div className='dashboard__calltoaction__button__text'>
141
-                  <div className='dashboard__calltoaction__button__text__icon'>
142
-                    <i className='fa fa-paperclip' />
143
-                  </div>
144
-                  <div className='dashboard__calltoaction__button__text__title'>
145
-                    {this.props.t('Upload a file')}
146
-                  </div>
147
-                </div>
148
-              </div>
149
-
150
-              {/*
151
-                <div className='dashboard__calltoaction__button btnaction visioconf'>
152
-                  <div className='dashboard__calltoaction__button__text'>
153
-                    <div className='dashboard__calltoaction__button__text__icon'>
154
-                      <i className='fa fa-video-camera' />
155
-                    </div>
156
-                    <div className='dashboard__calltoaction__button__text__title'>
157
-                      {this.props.t('Start a videoconference')}
158
-                    </div>
159
-                  </div>
160
-                </div>
161
-
162
-                <div className='dashboard__calltoaction__button btnaction calendar'>
163
-                  <div className='dashboard__calltoaction__button__text'>
164
-                    <div className='dashboard__calltoaction__button__text__icon'>
165
-                      <i className='fa fa-calendar' />
166
-                    </div>
167
-                    <div className='dashboard__calltoaction__button__text__title'>
168
-                      {this.props.t('View the Calendar')}
169
-                    </div>
170
-                  </div>
171
-                </div>
172
-              */ }
173
-
174
-              <div className='dashboard__calltoaction__button btnaction explore'>
175
-                <div className='dashboard__calltoaction__button__text'>
176
-                  <div className='dashboard__calltoaction__button__text__icon'>
177
-                    <i className='fa fa-folder-open-o' />
178
-                  </div>
179
-                  <div className='dashboard__calltoaction__button__text__title'>
180
-                    {this.props.t('Explore the workspace')}
181
-                  </div>
182
-                </div>
183
-              </div>
184
-            </div>
185
-
186
-            <div className='dashboard__wksinfo'>
187
-              <div className='dashboard__activity'>
188
-                <div className='dashboard__activity__header'>
189
-                  <div className='dashboard__activity__header__title subTitle'>
190
-                    {this.props.t('Recent activity')}
191
-                  </div>
192
-
193
-                  <div className='dashboard__activity__header__allread btn btn-outline-primary'>
194
-                    {this.props.t('Mark everything as read')}
195
-                  </div>
196
-                </div>
197
-                <div className='dashboard__activity__wrapper'>
198
-                  <div className='dashboard__activity__workspace'>
199
-                    <div className='dashboard__activity__workspace__icon'>
200
-                      <i className='fa fa-comments-o' />
201
-                    </div>
202
-                    <div className='dashboard__activity__workspace__name'>
203
-                      <span>Développement Tracim</span>
204
-                    </div>
205
-                  </div>
206
-
207
-                  <div className='dashboard__activity__workspace'>
208
-                    <div className='dashboard__activity__workspace__icon'>
209
-                      <i className='fa fa-list-ul' />
210
-                    </div>
211
-                    <div className='dashboard__activity__workspace__name'>
212
-                      Mission externe
213
-                    </div>
214
-                  </div>
215
-
216
-                  <div className='dashboard__activity__workspace'>
217
-                    <div className='dashboard__activity__workspace__icon'>
218
-                      <i className='fa fa-list-ul' />
219
-                    </div>
220
-                    <div className='dashboard__activity__workspace__name'>
221
-                      Recherche et developpement
222
-                    </div>
223
-                  </div>
224
-
225
-                  <div className='dashboard__activity__workspace'>
226
-                    <div className='dashboard__activity__workspace__icon'>
227
-                      <i className='fa fa-file-text-o' />
228
-                    </div>
229
-                    <div className='dashboard__activity__workspace__name'>
230
-                      <span>Marketing</span>
231
-                    </div>
232
-                  </div>
233
-
234
-                  <div className='dashboard__activity__workspace'>
235
-                    <div className='dashboard__activity__workspace__icon'>
236
-                      <i className='fa fa-comments-o' />
237
-                    </div>
238
-                    <div className='dashboard__activity__workspace__name'>
239
-                      <span>Évolution</span>
240
-                    </div>
241
-                  </div>
242
-
243
-                  <div className='dashboard__activity__workspace'>
244
-                    <div className='dashboard__activity__workspace__icon'>
245
-                      <i className='fa fa-file-text-o' />
246
-                    </div>
247
-                    <div className='dashboard__activity__workspace__name'>
248
-                      Commercialisation
249
-                    </div>
250
-                  </div>
251
-
252
-                  <div className='dashboard__activity__more d-flex flex-row-reverse'>
253
-                    <div className='dashboard__activity__more__btn btn btn-outline-primary'>
254
-                      {this.props.t('See more')}
255
-                    </div>
256
-                  </div>
257
-                </div>
258
-              </div>
259
-
260
-              <div className='dashboard__memberlist'>
261
-
262
-                <div className='dashboard__memberlist__title subTitle'>
263
-                  {this.props.t('Member List')}
264
-                </div>
265
-
266
-                <div className='dashboard__memberlist__wrapper'>
267
-                  {this.state.displayNewMemberDashboard === false &&
268
-                  <div>
269
-                    <ul className='dashboard__memberlist__list'>
270
-                      <li className='dashboard__memberlist__list__item'>
271
-                        <div className='dashboard__memberlist__list__item__avatar'>
272
-                          <img src={imgProfil} alt='avatar' />
273
-                        </div>
274
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
275
-                          <div className='dashboard__memberlist__list__item__info__name'>
276
-                            Jean Dupont
277
-                          </div>
278
-                          <div className='dashboard__memberlist__list__item__info__role'>
279
-                            Responsable
280
-                          </div>
281
-                        </div>
282
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
283
-                          <i className='fa fa-trash-o' />
284
-                        </div>
285
-                      </li>
286
-
287
-                      <li className='dashboard__memberlist__list__item'>
288
-                        <div className='dashboard__memberlist__list__item__avatar'>
289
-                          <img src={imgProfil} alt='avatar' />
290
-                        </div>
291
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
292
-                          <div className='dashboard__memberlist__list__item__info__name'>
293
-                            Aldwin Vinel
294
-                          </div>
295
-                          <div className='dashboard__memberlist__list__item__info__role'>
296
-                            Lecteur
297
-                          </div>
298
-                        </div>
299
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
300
-                          <i className='fa fa-trash-o' />
301
-                        </div>
302
-                      </li>
303
-
304
-                      <li className='dashboard__memberlist__list__item'>
305
-                        <div className='dashboard__memberlist__list__item__avatar'>
306
-                          <img src={imgProfil} alt='avatar' />
307
-                        </div>
308
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
309
-                          <div className='dashboard__memberlist__list__item__info__name'>
310
-                            William Himme
311
-                          </div>
312
-                          <div className='dashboard__memberlist__list__item__info__role'>
313
-                            Contributeur
314
-                          </div>
315
-                        </div>
316
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
317
-                          <i className='fa fa-trash-o' />
318
-                        </div>
319
-                      </li>
320
-
321
-                      <li className='dashboard__memberlist__list__item'>
322
-                        <div className='dashboard__memberlist__list__item__avatar'>
323
-                          <img src={imgProfil} alt='avatar' />
324
-                        </div>
325
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
326
-                          <div className='dashboard__memberlist__list__item__info__name'>
327
-                            Yacine Lite
328
-                          </div>
329
-                          <div className='dashboard__memberlist__list__item__info__role'>
330
-                            Contributeur
331
-                          </div>
332
-                        </div>
333
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
334
-                          <i className='fa fa-trash-o' />
335
-                        </div>
336
-                      </li>
337
-
338
-                      <li className='dashboard__memberlist__list__item'>
339
-                        <div className='dashboard__memberlist__list__item__avatar'>
340
-                          <img src={imgProfil} alt='avatar' />
341
-                        </div>
342
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
343
-                          <div className='dashboard__memberlist__list__item__info__name'>
344
-                            Alexi Falcin
345
-                          </div>
346
-                          <div className='dashboard__memberlist__list__item__info__role'>
347
-                            Gestionnaire
348
-                          </div>
349
-                        </div>
350
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
351
-                          <i className='fa fa-trash-o' />
352
-                        </div>
353
-                      </li>
354
-
355
-                      <li className='dashboard__memberlist__list__item'>
356
-                        <div className='dashboard__memberlist__list__item__avatar'>
357
-                          <img src={imgProfil} alt='avatar' />
358
-                        </div>
359
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
360
-                          <div className='dashboard__memberlist__list__item__info__name'>
361
-                            Mickaël Fonati
362
-                          </div>
363
-                          <div className='dashboard__memberlist__list__item__info__role'>
364
-                            Gestionnaire
365
-                          </div>
366
-                        </div>
367
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
368
-                          <i className='fa fa-trash-o' />
369
-                        </div>
370
-                      </li>
371
-
372
-                      <li className='dashboard__memberlist__list__item'>
373
-                        <div className='dashboard__memberlist__list__item__avatar'>
374
-                          <img src={imgProfil} alt='avatar' />
375
-                        </div>
376
-                        <div className='dashboard__memberlist__list__item__info mr-auto'>
377
-                          <div className='dashboard__memberlist__list__item__info__name'>
378
-                            Eva Lonbard
379
-                          </div>
380
-                          <div className='dashboard__memberlist__list__item__info__role'>
381
-                            Gestionnaire
382
-                          </div>
383
-                        </div>
384
-                        <div className='dashboard__memberlist__list__item__delete d-flex justify-content-end'>
385
-                          <i className='fa fa-trash-o' />
386
-                        </div>
387
-                      </li>
388
-                    </ul>
389
-
390
-                    <div
391
-                      className='dashboard__memberlist__btnadd'
392
-                      onClick={this.handleToggleNewMemberDashboard}
393
-                    >
394
-                      <div className='dashboard__memberlist__btnadd__button'>
395
-                        <div className='dashboard__memberlist__btnadd__button__avatar'>
396
-                          <div className='dashboard__memberlist__btnadd__button__avatar__icon'>
397
-                            <i className='fa fa-plus' />
398
-                          </div>
399
-                        </div>
400
-                        <div
401
-                          className='dashboard__memberlist__btnadd__button__text'
402
-                        >
403
-                          {this.props.t('Add a member')}
404
-                        </div>
405
-                      </div>
406
-                    </div>
407
-                  </div>
408
-                  }
409
-
410
-                  {this.state.displayNewMemberDashboard === true &&
411
-                  <form className='dashboard__memberlist__form'>
412
-                    <div
413
-                      className='dashboard__memberlist__form__close d-flex justify-content-end'
414
-                    >
415
-                      <i className='fa fa-times' onClick={this.handleToggleNewMemberDashboard} />
416
-                    </div>
417
-                    <div className='dashboard__memberlist__form__member'>
418
-                      <div className='dashboard__memberlist__form__member__name'>
419
-                        <label className='name__label' htmlFor='addmember'>
420
-                          {this.props.t('Enter the name or email of the member')}
421
-                        </label>
422
-                        <input type='text' id='addmember' className='name__input form-control' placeholder='Nom ou Email' />
423
-                      </div>
424
-                      <div className='dashboard__memberlist__form__member__create'>
425
-                        <div className='create__radiobtn mr-3'>
426
-                          <input type='radio' />
427
-                        </div>
428
-                        <div className='create__text'>
429
-                          {this.props.t('Create an account')}
430
-                        </div>
431
-                      </div>
432
-                    </div>
433
-                    <div className='dashboard__memberlist__form__role'>
434
-                      <div className='dashboard__memberlist__form__role__text'>
435
-                        {this.props.t('Choose the role of the member')}
436
-                      </div>
437
-                      <ul className='dashboard__memberlist__form__role__list'>
438
-                        <li className='dashboard__memberlist__form__role__list__item'>
439
-                          <div className='item__radiobtn mr-3'>
440
-                            <input type='radio' name='role' value='responsable' />
441
-                          </div>
442
-                          <div className='item__text'>
443
-                            <div className='item_text_icon mr-2'>
444
-                              <i className='fa fa-gavel' />
445
-                            </div>
446
-                            <div className='item__text__name'>
447
-                              {this.props.t('Supervisor')}
448
-                            </div>
449
-                          </div>
450
-                        </li>
451
-                        <li className='dashboard__memberlist__form__role__list__item'>
452
-                          <div className='item__radiobtn mr-3'>
453
-                            <input type='radio' name='role' value='gestionnaire' />
454
-                          </div>
455
-                          <div className='item__text'>
456
-                            <div className='item_text_icon mr-2'>
457
-                              <i className='fa fa-graduation-cap' />
458
-                            </div>
459
-                            <div className='item__text__name'>
460
-                              {this.props.t('Content Manager')}
461
-                            </div>
462
-                          </div>
463
-                        </li>
464
-                        <li className='dashboard__memberlist__form__role__list__item'>
465
-                          <div className='item__radiobtn mr-3'>
466
-                            <input type='radio' name='role' value='contributeur' />
467
-                          </div>
468
-                          <div className='item__text'>
469
-                            <div className='item_text_icon mr-2'>
470
-                              <i className='fa fa-pencil' />
471
-                            </div>
472
-                            <div className='item__text__name'>
473
-                              {this.props.t('Contributor')}
474
-                            </div>
475
-                          </div>
476
-                        </li>
477
-                        <li className='dashboard__memberlist__form__role__list__item'>
478
-                          <div className='item__radiobtn mr-3'>
479
-                            <input type='radio' name='role' value='lecteur' />
480
-                          </div>
481
-                          <div className='item__text'>
482
-                            <div className='item_text_icon mr-2'>
483
-                              <i className='fa fa-eye' />
484
-                            </div>
485
-                            <div className='item__text__name'>
486
-                              {this.props.t('Reader')}
487
-                            </div>
488
-                          </div>
489
-                        </li>
490
-                      </ul>
491
-                    </div>
492
-                    <div className='dashboard__memberlist__form__submitbtn'>
493
-                      <button className='btn btn-outline-primary'>
494
-                        {this.props.t('Validate')}
495
-                      </button>
496
-                    </div>
497
-                  </form>
498
-                  }
499
-                </div>
500
-              </div>
501
-            </div>
502
-
503
-            <div className='dashboard__moreinfo'>
504
-              <div className='dashboard__moreinfo__webdav genericBtnInfoDashboard'>
505
-                <div
506
-                  className='dashboard__moreinfo__webdav__btn genericBtnInfoDashboard__btn'
507
-                  onClick={this.handleToggleWebdavBtn}
508
-                >
509
-                  <div className='dashboard__moreinfo__webdav__btn__icon genericBtnInfoDashboard__btn__icon'>
510
-                    <i className='fa fa-windows' />
511
-                  </div>
512
-
513
-                  <div className='dashboard__moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
514
-                    {this.props.t('Implement Tracim in your explorer')}
515
-                  </div>
516
-                </div>
517
-                {this.state.displayWebdavBtn === true &&
518
-                <div>
519
-                  <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
520
-                    <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
521
-                      {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
522
-                    </div>
523
-
524
-                    <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
525
-                      http://algoo.trac.im/webdav/
526
-                    </div>
527
-                  </div>
528
-                </div>
529
-                }
530
-              </div>
531
-              <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
532
-                <div className='dashboard__moreinfo__calendar__wrapperBtn'>
533
-                  <div
534
-                    className='dashboard__moreinfo__calendar__btn genericBtnInfoDashboard__btn'
535
-                    onClick={this.handleToggleCalendarBtn}
536
-                  >
537
-                    <div className='dashboard__moreinfo__calendar__btn__icon genericBtnInfoDashboard__btn__icon'>
538
-                      <i className='fa fa-calendar' />
539
-                    </div>
540
-
541
-                    <div className='dashboard__moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text'>
542
-                      {this.props.t('Workspace Calendar')}
543
-                    </div>
544
-                  </div>
545
-                </div>
546
-                <div className='dashboard__moreinfo__calendar__wrapperText'>
547
-                  {this.state.displayCalendarBtn === true &&
548
-                  <div>
549
-                    <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
550
-                      <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
551
-                        {this.props.t('Each workspace has its own calendar.')}
552
-                      </div>
553
-
554
-                      <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
555
-                        http://algoo.trac.im/calendar/
556
-                      </div>
557
-                    </div>
558
-                  </div>
559
-                  }
560
-                </div>
561
-              </div>
562
-            </div>
563
-          </div>
564
-        </div>
565
-      </div>
566
-    )
567
-  }
568
-}
569
-
570
-const mapStateToProps = ({ user, app, contentType, workspaceList }) => ({ user, app, contentType, workspaceList })
571
-export default connect(mapStateToProps)(translate()(Dashboard))

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

55
       Cookies.set(COOKIE.USER_LOGIN, inputLogin.value)
55
       Cookies.set(COOKIE.USER_LOGIN, inputLogin.value)
56
       Cookies.set(COOKIE.USER_AUTH, userAuth)
56
       Cookies.set(COOKIE.USER_AUTH, userAuth)
57
 
57
 
58
-      history.push(PAGE.HOME)
59
-    } else if (fetchPostUserLogin.status === 400) {
58
+      history.push(PAGE.WORKSPACE.ROOT)
59
+    } else if (fetchPostUserLogin.status === 403) {
60
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
60
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
61
     }
61
     }
62
   }
62
   }
74
               <div className='col-12 col-sm-11 col-md-8 col-lg-6 col-xl-4'>
74
               <div className='col-12 col-sm-11 col-md-8 col-lg-6 col-xl-4'>
75
 
75
 
76
                 <Card customClass='loginpage__connection'>
76
                 <Card customClass='loginpage__connection'>
77
-                  <CardHeader customClass='connection__header text-center'>{'Connexion'}</CardHeader>
77
+                  <CardHeader customClass='connection__header text-center'>{this.props.t('Connection')}</CardHeader>
78
 
78
 
79
                   <CardBody formClass='connection__form'>
79
                   <CardBody formClass='connection__form'>
80
                     <div>
80
                     <div>
118
                         <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6 text-sm-right'>
118
                         <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6 text-sm-right'>
119
                           <LoginBtnForgotPw
119
                           <LoginBtnForgotPw
120
                             customClass='connection__form__pwforgot'
120
                             customClass='connection__form__pwforgot'
121
-                            label='Mot de passe oublié ?'
121
+                            label={this.props.t('Forgotten password ?')}
122
                           />
122
                           />
123
                         </div>
123
                         </div>
124
                       </div>
124
                       </div>
127
                         htmlType='button'
127
                         htmlType='button'
128
                         bootstrapType='primary'
128
                         bootstrapType='primary'
129
                         customClass='connection__form__btnsubmit ml-auto'
129
                         customClass='connection__form__btnsubmit ml-auto'
130
-                        label='Connexion'
130
+                        label={this.props.t('Connection')}
131
                         onClick={this.handleClickSubmit}
131
                         onClick={this.handleClickSubmit}
132
                       />
132
                       />
133
                     </div>
133
                     </div>

+ 18 - 6
frontend/src/container/Sidebar.jsx View File

6
 import appFactory from '../appFactory.js'
6
 import appFactory from '../appFactory.js'
7
 import WorkspaceListItem from '../component/Sidebar/WorkspaceListItem.jsx'
7
 import WorkspaceListItem from '../component/Sidebar/WorkspaceListItem.jsx'
8
 import {
8
 import {
9
+  setAppList,
10
+  setContentTypeList,
9
   setWorkspaceListIsOpenInSidebar,
11
   setWorkspaceListIsOpenInSidebar,
10
   updateWorkspaceFilter,
12
   updateWorkspaceFilter,
11
   updateWorkspaceListData
13
   updateWorkspaceListData
12
 } from '../action-creator.sync.js'
14
 } from '../action-creator.sync.js'
13
 import {
15
 import {
16
+  getAppList, getContentTypeList,
14
   getWorkspaceList
17
   getWorkspaceList
15
 } from '../action-creator.async.js'
18
 } from '../action-creator.async.js'
16
 import { PAGE, workspaceConfig } from '../helper.js'
19
 import { PAGE, workspaceConfig } from '../helper.js'
22
     super(props)
25
     super(props)
23
     this.state = {
26
     this.state = {
24
       sidebarClose: false,
27
       sidebarClose: false,
25
-      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null
28
+      workspaceIdInUrl: props.match && props.match.params.idws ? parseInt(props.match.params.idws) : null
26
     }
29
     }
27
 
30
 
28
     document.addEventListener('appCustomEvent', this.customEventReducer)
31
     document.addEventListener('appCustomEvent', this.customEventReducer)
32
     switch (type) {
35
     switch (type) {
33
       case 'refreshWorkspaceList':
36
       case 'refreshWorkspaceList':
34
         console.log('%c<Sidebar> Custom event', 'color: #28a745', type, data)
37
         console.log('%c<Sidebar> Custom event', 'color: #28a745', type, data)
35
-        this.loadWorkspaceList()
38
+        this.loadAppConfigAndWorkspaceList()
36
         break
39
         break
37
     }
40
     }
38
   }
41
   }
39
 
42
 
40
   componentDidMount () {
43
   componentDidMount () {
41
-    this.loadWorkspaceList()
44
+    // console.log('Sidebar Did Mount', this.props)
45
+    this.loadAppConfigAndWorkspaceList()
42
   }
46
   }
43
 
47
 
44
   componentDidUpdate (prevProps, prevState) {
48
   componentDidUpdate (prevProps, prevState) {
49
+    const { props } = this
50
+
45
     // console.log('%c<Sidebar> Did Update', 'color: #c17838')
51
     // console.log('%c<Sidebar> Did Update', 'color: #c17838')
46
-    if (this.props.match.params.idws === undefined || isNaN(this.props.match.params.idws)) return
52
+    if (!props.match || props.match.params.idws === undefined || isNaN(props.match.params.idws)) return
47
 
53
 
48
-    const newWorkspaceId = parseInt(this.props.match.params.idws)
54
+    const newWorkspaceId = parseInt(props.match.params.idws)
49
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
55
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
50
   }
56
   }
51
 
57
 
52
-  loadWorkspaceList = async () => {
58
+  loadAppConfigAndWorkspaceList = async () => {
53
     const { workspaceIdInUrl } = this.state
59
     const { workspaceIdInUrl } = this.state
54
     const { user, dispatch } = this.props
60
     const { user, dispatch } = this.props
55
 
61
 
56
     if (user.user_id !== -1) {
62
     if (user.user_id !== -1) {
63
+      const fetchGetAppList = await dispatch(getAppList(user))
64
+      if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
65
+
66
+      const fetchGetContentTypeList = await dispatch(getContentTypeList(user))
67
+      if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
68
+
57
       const fetchGetWorkspaceList = await dispatch(getWorkspaceList(user))
69
       const fetchGetWorkspaceList = await dispatch(getWorkspaceList(user))
58
 
70
 
59
       if (fetchGetWorkspaceList.status === 200) {
71
       if (fetchGetWorkspaceList.status === 200) {

+ 53 - 35
frontend/src/container/Tracim.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 { translate } from 'react-i18next'
3
 import { translate } from 'react-i18next'
4
+import Sidebar from './Sidebar.jsx'
4
 import Header from './Header.jsx'
5
 import Header from './Header.jsx'
5
 import Login from './Login.jsx'
6
 import Login from './Login.jsx'
6
-import Dashboard from './Dashboard.jsx'
7
 import Account from './Account.jsx'
7
 import Account from './Account.jsx'
8
+import AdminWorkspacePage from './AdminWorkspacePage.jsx'
8
 import AppFullscreenManager from './AppFullscreenManager.jsx'
9
 import AppFullscreenManager from './AppFullscreenManager.jsx'
9
 import FlashMessage from '../component/FlashMessage.jsx'
10
 import FlashMessage from '../component/FlashMessage.jsx'
10
 import WorkspaceContent from './WorkspaceContent.jsx'
11
 import WorkspaceContent from './WorkspaceContent.jsx'
11
 import WIPcomponent from './WIPcomponent.jsx'
12
 import WIPcomponent from './WIPcomponent.jsx'
12
 import {
13
 import {
13
-  Route, withRouter, Switch
14
+  Route, withRouter, Redirect
14
 } from 'react-router-dom'
15
 } from 'react-router-dom'
15
-import PrivateRoute from './PrivateRoute.jsx'
16
 import { COOKIE, PAGE } from '../helper.js'
16
 import { COOKIE, PAGE } from '../helper.js'
17
 import {
17
 import {
18
-  getAppList,
19
-  getUserIsConnected,
20
-  getContentTypeList
18
+  getUserIsConnected
21
 } from '../action-creator.async.js'
19
 } from '../action-creator.async.js'
22
 import {
20
 import {
23
   removeFlashMessage,
21
   removeFlashMessage,
24
-  setAppList,
25
-  setUserConnected,
26
-  setContentTypeList
22
+  setUserConnected
27
 } from '../action-creator.sync.js'
23
 } from '../action-creator.sync.js'
28
 import Cookies from 'js-cookie'
24
 import Cookies from 'js-cookie'
25
+import Dashboard from './Dashboard.jsx'
29
 
26
 
30
 class Tracim extends React.Component {
27
 class Tracim extends React.Component {
31
   constructor (props) {
28
   constructor (props) {
44
   }
41
   }
45
 
42
 
46
   async componentDidMount () {
43
   async componentDidMount () {
44
+    // console.log('<Tracim> did Mount')
47
     const { dispatch } = this.props
45
     const { dispatch } = this.props
48
 
46
 
49
     const userFromCookies = {
47
     const userFromCookies = {
54
     const fetchGetUserIsConnected = await dispatch(getUserIsConnected(userFromCookies))
52
     const fetchGetUserIsConnected = await dispatch(getUserIsConnected(userFromCookies))
55
     switch (fetchGetUserIsConnected.status) {
53
     switch (fetchGetUserIsConnected.status) {
56
       case 200:
54
       case 200:
57
-        const userLogged = {
55
+        dispatch(setUserConnected({
58
           ...fetchGetUserIsConnected.json,
56
           ...fetchGetUserIsConnected.json,
59
           auth: userFromCookies.auth,
57
           auth: userFromCookies.auth,
60
           logged: true
58
           logged: true
61
-        }
62
-
63
-        dispatch(setUserConnected(userLogged))
64
-
65
-        const fetchGetAppList = await dispatch(getAppList(userLogged))
66
-        if (fetchGetAppList.status === 200) dispatch(setAppList(fetchGetAppList.json))
67
-
68
-        const fetchGetContentTypeList = await dispatch(getContentTypeList(userLogged))
69
-        if (fetchGetContentTypeList.status === 200) dispatch(setContentTypeList(fetchGetContentTypeList.json))
59
+        }))
70
         break
60
         break
71
-
72
       case 401:
61
       case 401:
73
         dispatch(setUserConnected({logged: false})); break
62
         dispatch(setUserConnected({logged: false})); break
74
-
75
       default:
63
       default:
76
         dispatch(setUserConnected({logged: null})); break
64
         dispatch(setUserConnected({logged: null})); break
77
     }
65
     }
80
   handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
68
   handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
81
 
69
 
82
   render () {
70
   render () {
83
-    const { flashMessage, t } = this.props
71
+    const { props } = this
84
 
72
 
85
     return (
73
     return (
86
       <div className='tracim'>
74
       <div className='tracim'>
87
         <Header />
75
         <Header />
88
-        <FlashMessage flashMessage={flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={t} />
76
+        <FlashMessage flashMessage={props.flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={props.t} />
89
 
77
 
90
         <div className='tracim__content'>
78
         <div className='tracim__content'>
91
           <Route path={PAGE.LOGIN} component={Login} />
79
           <Route path={PAGE.LOGIN} component={Login} />
92
 
80
 
93
-          <PrivateRoute exact path='/' component={WorkspaceContent} />
94
-
95
-          <Switch>
96
-            <PrivateRoute path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />
97
-            <PrivateRoute path={PAGE.WORKSPACE.CALENDAR(':idws')} component={() => <div><br /><br /><br /><br />NYI</div>} />
98
-            <PrivateRoute path={PAGE.WORKSPACE.CONTENT(':idws', ':type?', ':idcts?')} component={WorkspaceContent} />
99
-          </Switch>
100
-
101
-          <PrivateRoute path={PAGE.ACCOUNT} component={Account} />
102
-          <PrivateRoute path={PAGE.ADMIN.ROOT} component={AppFullscreenManager} />
103
-          <PrivateRoute path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
81
+          <Route exact path='/' component={() => {
82
+            switch (props.user.logged) {
83
+              case true:
84
+                return <Redirect to={{pathname: PAGE.WORKSPACE.ROOT, state: {from: props.location}}} />
85
+              case false:
86
+                return <Redirect to={{pathname: '/login', state: {from: props.location}}} />
87
+              case null:
88
+                return null
89
+            }
90
+          }} />
91
+
92
+          { props.user.logged
93
+            ? (
94
+              <Route path='/workspaces/:idws?' render={() => // Workspace Router
95
+                <div className='sidebarpagecontainer'>
96
+                  <Sidebar />
97
+
98
+                  <Route exact path={PAGE.WORKSPACE.ROOT} render={() => props.workspaceList.length === 0 // handle '/' and redirect to first workspace
99
+                    ? null
100
+
101
+          <PrivateRoute path='/admin_temp/workspace' component={AdminWorkspacePage} />
102
+
103
+                  <Route exact path={`${PAGE.WORKSPACE.ROOT}/:idws`} render={props2 => // handle '/workspaces/:id' and add '/contents'
104
+                    <Redirect to={{pathname: `/workspaces/${props2.match.params.idws}/contents`, state: {from: props.location}}} />
105
+                  } />
106
+
107
+                  <Route path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />
108
+                  <Route path={PAGE.WORKSPACE.CALENDAR(':idws')} component={() => <div><br /><br /><br /><br />NYI</div>} />
109
+                  <Route path={PAGE.WORKSPACE.CONTENT(':idws', ':type', ':idcts')} component={WorkspaceContent} />
110
+                  <Route exact path={PAGE.WORKSPACE.CONTENT_LIST(':idws')} component={WorkspaceContent} />
111
+
112
+                  <Route path={PAGE.ACCOUNT} component={Account} />
113
+                  <Route path={PAGE.ADMIN.ROOT} component={AppFullscreenManager} />
114
+                </div>
115
+              } />
116
+            )
117
+            : props.user.logged === false && props.location.pathname !== '/login' &&
118
+              <Redirect to={{pathname: '/login', state: {from: props.location}}} />
119
+          }
120
+
121
+          <Route path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
104
 
122
 
105
           <div id='appFeatureContainer' />
123
           <div id='appFeatureContainer' />
106
         </div>
124
         </div>
110
   }
128
   }
111
 }
129
 }
112
 
130
 
113
-const mapStateToProps = ({ flashMessage }) => ({ flashMessage })
131
+const mapStateToProps = ({ user, appList, contentType, workspaceList, flashMessage }) => ({ user, appList, contentType, workspaceList, flashMessage })
114
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))
132
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 3 - 6
frontend/src/container/WorkspaceContent.jsx View File

3
 import { withRouter, Route } from 'react-router-dom'
3
 import { withRouter, Route } from 'react-router-dom'
4
 import appFactory from '../appFactory.js'
4
 import appFactory from '../appFactory.js'
5
 import { PAGE } from '../helper.js'
5
 import { PAGE } from '../helper.js'
6
-import Sidebar from './Sidebar.jsx'
7
 import Folder from '../component/Workspace/Folder.jsx'
6
 import Folder from '../component/Workspace/Folder.jsx'
8
 import ContentItem from '../component/Workspace/ContentItem.jsx'
7
 import ContentItem from '../component/Workspace/ContentItem.jsx'
9
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
8
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
108
 
107
 
109
   handleClickContentItem = content => {
108
   handleClickContentItem = content => {
110
     console.log('%c<WorkspaceContent> content clicked', 'color: #c17838', content)
109
     console.log('%c<WorkspaceContent> content clicked', 'color: #c17838', content)
111
-    this.props.history.push(`/workspaces/${content.idWorkspace}/${content.type}/${content.id}`)
110
+    this.props.history.push(PAGE.WORKSPACE.CONTENT(content.idWorkspace, content.type, content.id))
112
   }
111
   }
113
 
112
 
114
   handleClickEditContentItem = (e, content) => {
113
   handleClickEditContentItem = (e, content) => {
166
       : []
165
       : []
167
 
166
 
168
     return (
167
     return (
169
-      <div className='sidebarpagecontainer'>
170
-        <Sidebar />
171
-
168
+      <div className='WorkspaceContent' style={{width: '100%'}}>
172
         <OpenContentApp
169
         <OpenContentApp
173
           // automatically open the app for the idContent in url
170
           // automatically open the app for the idContent in url
174
           idWorkspace={this.state.workspaceIdInUrl}
171
           idWorkspace={this.state.workspaceIdInUrl}
192
             subtitle={workspaceContentList.label ? workspaceContentList.label : ''}
189
             subtitle={workspaceContentList.label ? workspaceContentList.label : ''}
193
           >
190
           >
194
             <DropdownCreateButton
191
             <DropdownCreateButton
195
-              parentClass='workspace__header__btnaddworkspace'
192
+              parentClass='workspace__header__btnaddcontent'
196
               idFolder={null} // null because it is workspace root content
193
               idFolder={null} // null because it is workspace root content
197
               onClickCreateContent={this.handleClickCreateContent}
194
               onClickCreateContent={this.handleClickCreateContent}
198
               availableApp={contentType}
195
               availableApp={contentType}

+ 11 - 0
frontend/src/css/AdminWorkspacePage.styl View File

1
+.adminWorkspacePage
2
+  &__createworkspace
3
+    &__btncreate
4
+      margin 25px 15px
5
+  &__description
6
+    margin 25px 15px
7
+    font-size 20px
8
+  &__delimiter
9
+    margin 25px auto 65px auto
10
+  &__workspaceTable
11
+    margin 25px 15px

+ 5 - 121
frontend/src/css/Dashboard.styl View File

31
     color lecteur
31
     color lecteur
32
 
32
 
33
 .dashboard
33
 .dashboard
34
-  margin-left 20px
35
   width 100%
34
   width 100%
36
   &__header
35
   &__header
37
     flexwrap()
36
     flexwrap()
59
   &__userstatut
58
   &__userstatut
60
     width 35%
59
     width 35%
61
     &__role
60
     &__role
62
-      flexwrap()
63
       margin 20px 0
61
       margin 20px 0
64
       font-size 18px
62
       font-size 18px
65
       &__msg
63
       &__msg
93
           font-size 30px
91
           font-size 30px
94
         &__title
92
         &__title
95
           font-size 18px
93
           font-size 18px
96
-  &__wksinfo
94
+  &__workspaceInfo
97
     flexwrap()
95
     flexwrap()
98
-    margin-top 150px
99
   &__activity
96
   &__activity
100
     margin 0 35px 50px 0
97
     margin 0 35px 50px 0
101
     width 60%
98
     width 60%
99
+    &__wrapper
100
+      border 1px solid grey
101
+      height 480px
102
+      overflow-y scroll
102
     &__header
103
     &__header
103
       display flex
104
       display flex
104
       justify-content space-between
105
       justify-content space-between
109
         padding 10px 25px
110
         padding 10px 25px
110
         font-size 18px
111
         font-size 18px
111
         cursor pointer
112
         cursor pointer
112
-    &__wrapper
113
-      border 1px solid grey
114
-      height 480px
115
     &__workspace
113
     &__workspace
116
       display flex
114
       display flex
117
       align-items center
115
       align-items center
137
         margin 15px
135
         margin 15px
138
         padding 10px 25px
136
         padding 10px 25px
139
         cursor pointer
137
         cursor pointer
140
-  &__memberlist
141
-    margin 0 0 50px 0
142
-    width 35%
143
-    &__title
144
-      margin-bottom 20px
145
-      padding 6px
146
-      height 45px
147
-    &__wrapper
148
-      position relative
149
-      border 1px solid grey
150
-      height 480px
151
-    &__list
152
-      margin 0
153
-      padding 0
154
-      list-style none
155
-      height 400px
156
-      overflow-Y scroll
157
-      &__item
158
-        display flex
159
-        border-bottom 1px solid grey
160
-        padding 10px 15px
161
-        &:hover
162
-          background-color fourthColor
163
-        &:nth-last-child(1)
164
-          border-bottom 0
165
-        &:nth-child(even)
166
-          background-color grey-hover
167
-          &:hover
168
-            background-color fourthColor
169
-        &__avatar
170
-          margin-right 20px
171
-          & > img
172
-            width 50px
173
-            height 50px
174
-        &__info
175
-          &__name
176
-            font-size 20px
177
-          &__role
178
-            font-size 18px
179
-        &__delete
180
-          font-size 20px
181
-          color darkGrey
182
-          cursor pointer
183
-    &__btnadd
184
-      border-top 1px solid grey
185
-      padding 15px
186
-      &__button
187
-        display flex
188
-        align-items center
189
-        &__avatar
190
-          display flex
191
-          justify-content center
192
-          align-items center
193
-          margin-right 20px
194
-          border 2px dashed grey
195
-          border-radius 50%
196
-          width 50px
197
-          height 50px
198
-          cursor pointer
199
-          &__icon
200
-            color grey
201
-            font-size 25px
202
-        &__text
203
-          font-size 18px
204
-          color fontColor
205
-          cursor pointer
206
-    &__form
207
-      padding 15px
208
-      flex-direction column
209
-      height 100%
210
-      width 100%
211
-      background-color off-white
212
-      &__close
213
-        font-size 20px
214
-        & > i
215
-          cursor pointer
216
-      &__member
217
-        &__name
218
-          .name__label
219
-            margin 30px 0 20px 0
220
-            label()
221
-          .name__input
222
-            margin-bottom 20px
223
-            border 1px solid grey
224
-            border-radius 10px
225
-            padding 10px
226
-            width 300px
227
-        &__create
228
-          display flex
229
-          align-items center
230
-          margin 15px 0
231
-      &__role
232
-        margin-bottom 15px
233
-        coloricon()
234
-        &__text
235
-          margin 15px 0
236
-          label()
237
-        &__list
238
-          margin 0
239
-          padding 0
240
-          list-style none
241
-          &__item
242
-            display flex
243
-            align-items center
244
-            margin 10px 25px 10px 0
245
-            .item
246
-              &__text
247
-                display flex
248
-      &__submitbtn
249
-        display flex
250
-        justify-content flex-end
251
-        & > button
252
-          padding 8px 30px
253
-          cursor pointer
254
   &__moreinfo
138
   &__moreinfo
255
     display flex
139
     display flex
256
     justify-content space-between
140
     justify-content space-between

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

111
 
111
 
112
 
112
 
113
 .pageContentGeneric
113
 .pageContentGeneric
114
-  margin 10px 0 0 0
114
+  margin 10px 15px 0 15px
115
   width 100%
115
   width 100%
116
   height 100%
116
   height 100%
117
 
117
 
198
   font-weight 500
198
   font-weight 500
199
   color thirdColor
199
   color thirdColor
200
 
200
 
201
-.btnaction
202
-  display flex
203
-  flex-direction column
204
-  justify-content center
205
-  margin 0 15px
206
-  border-radius 10px
207
-  padding 15px
208
-  width 230px
209
-  height 200px
210
-  box-shadow shadow-all
211
-  text-align center
212
-  cursor pointer
213
-  &:nth-child(1)
214
-    margin-left 0
215
-  &:nth-last-child
216
-    margin-right 0
217
-
218
 .genericBtnInfoDashboard
201
 .genericBtnInfoDashboard
219
   &__btn
202
   &__btn
220
     display flex
203
     display flex

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

4
     flex-wrap wrap
4
     flex-wrap wrap
5
     margin-right 15px
5
     margin-right 15px
6
   &__content
6
   &__content
7
-    margin 0 15px
8
     &__button
7
     &__button
9
       display flex
8
       display flex
10
       justify-content flex-end
9
       justify-content flex-end

+ 19 - 17
frontend/src/helper.js View File

24
 export const PAGE = {
24
 export const PAGE = {
25
   HOME: '/',
25
   HOME: '/',
26
   WORKSPACE: {
26
   WORKSPACE: {
27
+    ROOT: '/workspaces',
27
     DASHBOARD: (idws = ':idws') => `/workspaces/${idws}/dashboard`,
28
     DASHBOARD: (idws = ':idws') => `/workspaces/${idws}/dashboard`,
28
-    NEW: (idws, type) => `/workspaces/${idws}/${type}/new`,
29
+    NEW: (idws, type) => `/workspaces/${idws}/contents/${type}/new`,
29
     CALENDAR: (idws = ':idws') => `/workspaces/${idws}/calendar`,
30
     CALENDAR: (idws = ':idws') => `/workspaces/${idws}/calendar`,
30
     CONTENT_LIST: (idws = ':idws') => `/workspaces/${idws}/contents`,
31
     CONTENT_LIST: (idws = ':idws') => `/workspaces/${idws}/contents`,
31
-    CONTENT: (idws = ':idws', type = ':type?', idcts = ':idcts?') => `/workspaces/${idws}/${type}/${idcts}`, // @TODO add /contents/ in url and remove <Switch> in <Tracim>
32
-    // CONTENT_NEW: (idws = ':idws', ctstype = ':ctstype') => `/workspaces/${idws}/contents/${ctstype}/new`,
33
-    // CONTENT_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}/edit`,
34
-    // CONTENT_TITLE_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}/title/edit`,
32
+    CONTENT: (idws = ':idws', type = ':type', idcts = ':idcts') => `/workspaces/${idws}/contents/${type}/${idcts}`,
35
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
33
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
36
   },
34
   },
37
   LOGIN: '/login',
35
   LOGIN: '/login',
45
 
43
 
46
 export const ROLE = [{
44
 export const ROLE = [{
47
   id: 0,
45
   id: 0,
48
-  name: 'reader',
49
-  icon: 'fa-eye',
50
-  translationKey: 'role.reader'
46
+  slug: 'reader',
47
+  faIcon: 'eye',
48
+  hexcolor: '#15D948',
49
+  label: 'Reader'
51
 }, {
50
 }, {
52
   id: 1,
51
   id: 1,
53
-  name: 'contributor',
54
-  icon: 'fa-pencil',
55
-  translationKey: 'role.contributor'
52
+  slug: 'contributor',
53
+  faIcon: 'pencil',
54
+  hexcolor: '#3145F7',
55
+  label: 'Contributor'
56
 }, {
56
 }, {
57
   id: 2,
57
   id: 2,
58
-  name: 'content_manager',
59
-  icon: 'fa-graduation-cap',
60
-  translationKey: 'role.content_manager'
58
+  slug: 'content-manager',
59
+  faIcon: 'graduation-cap',
60
+  hexcolor: '#f2af2d',
61
+  label: 'Content manager'
61
 }, {
62
 }, {
62
   id: 3,
63
   id: 3,
63
-  name: 'manager',
64
-  icon: 'fa-gavel',
65
-  translationKey: 'role.manager'
64
+  slug: 'workspace-manager',
65
+  faIcon: 'gavel',
66
+  hexcolor: '#ed0007',
67
+  label: 'Workspace manager'
66
 }]
68
 }]
67
 
69
 
68
 export const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route
70
 export const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route

+ 39 - 6
frontend/src/reducer/currentWorkspace.js View File

1
-import {SET, WORKSPACE_DETAIL, WORKSPACE_MEMBER_LIST} from '../action-creator.sync.js'
1
+import {
2
+  SET,
3
+  WORKSPACE_DETAIL,
4
+  WORKSPACE_MEMBER_LIST,
5
+  WORKSPACE_READ_STATUS_LIST,
6
+  WORKSPACE_RECENT_ACTIVITY_LIST
7
+} from '../action-creator.sync.js'
2
 import { handleRouteFromApi } from '../helper.js'
8
 import { handleRouteFromApi } from '../helper.js'
3
 
9
 
4
 const defaultWorkspace = {
10
 const defaultWorkspace = {
6
   slug: '',
12
   slug: '',
7
   label: '',
13
   label: '',
8
   description: '',
14
   description: '',
9
-  sidebarEntries: [],
10
-  member: []
15
+  sidebarEntryList: [],
16
+  memberList: [],
17
+  recentActivityList: [],
18
+  contentReadStatusList: []
11
 }
19
 }
12
 
20
 
13
 export default function currentWorkspace (state = defaultWorkspace, action) {
21
 export default function currentWorkspace (state = defaultWorkspace, action) {
19
         slug: action.workspaceDetail.slug,
27
         slug: action.workspaceDetail.slug,
20
         label: action.workspaceDetail.label,
28
         label: action.workspaceDetail.label,
21
         description: action.workspaceDetail.description,
29
         description: action.workspaceDetail.description,
22
-        sidebarEntries: action.workspaceDetail.sidebar_entries.map(sbe => ({
30
+        sidebarEntryList: action.workspaceDetail.sidebar_entries.map(sbe => ({
23
           slug: sbe.slug,
31
           slug: sbe.slug,
24
           route: handleRouteFromApi(sbe.route),
32
           route: handleRouteFromApi(sbe.route),
25
           faIcon: sbe.fa_icon,
33
           faIcon: sbe.fa_icon,
31
     case `${SET}/${WORKSPACE_MEMBER_LIST}`:
39
     case `${SET}/${WORKSPACE_MEMBER_LIST}`:
32
       return {
40
       return {
33
         ...state,
41
         ...state,
34
-        member: action.workspaceMemberList.map(m => ({
42
+        memberList: action.workspaceMemberList.map(m => ({
35
           id: m.user_id,
43
           id: m.user_id,
36
           publicName: m.user.public_name,
44
           publicName: m.user.public_name,
37
           avatarUrl: m.user.avatar_url,
45
           avatarUrl: m.user.avatar_url,
38
           role: m.role,
46
           role: m.role,
39
-          isActive: m.is_active,
47
+          isActive: m.is_active
40
         }))
48
         }))
41
       }
49
       }
42
 
50
 
51
+    case `${SET}/${WORKSPACE_RECENT_ACTIVITY_LIST}`:
52
+      return {
53
+        ...state,
54
+        recentActivityList: action.workspaceRecentActivityList.map(ra => ({
55
+          id: ra.content_id,
56
+          slug: ra.slug,
57
+          label: ra.label,
58
+          type: ra.content_type,
59
+          idParent: ra.parent_id,
60
+          showInUi: ra.show_in_ui,
61
+          isArchived: ra.is_archived,
62
+          isDeleted: ra.is_deleted,
63
+          statusSlug: ra.status,
64
+          subContentTypeSlug: ra.sub_content_types
65
+        }))
66
+      }
67
+
68
+    case `${SET}/${WORKSPACE_READ_STATUS_LIST}`:
69
+      return {
70
+        ...state,
71
+        contentReadStatusList: action.workspaceReadStatusList
72
+          .filter(content => content.read_by_user)
73
+          .map(content => content.content_id)
74
+      }
75
+
43
     default:
76
     default:
44
       return state
77
       return state
45
   }
78
   }

+ 1 - 1
frontend/src/reducer/workspaceContentList.js View File

16
         type: wsc.content_type,
16
         type: wsc.content_type,
17
         idWorkspace: wsc.workspace_id,
17
         idWorkspace: wsc.workspace_id,
18
         isArchived: wsc.is_archived,
18
         isArchived: wsc.is_archived,
19
-        parentId: wsc.parent_id,
19
+        idParent: wsc.parent_id,
20
         isDeleted: wsc.is_deleted,
20
         isDeleted: wsc.is_deleted,
21
         showInUi: wsc.show_in_ui,
21
         showInUi: wsc.show_in_ui,
22
         statusSlug: wsc.status,
22
         statusSlug: wsc.status,

+ 1 - 1
frontend_lib/dist/index.html View File

17
 
17
 
18
   <div id='content'></div>
18
   <div id='content'></div>
19
 
19
 
20
-  <script type='text/javascript' src='./tracim_lib.dev.js'></script>
20
+  <script type='text/javascript' src='./tracim_frontend_lib.dev.js'></script>
21
 </body>
21
 </body>
22
 </html>
22
 </html>

File diff suppressed because it is too large
+ 0 - 15
frontend_lib/dist/tracim_lib.js


+ 2 - 2
frontend_lib/package.json View File

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 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",
11
-    "build": "NODE_ENV=production webpack -p",
10
+    "buildwindoz": "echo 'Only use npm run buildtracimlibwindoz'",
11
+    "build": "echo 'Only use npm run buildtracimlib'",
12
     "build-translation": "node i18next.scanner.js",
12
     "build-translation": "node i18next.scanner.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
     "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",
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
     "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",

+ 68 - 0
frontend_lib/src/component/Input/Checkbox.jsx View File

1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import classnames from 'classnames'
4
+
5
+const style = {
6
+  label: {
7
+    position: 'relative',
8
+    width: '18px',
9
+    height: '18px',
10
+    border: '1px solid #999',
11
+    borderRadius: '3px',
12
+    backgroundColor: '#eee',
13
+    cursor: 'pointer'
14
+  },
15
+  checked: {
16
+    position: 'absolute',
17
+    top: '-3px',
18
+    left: '2px',
19
+    fontSize: '18px',
20
+    color: '#333'
21
+  },
22
+  input: {
23
+    width: '0',
24
+    height: '0',
25
+    visibility: 'hidden'
26
+  },
27
+  disabled: {
28
+    cursor: 'default'
29
+  }
30
+}
31
+
32
+export const Checkbox = props =>
33
+  <label
34
+    className={classnames('checkboxCustom', {'checked': props.checked})}
35
+    style={{
36
+      ...style.label,
37
+      ...(props.disabled ? style.disabled : {})
38
+    }}
39
+    onClick={props.onClickCheckbox}
40
+  >
41
+    { props.checked && <div className='checboxCustom__checked' style={style.checked}>✔</div> }
42
+    <input
43
+      type='checkbox'
44
+      name={`checkbox-${props.name}`}
45
+      checked={props.checked}
46
+      defaultChecked={props.defaultChecked}
47
+      onChange={() => {}} // to remove warning
48
+      style={style.input}
49
+      disabled={props.disabled}
50
+    />
51
+  </label>
52
+
53
+Checkbox.propTypes = {
54
+  name: PropTypes.string.isRequired,
55
+  onClickCheckbox: PropTypes.func.isRequired,
56
+  checked: PropTypes.bool,
57
+  defaultChecked: PropTypes.bool,
58
+  disabled: PropTypes.bool
59
+}
60
+
61
+Checkbox.defaultProps = {
62
+  name: '',
63
+  onClickCheckbox: () => {},
64
+  checked: false,
65
+  disabled: false
66
+}
67
+
68
+export default Checkbox

+ 7 - 0
frontend_lib/src/index.dev.js View File

8
 
8
 
9
 import TextAreaApp from './component/Input/TextAreaApp/TextAreaApp.jsx'
9
 import TextAreaApp from './component/Input/TextAreaApp/TextAreaApp.jsx'
10
 import BtnSwitch from './component/Input/BtnSwitch/BtnSwitch.jsx'
10
 import BtnSwitch from './component/Input/BtnSwitch/BtnSwitch.jsx'
11
+import Checkbox from './component/Input/Checkbox.jsx'
11
 
12
 
12
 import Timeline from './component/Timeline/Timeline.jsx'
13
 import Timeline from './component/Timeline/Timeline.jsx'
13
 import TimelineDebugData from './component/Timeline/debugData.js'
14
 import TimelineDebugData from './component/Timeline/debugData.js'
17
 import CardPopup from './component/CardPopup/CardPopup.jsx'
18
 import CardPopup from './component/CardPopup/CardPopup.jsx'
18
 import CardPopupCreateContent from './component/CardPopup/CardPopupCreateContent.jsx'
19
 import CardPopupCreateContent from './component/CardPopup/CardPopupCreateContent.jsx'
19
 
20
 
21
+
20
 import NewVersionButton from './component/OptionComponent/NewVersionBtn.jsx'
22
 import NewVersionButton from './component/OptionComponent/NewVersionBtn.jsx'
21
 import ArchiveDeleteContent from './component/OptionComponent/ArchiveDeleteContent.jsx'
23
 import ArchiveDeleteContent from './component/OptionComponent/ArchiveDeleteContent.jsx'
22
 
24
 
78
           <span>Here will be the app content. Style is handled by the app (obviously)</span>
80
           <span>Here will be the app content. Style is handled by the app (obviously)</span>
79
           <BtnSwitch />
81
           <BtnSwitch />
80
           {/* <TextAreaApp customClass={'randomClass'} text={'woot'} /> */}
82
           {/* <TextAreaApp customClass={'randomClass'} text={'woot'} /> */}
83
+          <Checkbox
84
+            name='osef'
85
+            onClickCheckbox={() => {}}
86
+            checked
87
+          />
81
         </div>
88
         </div>
82
 
89
 
83
         <Timeline
90
         <Timeline

+ 6 - 8
frontend_lib/src/index.js View File

1
-import { libAddAllResourceI18n, libHandleFetchResult } from './helper.js'
2
-
3
-// fr and en are deprecated
4
-import fr from './translate/fr.js'
5
-import en from './translate/en.js'
1
+import {
2
+  libAddAllResourceI18n,
3
+  libHandleFetchResult
4
+} from './helper.js'
6
 
5
 
7
 import libPopinFixed from './component/PopinFixed/PopinFixed.jsx'
6
 import libPopinFixed from './component/PopinFixed/PopinFixed.jsx'
8
 import libPopinFixedHeader from './component/PopinFixed/PopinFixedHeader.jsx'
7
 import libPopinFixedHeader from './component/PopinFixed/PopinFixedHeader.jsx'
13
 
12
 
14
 import libTextAreaApp from './component/Input/TextAreaApp/TextAreaApp.jsx'
13
 import libTextAreaApp from './component/Input/TextAreaApp/TextAreaApp.jsx'
15
 import libBtnSwitch from './component/Input/BtnSwitch/BtnSwitch.jsx'
14
 import libBtnSwitch from './component/Input/BtnSwitch/BtnSwitch.jsx'
15
+import libCheckbox from './component/Input/Checkbox.jsx'
16
 
16
 
17
 import libPageWrapper from './component/Layout/PageWrapper.jsx'
17
 import libPageWrapper from './component/Layout/PageWrapper.jsx'
18
 import libPageTitle from './component/Layout/PageTitle.jsx'
18
 import libPageTitle from './component/Layout/PageTitle.jsx'
27
 import libArchiveDeleteContent from './component/OptionComponent/ArchiveDeleteContent.jsx'
27
 import libArchiveDeleteContent from './component/OptionComponent/ArchiveDeleteContent.jsx'
28
 import libSelectStatus from './component/Input/SelectStatus/SelectStatus.jsx'
28
 import libSelectStatus from './component/Input/SelectStatus/SelectStatus.jsx'
29
 
29
 
30
-export const langFr = fr
31
-export const langEn = en
32
-
33
 export const addAllResourceI18n = libAddAllResourceI18n
30
 export const addAllResourceI18n = libAddAllResourceI18n
34
 
31
 
35
 export const handleFetchResult = libHandleFetchResult
32
 export const handleFetchResult = libHandleFetchResult
43
 
40
 
44
 export const TextAreaApp = libTextAreaApp
41
 export const TextAreaApp = libTextAreaApp
45
 export const BtnSwitch = libBtnSwitch
42
 export const BtnSwitch = libBtnSwitch
43
+export const Checkbox = libCheckbox
46
 
44
 
47
 export const PageWrapper = libPageWrapper
45
 export const PageWrapper = libPageWrapper
48
 export const PageTitle = libPageTitle
46
 export const PageTitle = libPageTitle

+ 45 - 28
install_frontend_dependencies.sh View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
+# shellcheck disable=SC1091
3
 . bash_library.sh # source bash_library.sh
4
 . bash_library.sh # source bash_library.sh
4
 
5
 
5
 # install Tracim Lib
6
 # install Tracim Lib
6
 
7
 
7
 log "cd frontend_lib"
8
 log "cd frontend_lib"
8
-cd frontend_lib
9
-log "npm i"
10
-npm i
11
-log "sudo npm link"
12
-sudo npm link
13
-cd -
9
+(
10
+  cd frontend_lib
11
+  log "npm i"
12
+  npm i
13
+  log "sudo npm link"
14
+  sudo npm link
15
+)
14
 
16
 
15
 # install app Html Document
17
 # install app Html Document
16
 
18
 
17
 log "cd frontend_app_html-document"
19
 log "cd frontend_app_html-document"
18
-cd frontend_app_html-document
19
-log "npm i"
20
-npm i
21
-log "npm link tracim_frontend_lib"
22
-npm link tracim_frontend_lib
23
-cd -
20
+(
21
+  cd frontend_app_html-document
22
+  log "npm i"
23
+  npm i
24
+  log "npm link tracim_frontend_lib"
25
+  npm link tracim_frontend_lib
26
+)
24
 
27
 
25
 # install app Thread
28
 # install app Thread
26
 
29
 
27
 log "cd frontend_app_thread"
30
 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 -
31
+(
32
+  cd frontend_app_thread
33
+  log "npm i"
34
+  npm i
35
+  log "npm link tracim_frontend_lib"
36
+  npm link tracim_frontend_lib
37
+)
38
+
39
+# install app Workspace
40
+
41
+log "cd frontend_app_workspace"
42
+(
43
+  cd frontend_app_workspace
44
+  log "npm i"
45
+  npm i
46
+  log "npm link tracim_frontend_lib"
47
+  npm link tracim_frontend_lib
48
+)
34
 
49
 
35
 # install app Admin Workspace User
50
 # install app Admin Workspace User
36
 
51
 
37
 log "cd frontend_app_admin_workspace_user"
52
 log "cd frontend_app_admin_workspace_user"
38
-cd frontend_app_admin_workspace_user
39
-log "npm i"
40
-npm i
41
-log "npm link tracim_frontend_lib"
42
-npm link tracim_frontend_lib
43
-cd -
53
+(
54
+  cd frontend_app_admin_workspace_user
55
+  log "npm i"
56
+  npm i
57
+  log "npm link tracim_frontend_lib"
58
+  npm link tracim_frontend_lib
59
+)
44
 
60
 
45
 # install Tracim Frontend
61
 # install Tracim Frontend
46
 
62
 
47
 log "cd frontend"
63
 log "cd frontend"
48
-cd frontend
49
-log "npm i"
50
-npm i
51
-cd -
64
+(
65
+  cd frontend
66
+  log "npm i"
67
+  npm i
68
+)