Browse Source

Merge branch 'develop' into feature/650_folder_missing_endpoint_about_edit

inkhey 6 years ago
parent
commit
439cdaaa98
No account linked to committer's email
100 changed files with 1346 additions and 760 deletions
  1. 4 0
      .gitignore
  2. 7 0
      backend/development.ini.sample
  3. 3 0
      backend/setup.py
  4. 9 3
      backend/tests_configs.ini
  5. 9 2
      backend/tracim_backend/__init__.py
  6. 31 0
      backend/tracim_backend/config.py
  7. 4 0
      backend/tracim_backend/exceptions.py
  8. 1 1
      backend/tracim_backend/lib/core/notifications.py
  9. 1 1
      backend/tracim_backend/lib/core/user.py
  10. 1 1
      backend/tracim_backend/lib/core/userworkspace.py
  11. 1 1
      backend/tracim_backend/lib/core/workspace.py
  12. 37 17
      backend/tracim_backend/lib/mail_notifier/notifier.py
  13. 29 0
      backend/tracim_backend/lib/utils/utils.py
  14. 6 0
      backend/tracim_backend/models/applications.py
  15. 23 2
      backend/tracim_backend/models/context_models.py
  16. 3 1
      backend/tracim_backend/templates/mail/content_update_body_html.mak
  17. 1 2
      backend/tracim_backend/templates/mail/created_account_body_html.mak
  18. 1 0
      backend/tracim_backend/tests/library/test_webdav.py
  19. 1 1
      backend/tracim_backend/views/core_api/schemas.py
  20. 0 1
      backend/tracim_backend/views/core_api/user_controller.py
  21. 67 0
      backend/tracim_backend/views/frontend.py
  22. 20 46
      build_full_frontend.sh
  23. 6 0
      color.json.sample
  24. 3 1
      frontend/.gitignore
  25. 0 0
      frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.2.js
  26. 0 0
      frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css
  27. 0 0
      frontend/dist/asset/bootstrap/jquery-3.2.1.js
  28. 0 0
      frontend/dist/asset/bootstrap/popper-1.12.3.js
  29. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/HELP-US-OUT.txt
  30. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.css
  31. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.min.css
  32. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf
  33. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot
  34. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg
  35. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf
  36. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff
  37. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2
  38. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less
  39. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/bordered-pulled.less
  40. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/core.less
  41. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/fixed-width.less
  42. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/font-awesome.less
  43. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less
  44. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less
  45. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/list.less
  46. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less
  47. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/path.less
  48. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/rotated-flipped.less
  49. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/screen-reader.less
  50. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less
  51. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less
  52. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss
  53. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_bordered-pulled.scss
  54. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss
  55. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_fixed-width.scss
  56. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss
  57. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss
  58. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss
  59. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss
  60. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss
  61. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_rotated-flipped.scss
  62. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_screen-reader.scss
  63. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss
  64. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss
  65. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/font-awesome.scss
  66. 0 0
      frontend/dist/asset/tracim/appInterface.js
  67. 0 0
      frontend/dist/asset/tracim/tinymceInit.js
  68. BIN
      frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg
  69. 50 52
      frontend/dist/index.html
  70. 80 0
      frontend/dist/index.mak
  71. 5 12
      frontend/i18next.scanner/en/translation.json
  72. 5 12
      frontend/i18next.scanner/fr/translation.json
  73. 53 3
      frontend/src/action-creator.async.js
  74. 9 4
      frontend/src/action-creator.sync.js
  75. 3 1
      frontend/src/component/Dashboard/ContentTypeBtn.styl
  76. 58 21
      frontend/src/component/Dashboard/MemberList.jsx
  77. 56 1
      frontend/src/component/Dashboard/MemberList.styl
  78. 66 0
      frontend/src/component/Dashboard/MoreInfo.jsx
  79. 30 0
      frontend/src/component/Dashboard/MoreInfo.styl
  80. 19 10
      frontend/src/component/Dashboard/RecentActivity.jsx
  81. 67 0
      frontend/src/component/Dashboard/RecentActivity.styl
  82. 75 0
      frontend/src/component/Dashboard/UserStatus.jsx
  83. 30 0
      frontend/src/component/Dashboard/UserStatus.styl
  84. 8 8
      frontend/src/component/Sidebar/WorkspaceListItem.jsx
  85. 1 1
      frontend/src/component/common/Input/SubDropdownCreateButton.jsx
  86. 75 62
      frontend/src/container/AdminWorkspacePage.jsx
  87. 3 3
      frontend/src/container/AppFullscreenManager.jsx
  88. 144 157
      frontend/src/container/Dashboard.jsx
  89. 5 3
      frontend/src/container/Header.jsx
  90. 25 24
      frontend/src/container/Login.jsx
  91. 37 39
      frontend/src/container/Sidebar.jsx
  92. 38 25
      frontend/src/container/Tracim.jsx
  93. 5 2
      frontend/src/css/AdminWorkspacePage.styl
  94. 6 163
      frontend/src/css/Dashboard.styl
  95. 1 0
      frontend/src/css/Header.styl
  96. 8 2
      frontend/src/css/Login.styl
  97. 95 74
      frontend/src/css/Sidebar.styl
  98. 2 0
      frontend/src/css/index.styl
  99. 19 1
      frontend/src/reducer/currentWorkspace.js
  100. 0 0
      frontend/src/reducer/user.js

+ 4 - 0
.gitignore View File

1
 .idea/
1
 .idea/
2
 frontend/dist/app/
2
 frontend/dist/app/
3
+frontend/dist/tracim.app.entry.js
4
+frontend/dist/asset/tracim/tracim.vendor.bundle.js
3
 frontend_app_html-document/dist/html-document.app.js
5
 frontend_app_html-document/dist/html-document.app.js
4
 frontend_lib/dist/tracim_frontend_lib.js
6
 frontend_lib/dist/tracim_frontend_lib.js
7
+npm-debug.log
8
+package-lock.json

+ 7 - 0
backend/development.ini.sample View File

186
 ## return error
186
 ## return error
187
 # preview.jpg.restricted_dims = True
187
 # preview.jpg.restricted_dims = True
188
 
188
 
189
+### Frontend
190
+frontend.serve = True
191
+# You can set dist folder of tracim frontend. by default, system
192
+# will try to get it automatically according to tracim_v2 repository
193
+# organisation.
194
+# frontend.dist_folder_path = /home/user/tracim_v2/frontend/dist
195
+
189
 ###
196
 ###
190
 # wsgi server configuration
197
 # wsgi server configuration
191
 ###
198
 ###

+ 3 - 0
backend/setup.py View File

41
     'lxml',
41
     'lxml',
42
     'redis',
42
     'redis',
43
     'rq',
43
     'rq',
44
+    # frontend file serve
45
+    'pyramid_mako',
46
+    'spectra',
44
 ]
47
 ]
45
 
48
 
46
 tests_require = [
49
 tests_require = [

+ 9 - 3
backend/tests_configs.ini View File

4
 depot_storage_dir = /tmp/test/depot
4
 depot_storage_dir = /tmp/test/depot
5
 user.auth_token.validity = 604800
5
 user.auth_token.validity = 604800
6
 preview_cache_dir = /tmp/test/preview_cache
6
 preview_cache_dir = /tmp/test/preview_cache
7
-
7
+website.base_url = http://localhost:6543
8
 [app:command_test]
8
 [app:command_test]
9
 use = egg:tracim_backend
9
 use = egg:tracim_backend
10
 sqlalchemy.url = sqlite:///tracim_test.sqlite
10
 sqlalchemy.url = sqlite:///tracim_test.sqlite
12
 depot_storage_dir = /tmp/test/depot
12
 depot_storage_dir = /tmp/test/depot
13
 user.auth_token.validity = 604800
13
 user.auth_token.validity = 604800
14
 preview_cache_dir = /tmp/test/preview_cache
14
 preview_cache_dir = /tmp/test/preview_cache
15
+website.base_url = http://localhost:6543
15
 
16
 
16
 [mail_test]
17
 [mail_test]
17
 sqlalchemy.url = sqlite:///:memory:
18
 sqlalchemy.url = sqlite:///:memory:
37
 email.notification.smtp.port = 1025
38
 email.notification.smtp.port = 1025
38
 email.notification.smtp.user = test_user
39
 email.notification.smtp.user = test_user
39
 email.notification.smtp.password = just_a_password
40
 email.notification.smtp.password = just_a_password
41
+website.base_url = http://localhost:6543
40
 
42
 
41
 [mail_test_async]
43
 [mail_test_async]
42
 sqlalchemy.url = sqlite:///:memory:
44
 sqlalchemy.url = sqlite:///:memory:
63
 email.notification.smtp.port = 1025
65
 email.notification.smtp.port = 1025
64
 email.notification.smtp.user = test_user
66
 email.notification.smtp.user = test_user
65
 email.notification.smtp.password = just_a_password
67
 email.notification.smtp.password = just_a_password
68
+website.base_url = http://localhost:6543
66
 
69
 
67
 [functional_test]
70
 [functional_test]
68
 sqlalchemy.url = sqlite:///tracim_test.sqlite
71
 sqlalchemy.url = sqlite:///tracim_test.sqlite
72
 preview_cache_dir = /tmp/test/preview_cache
75
 preview_cache_dir = /tmp/test/preview_cache
73
 preview.jpg.restricted_dims = True
76
 preview.jpg.restricted_dims = True
74
 email.notification.activated = false
77
 email.notification.activated = false
78
+website.base_url = http://localhost:6543
75
 
79
 
76
 [functional_test_no_db]
80
 [functional_test_no_db]
77
 sqlalchemy.url = sqlite://
81
 sqlalchemy.url = sqlite://
81
 preview_cache_dir = /tmp/test/preview_cache
85
 preview_cache_dir = /tmp/test/preview_cache
82
 preview.jpg.restricted_dims = True
86
 preview.jpg.restricted_dims = True
83
 email.notification.activated = false
87
 email.notification.activated = false
88
+website.base_url = http://localhost:6543
84
 
89
 
85
 [functional_test_with_mail_test_sync]
90
 [functional_test_with_mail_test_sync]
86
 sqlalchemy.url = sqlite:///tracim_test.sqlite
91
 sqlalchemy.url = sqlite:///tracim_test.sqlite
105
 email.notification.smtp.port = 1025
110
 email.notification.smtp.port = 1025
106
 email.notification.smtp.user = test_user
111
 email.notification.smtp.user = test_user
107
 email.notification.smtp.password = just_a_password
112
 email.notification.smtp.password = just_a_password
108
-
113
+website.base_url = http://localhost:6543
109
 
114
 
110
 [functional_test_with_mail_test_async]
115
 [functional_test_with_mail_test_async]
111
 sqlalchemy.url = sqlite:///tracim_test.sqlite
116
 sqlalchemy.url = sqlite:///tracim_test.sqlite
129
 email.notification.smtp.server = 127.0.0.1
134
 email.notification.smtp.server = 127.0.0.1
130
 email.notification.smtp.port = 1025
135
 email.notification.smtp.port = 1025
131
 email.notification.smtp.user = test_user
136
 email.notification.smtp.user = test_user
132
-email.notification.smtp.password = just_a_password
137
+email.notification.smtp.password = just_a_password
138
+website.base_url = http://localhost:6543

+ 9 - 2
backend/tracim_backend/__init__.py View File

8
 from pyramid.config import Configurator
8
 from pyramid.config import Configurator
9
 from pyramid.authentication import BasicAuthAuthenticationPolicy
9
 from pyramid.authentication import BasicAuthAuthenticationPolicy
10
 from hapic.ext.pyramid import PyramidContext
10
 from hapic.ext.pyramid import PyramidContext
11
-from pyramid.exceptions import NotFound
12
 from sqlalchemy.exc import OperationalError
11
 from sqlalchemy.exc import OperationalError
13
 
12
 
14
 from tracim_backend.extensions import hapic
13
 from tracim_backend.extensions import hapic
30
 from tracim_backend.views.contents_api.comment_controller import CommentController
29
 from tracim_backend.views.contents_api.comment_controller import CommentController
31
 from tracim_backend.views.contents_api.file_controller import FileController
30
 from tracim_backend.views.contents_api.file_controller import FileController
32
 from tracim_backend.views.contents_api.folder_controller import FolderController
31
 from tracim_backend.views.contents_api.folder_controller import FolderController
32
+from tracim_backend.views.frontend import FrontendController
33
 from tracim_backend.views.errors import ErrorSchema
33
 from tracim_backend.views.errors import ErrorSchema
34
 from tracim_backend.exceptions import NotAuthenticated
34
 from tracim_backend.exceptions import NotAuthenticated
35
+from tracim_backend.exceptions import PageNotFound
35
 from tracim_backend.exceptions import UserNotActive
36
 from tracim_backend.exceptions import UserNotActive
36
 from tracim_backend.exceptions import InvalidId
37
 from tracim_backend.exceptions import InvalidId
37
 from tracim_backend.exceptions import InsufficientUserProfile
38
 from tracim_backend.exceptions import InsufficientUserProfile
86
     hapic.set_context(context)
87
     hapic.set_context(context)
87
     # INFO - G.M - 2018-07-04 - global-context exceptions
88
     # INFO - G.M - 2018-07-04 - global-context exceptions
88
     # Not found
89
     # Not found
89
-    context.handle_exception(NotFound, HTTPStatus.NOT_FOUND)
90
+    context.handle_exception(PageNotFound, HTTPStatus.NOT_FOUND)
90
     # Bad request
91
     # Bad request
91
     context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
92
     context.handle_exception(WorkspaceNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
92
     context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
93
     context.handle_exception(UserNotFoundInTracimRequest, HTTPStatus.BAD_REQUEST)  # nopep8
106
     context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
107
     context.handle_exception(OperationalError, HTTPStatus.INTERNAL_SERVER_ERROR)
107
     context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)
108
     context.handle_exception(Exception, HTTPStatus.INTERNAL_SERVER_ERROR)
108
 
109
 
110
+
109
     # Add controllers
111
     # Add controllers
110
     session_controller = SessionController()
112
     session_controller = SessionController()
111
     system_controller = SystemController()
113
     system_controller = SystemController()
126
     configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
128
     configurator.include(file_controller.bind, route_prefix=BASE_API_V2)
127
     configurator.include(folder_controller.bind, route_prefix=BASE_API_V2)
129
     configurator.include(folder_controller.bind, route_prefix=BASE_API_V2)
128
 
130
 
131
+    if app_config.FRONTEND_SERVE:
132
+        configurator.include('pyramid_mako')
133
+        frontend_controller = FrontendController(app_config.FRONTEND_DIST_FOLDER_PATH)  # nopep8
134
+        configurator.include(frontend_controller.bind)
135
+
129
     hapic.add_documentation_view(
136
     hapic.add_documentation_view(
130
         '/api/v2/doc',
137
         '/api/v2/doc',
131
         'Tracim v2 API',
138
         'Tracim v2 API',

+ 31 - 0
backend/tracim_backend/config.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 from urllib.parse import urlparse
2
 from urllib.parse import urlparse
3
+
4
+import os
3
 from paste.deploy.converters import asbool
5
 from paste.deploy.converters import asbool
4
 from tracim_backend.lib.utils.logger import logger
6
 from tracim_backend.lib.utils.logger import logger
5
 from depot.manager import DepotManager
7
 from depot.manager import DepotManager
79
             'website.base_url',
81
             'website.base_url',
80
             '',
82
             '',
81
         )
83
         )
84
+        if not self.WEBSITE_BASE_URL:
85
+            raise Exception(
86
+                'website.base_url is needed in order to have correct path in'
87
+                'few place like in email.'
88
+                'You should set it with frontend root url.'
89
+            )
82
 
90
 
83
         # TODO - G.M - 26-03-2018 - [Cleanup] These params seems deprecated for tracimv2,  # nopep8
91
         # TODO - G.M - 26-03-2018 - [Cleanup] These params seems deprecated for tracimv2,  # nopep8
84
         # Verify this
92
         # Verify this
435
 
443
 
436
         self.PREVIEW_JPG_ALLOWED_DIMS = allowed_dims
444
         self.PREVIEW_JPG_ALLOWED_DIMS = allowed_dims
437
 
445
 
446
+        self.FRONTEND_SERVE = asbool(settings.get(
447
+            'frontend.serve', False
448
+        ))
449
+
450
+        # INFO - G.M - 2018-08-06 - we pretend that frontend_dist_folder
451
+        # is probably in frontend subfolder
452
+        # of tracim_v2 parent of both backend and frontend
453
+        backend_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # nopep8
454
+        tracim_v2_folder = os.path.dirname(backend_folder)
455
+        frontend_dist_folder = os.path.join(tracim_v2_folder, 'frontend', 'dist')  # nopep8
456
+
457
+        self.FRONTEND_DIST_FOLDER_PATH = settings.get(
458
+            'frontend.dist_folder_path', frontend_dist_folder
459
+        )
460
+
461
+        # INFO - G.M - 2018-08-06 - We check dist folder existence
462
+        if self.FRONTEND_SERVE and not os.path.isdir(self.FRONTEND_DIST_FOLDER_PATH):  # nopep8
463
+            raise Exception(
464
+                'ERROR: {} folder does not exist as folder. '
465
+                'please set frontend.dist_folder.path'
466
+                'with a correct value'.format(self.FRONTEND_DIST_FOLDER_PATH)
467
+            )
468
+
438
     def configure_filedepot(self):
469
     def configure_filedepot(self):
439
         depot_storage_name = self.DEPOT_STORAGE_NAME
470
         depot_storage_name = self.DEPOT_STORAGE_NAME
440
         depot_storage_path = self.DEPOT_STORAGE_DIR
471
         depot_storage_path = self.DEPOT_STORAGE_DIR

+ 4 - 0
backend/tracim_backend/exceptions.py View File

209
 
209
 
210
 class TooShortAutocompleteString(TracimException):
210
 class TooShortAutocompleteString(TracimException):
211
     pass
211
     pass
212
+
213
+
214
+class PageNotFound(TracimException):
215
+    pass

+ 1 - 1
backend/tracim_backend/lib/core/notifications.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 from sqlalchemy.orm import Session
2
 from sqlalchemy.orm import Session
3
 
3
 
4
-from tracim_backend import CFG
4
+from tracim_backend.config import CFG
5
 from tracim_backend.lib.utils.logger import logger
5
 from tracim_backend.lib.utils.logger import logger
6
 from tracim_backend.models.auth import User
6
 from tracim_backend.models.auth import User
7
 from tracim_backend.models.data import Content
7
 from tracim_backend.models.data import Content

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

9
 from sqlalchemy.orm import Query
9
 from sqlalchemy.orm import Query
10
 from sqlalchemy.orm.exc import NoResultFound
10
 from sqlalchemy.orm.exc import NoResultFound
11
 
11
 
12
-from tracim_backend import CFG
12
+from tracim_backend.config import CFG
13
 from tracim_backend.models.auth import User
13
 from tracim_backend.models.auth import User
14
 from tracim_backend.models.auth import Group
14
 from tracim_backend.models.auth import Group
15
 from tracim_backend.exceptions import NoUserSetted
15
 from tracim_backend.exceptions import NoUserSetted

+ 1 - 1
backend/tracim_backend/lib/core/userworkspace.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import typing
2
 import typing
3
 
3
 
4
-from tracim_backend import CFG
4
+from tracim_backend.config import CFG
5
 from tracim_backend.models.context_models import UserRoleWorkspaceInContext
5
 from tracim_backend.models.context_models import UserRoleWorkspaceInContext
6
 from tracim_backend.models.roles import WorkspaceRoles
6
 from tracim_backend.models.roles import WorkspaceRoles
7
 
7
 

+ 1 - 1
backend/tracim_backend/lib/core/workspace.py View File

5
 from sqlalchemy.orm import Session
5
 from sqlalchemy.orm import Session
6
 from sqlalchemy.orm.exc import NoResultFound
6
 from sqlalchemy.orm.exc import NoResultFound
7
 
7
 
8
-from tracim_backend import CFG
8
+from tracim_backend.config import CFG
9
 from tracim_backend.exceptions import EmptyLabelNotAllowed
9
 from tracim_backend.exceptions import EmptyLabelNotAllowed
10
 from tracim_backend.exceptions import WorkspaceNotFound
10
 from tracim_backend.exceptions import WorkspaceNotFound
11
 from tracim_backend.lib.utils.translation import fake_translator as _
11
 from tracim_backend.lib.utils.translation import fake_translator as _

+ 37 - 17
backend/tracim_backend/lib/mail_notifier/notifier.py View File

10
 from mako.template import Template
10
 from mako.template import Template
11
 from sqlalchemy.orm import Session
11
 from sqlalchemy.orm import Session
12
 
12
 
13
-from tracim_backend import CFG
13
+from tracim_backend.config import CFG
14
 from tracim_backend.lib.core.notifications import INotifier
14
 from tracim_backend.lib.core.notifications import INotifier
15
 from tracim_backend.lib.mail_notifier.sender import EmailSender
15
 from tracim_backend.lib.mail_notifier.sender import EmailSender
16
 from tracim_backend.lib.mail_notifier.utils import SmtpConfiguration, EST
16
 from tracim_backend.lib.mail_notifier.utils import SmtpConfiguration, EST
17
 from tracim_backend.lib.mail_notifier.sender import send_email_through
17
 from tracim_backend.lib.mail_notifier.sender import send_email_through
18
 from tracim_backend.lib.core.workspace import WorkspaceApi
18
 from tracim_backend.lib.core.workspace import WorkspaceApi
19
 from tracim_backend.lib.utils.logger import logger
19
 from tracim_backend.lib.utils.logger import logger
20
-from tracim_backend.models import User
20
+from tracim_backend.lib.utils.utils import get_login_frontend_url
21
+from tracim_backend.lib.utils.utils import get_email_logo_frontend_url
21
 from tracim_backend.models.auth import User
22
 from tracim_backend.models.auth import User
22
 from tracim_backend.models.contents import CONTENT_TYPES
23
 from tracim_backend.models.contents import CONTENT_TYPES
24
+from tracim_backend.models.context_models import ContentInContext
25
+from tracim_backend.models.context_models import WorkspaceInContext
23
 from tracim_backend.models.data import ActionDescription
26
 from tracim_backend.models.data import ActionDescription
24
 from tracim_backend.models.data import Content
27
 from tracim_backend.models.data import Content
25
 from tracim_backend.models.data import UserRoleInWorkspace
28
 from tracim_backend.models.data import UserRoleInWorkspace
234
             show_archived=True,
237
             show_archived=True,
235
             show_deleted=True,
238
             show_deleted=True,
236
         ).get_one(event_content_id, CONTENT_TYPES.Any_SLUG)
239
         ).get_one(event_content_id, CONTENT_TYPES.Any_SLUG)
237
-        main_content = content.parent if content.type == CONTENT_TYPES.Comment.slug else content
240
+        workspace_api = WorkspaceApi(
241
+            session=self.session,
242
+            current_user=user,
243
+            config=self.config,
244
+        )
245
+        workpace_in_context = workspace_api.get_workspace_with_context(workspace_api.get_one(content.workspace_id))  # nopep8
246
+        main_content = content.parent if content.type == CONTENT_TYPES.Comment.slug else content  # nopep8
238
         notifiable_roles = WorkspaceApi(
247
         notifiable_roles = WorkspaceApi(
239
             current_user=user,
248
             current_user=user,
240
             session=self.session,
249
             session=self.session,
265
             # INFO - G.M - 2017-11-15 - set content_id in header to permit reply
274
             # INFO - G.M - 2017-11-15 - set content_id in header to permit reply
266
             # references can have multiple values, but only one in this case.
275
             # references can have multiple values, but only one in this case.
267
             replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8
276
             replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8
268
-                '{content_id}',str(content.content_id)
277
+                '{content_id}', str(content.content_id)
269
             )
278
             )
270
 
279
 
271
             reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8
280
             reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8
297
             # To link this email to a content we create a virtual parent
306
             # To link this email to a content we create a virtual parent
298
             # in reference who contain the content_id.
307
             # in reference who contain the content_id.
299
             message['References'] = formataddr(('',reference_addr))
308
             message['References'] = formataddr(('',reference_addr))
300
-            body_text = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user)
301
-            body_html = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user)
309
+            content_in_context = content_api.get_content_in_context(content)
310
+            body_text = self._build_email_body_for_content(
311
+                self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT,
312
+                role,
313
+                content_in_context,
314
+                workpace_in_context,
315
+                user
316
+            )
317
+            body_html = self._build_email_body_for_content(
318
+                self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML,
319
+                role,
320
+                content_in_context,
321
+                workpace_in_context,
322
+                user
323
+            )
302
 
324
 
303
             part1 = MIMEText(body_text, 'plain', 'utf-8')
325
             part1 = MIMEText(body_text, 'plain', 'utf-8')
304
             part2 = MIMEText(body_html, 'html', 'utf-8')
326
             part2 = MIMEText(body_html, 'html', 'utf-8')
362
             'user': user,
384
             'user': user,
363
             'password': password,
385
             'password': password,
364
             # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url  # nopep8
386
             # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url  # nopep8
365
-            'logo_url': '',
387
+            'logo_url': get_email_logo_frontend_url(self.config),
366
             # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for login_url  # nopep8
388
             # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for login_url  # nopep8
367
-            'login_url': self.config.WEBSITE_BASE_URL,
389
+            'login_url': get_login_frontend_url(self.config),
368
         }
390
         }
369
         body_text = self._render_template(
391
         body_text = self._render_template(
370
             mako_template_filepath=text_template_file_path,
392
             mako_template_filepath=text_template_file_path,
415
             self,
437
             self,
416
             mako_template_filepath: str,
438
             mako_template_filepath: str,
417
             role: UserRoleInWorkspace,
439
             role: UserRoleInWorkspace,
418
-            content: Content,
419
-            actor: User
440
+            content_in_context: ContentInContext,
441
+            workspace_in_context: WorkspaceInContext,
442
+            actor: User,
420
     ) -> str:
443
     ) -> str:
421
         """
444
         """
422
         Build an email body and return it as a string
445
         Build an email body and return it as a string
424
         :param role: the role related to user to whom the email must be sent. The role is required (and not the user only) in order to show in the mail why the user receive the notification
447
         :param role: the role related to user to whom the email must be sent. The role is required (and not the user only) in order to show in the mail why the user receive the notification
425
         :param content: the content item related to the notification
448
         :param content: the content item related to the notification
426
         :param actor: the user at the origin of the action / notification (for example the one who wrote a comment
449
         :param actor: the user at the origin of the action / notification (for example the one who wrote a comment
427
-        :param config: the global configuration
428
         :return: the built email body as string. In case of multipart email, this method must be called one time for text and one time for html
450
         :return: the built email body as string. In case of multipart email, this method must be called one time for text and one time for html
429
         """
451
         """
430
         logger.debug(self, 'Building email content from MAKO template {}'.format(mako_template_filepath))
452
         logger.debug(self, 'Building email content from MAKO template {}'.format(mako_template_filepath))
431
-
453
+        content = content_in_context.content
432
         main_title = content.label
454
         main_title = content.label
433
         content_intro = ''
455
         content_intro = ''
434
         content_text = ''
456
         content_text = ''
435
         call_to_action_text = ''
457
         call_to_action_text = ''
436
 
458
 
437
-        # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for call_to_action_url  # nopep8
438
-        call_to_action_url =''
459
+        call_to_action_url = content_in_context.frontend_url
439
         # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for status_icon_url  # nopep8
460
         # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for status_icon_url  # nopep8
440
         status_icon_url = ''
461
         status_icon_url = ''
441
-        # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for workspace_url  # nopep8
442
-        workspace_url = ''
462
+        workspace_url = workspace_in_context.frontend_url
443
         # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url  # nopep8
463
         # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url  # nopep8
444
-        logo_url = ''
464
+        logo_url = get_email_logo_frontend_url(self.config)
445
 
465
 
446
         action = content.get_last_action().id
466
         action = content.get_last_action().id
447
         if ActionDescription.COMMENT == action:
467
         if ActionDescription.COMMENT == action:

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

2
 import datetime
2
 import datetime
3
 import random
3
 import random
4
 import string
4
 import string
5
+from enum import Enum
6
+
5
 from redis import Redis
7
 from redis import Redis
6
 from rq import Queue
8
 from rq import Queue
7
 
9
 
10
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
12
 DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
11
 DEFAULT_WEBDAV_CONFIG_FILE = "wsgidav.conf"
13
 DEFAULT_WEBDAV_CONFIG_FILE = "wsgidav.conf"
12
 DEFAULT_TRACIM_CONFIG_FILE = "development.ini"
14
 DEFAULT_TRACIM_CONFIG_FILE = "development.ini"
15
+CONTENT_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}/contents/{content_type}/{content_id}'  # nopep8
16
+WORKSPACE_FRONTEND_URL_SCHEMA = 'workspaces/{workspace_id}'  # nopep8
17
+
18
+
19
+def get_root_frontend_url(config: CFG) -> str:
20
+    """
21
+    Return website base url with always '/' at the end
22
+    """
23
+    base_url = ''
24
+    if config.WEBSITE_BASE_URL[-1] == '/':
25
+        base_url = config.WEBSITE_BASE_URL
26
+    else:
27
+        base_url = config.WEBSITE_BASE_URL + '/'
28
+    return base_url
29
+
30
+
31
+def get_login_frontend_url(config: CFG):
32
+    """
33
+    Return login page url
34
+    """
35
+    return get_root_frontend_url(config) + 'login'
36
+
37
+
38
+def get_email_logo_frontend_url(config: CFG):
39
+    # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for email_logo_frontend_url  # nopep8
40
+    return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4QUTDjMSlsws9AAAB89JREFUaN7tmWtwlFcZx3/PeXeXXLmKQJEGsFZApIUBnaqjUm4VgdYyCUib0cJoKLmHW5EWmlGoQkMICURgEPuhtiYDU7XcyVgKVugULZVCQAwUYyENYAmEXHb3PH7IFjYQNgnZHfnA+bKz+7573v//PPf/C/fWvdWhJWHfMbO4P479OpCAigeowWglPq3ARWfU9AJ7Cb/3MAXZn95dBLKKHsfIQmAUIq7rv6t6gU+BKCAGaEBkN9h88tLe7sgjTdjAJ5Y4OGYMIo80Aw8g4kakJyLxiDiIxABPoMwj5+XP3R0ESpP8WC1H1YuqBj4bUWwIB5gA0d/6/xFQFUpKHBJLHADyU9chTEc1BdEZqJ0O+ofb4xcPyuBIxoBQuHc4ov3w2zrwH0Nd0aBDccwgVPsjEg/4Uc4jegKfvkfMmQ9JSfECkFP0HGJeuu0TrJZhSacg9Xh4Caze2wsXC1B9GugO+BAqUIkC/QIinhYsYoEqYDt+fY2s8WXkFE5CnBIg+vaWtJuQukzy5te2l4Bz2ysTkxchshCRWERMU2BKT4RuiLT0vwagHsENPIyR7zAx2eGybws09AUZEQLHcNS5wMEdB8NjgaKyBNTuQuTLoWOAs6AHUd5DpRzxX0KMBe0M+gA4D4Hdw5mT72LJQORxkM6IfgxSC3wjyHpHEN8M8jKPtYeAq2VgvhHg9A4RvPUIKzCyBVe346SM9LZw1y5KShzOxfUhL60SmMfcNUXgisXYS/iNA/IzhNmBgH4I3E8Cxzpmgby9ffFoMSKTQxA4jZjRpI35qENZbF5Rb1SWgcwM7HsQr28KhZnVd55GPfqTkOCbaB+i2lR1uHa8nHYeda0EygJWGILHGXzndaBgz3CQ5Fb8/hSqr5E7uj4sBXBVSjlqN6FaixKHlS8BxCYtHhs/bUly9JT597U9C30/eRYiU4Nc5Q1gH+AG/gvsxJBH2vg3w9pDPTjqFDGxX0FkKOih2PtHOcbIBiPMMi5XT9eDo9/xlu+rDW2BpX+MAQnOCudRWcWFA3OwncYhrke5VpuK1SrW7+sTVgKv5NYjsr2p6TMY1QEGvhi4mmw89unWXaiX6QQEN1YHuHjpELm5lsxvV2MbhxAdWwCSiNfnCXsb7mc/Ih8hXED8J1T1k6awEAG+R+LCLqEJOL2vInwQOP46VHaSm9QYiI1nMU4xwv2oa3OHs0+LiaHuHKr/wOo/r1yuOQwcDEqVw2KdqITQBFJGelFWojITPz8kY+wmAAp3pWLkJVT7oGwkY3R5REar/Ln1qO7AyIfsLGxQ1eB60E3UDmi9kKWPOwmcvP69cM8kROYCXVB5FW18I4LToeLSbTGnKh2T9MIChLHBOI2lT9srMUDB9s7AM8CApj7H/yaZExsiOd9GnbngcjzOciPyVPOuW0RFB7ZvHnDck4FJgbOpwPoPRHpAdxs782bwQSwei3ty0aC2EVh61ANMutEy63Gi/l0VUfSJC7sIjAnhYYON4x7eNgI9KgeifDPIOyuuDygRWnGuKDcQF6roWrGxbSNgzDAgqBuV6ki7z9WT5y4DR0LdoqoVbSOgZhAi7hv4bXzEFarDG7yqbPmsgN2aovhdLdf+2jqBorIE4JGbIugZCvcsijQHr5ojN1tbVaustUWNNP6S0vy60AR+ut4Ndj7CYzdlgL7AixTtmRnRLOToVNAhQeDfR+yPr+iJrIbf/+pM62l0WP8RKNNCSCCTySuJjgT42MTnv4rqjEDvg6J+VH5T8/qynZSW+ts40EgCQtcQDcsDEN2d9IKe4U2hiR5jmG1ERt4QKrS4pkqK2zmRSQMaUsj6D5cuu3C5ppCY6IQLf2dn8CIjZk4Q+N961VnJvlxf+4Z6v/cojvsDYEQL4BXRHVTXfkysjqPfd3tA6YqOnXx293gnLgvV5xFBVa2iv/b6dEX91p+fbf9ImTnxX6gWAadbILARX+0rbEjxohwCFpBVlH1nyJea2KTFY+OduHUGeaFJoNN3gcwr1smu37qsze16y7pQ4a7pYFYj0iuAvho0jbTxJQBkrB6Ky70VGIDqamA9+WmnWtwrZ20/fN4uQA1rss52Slw8wIOZKg4/QIlTKBfY32DttobSZafDJS0Ka3YXYEx6UCXZid8/n6wJRwHIXrsEI7kB6+xHtQTDn8hLPQuipBb2oJOkgSQBPREuAlupq98cfa6mXsXnOFpfV5uQMBRxlMpP/kJpbmP4tNHC3SNBNiLycJAbleLlOXLGVTB33edRLUBkepAuegyRd1D7Pphh10Wr5mJuMT5fNo67L45NQSUlIBqsYlXqkqaiGy51es3eWQh5CDfmUeufTcaE9QBkFY7AyIuImXxT3bfI7doUvQayFbUDMSZYRHiLq64n2JByOXzvBzLGbkJ1GarnP4OPmBt60Or0v+E381Fdj+ILOhYT4p1ADOhTzcA3EdjeXvCtEwDIGNc0J6vNB/0FDXXbmk9uc05wMSYLq+nAgYArtWJ3keYW0eVUVheE9/3ArbYXkND+mbO2H8gk0AnAlGZAb3WlWpQ/I/o6q9JevWMxIyJdWWZ+V4xnOUaebQH4MVS2I/59eO3bFGbWdEiNiVhrmVncH+NfhMg0IA4RH1bfQnQlq9LKwiYnRbTB/9HmKLrWfg2j94HUoI1/Z3XOOe6te+vuWf8DkM0cb7DOQZgAAAAASUVORK5CYII='  # nopep8'
13
 
41
 
14
 
42
 
15
 def get_redis_connection(config: CFG) -> Redis:
43
 def get_redis_connection(config: CFG) -> Redis:
96
     :return: password as string
124
     :return: password as string
97
     """
125
     """
98
     return ''.join(random.choice(chars) for char_number in range(length))
126
     return ''.join(random.choice(chars) for char_number in range(length))
127
+

+ 6 - 0
backend/tracim_backend/models/applications.py View File

36
         self.config = config
36
         self.config = config
37
         self.main_route = main_route
37
         self.main_route = main_route
38
 
38
 
39
+    # TODO - G.M - 2018-08-07 - Refactor slug coherence issue like this one.
40
+    # we probably should not have 2 kind of slug
41
+    @property
42
+    def minislug(self):
43
+        return self.slug.replace('contents/', '')
44
+
39
 
45
 
40
 # default apps
46
 # default apps
41
 calendar = Application(
47
 calendar = Application(

+ 23 - 2
backend/tracim_backend/models/context_models.py View File

5
 
5
 
6
 from slugify import slugify
6
 from slugify import slugify
7
 from sqlalchemy.orm import Session
7
 from sqlalchemy.orm import Session
8
-from tracim_backend import CFG
8
+from tracim_backend.config import CFG
9
 from tracim_backend.config import PreviewDim
9
 from tracim_backend.config import PreviewDim
10
+from tracim_backend.lib.utils.utils import get_root_frontend_url
11
+from tracim_backend.lib.utils.utils import CONTENT_FRONTEND_URL_SCHEMA
12
+from tracim_backend.lib.utils.utils import WORKSPACE_FRONTEND_URL_SCHEMA
10
 from tracim_backend.models import User
13
 from tracim_backend.models import User
11
 from tracim_backend.models.auth import Profile
14
 from tracim_backend.models.auth import Profile
12
 from tracim_backend.models.data import Content
15
 from tracim_backend.models.data import Content
14
 from tracim_backend.models.data import Workspace
17
 from tracim_backend.models.data import Workspace
15
 from tracim_backend.models.data import UserRoleInWorkspace
18
 from tracim_backend.models.data import UserRoleInWorkspace
16
 from tracim_backend.models.roles import WorkspaceRoles
19
 from tracim_backend.models.roles import WorkspaceRoles
17
-from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry
20
+from tracim_backend.models.workspace_menu_entries import default_workspace_menu_entry  # nopep8
18
 from tracim_backend.models.workspace_menu_entries import WorkspaceMenuEntry
21
 from tracim_backend.models.workspace_menu_entries import WorkspaceMenuEntry
19
 from tracim_backend.models.contents import CONTENT_TYPES
22
 from tracim_backend.models.contents import CONTENT_TYPES
20
 
23
 
477
         # apps)
480
         # apps)
478
         return default_workspace_menu_entry(self.workspace)
481
         return default_workspace_menu_entry(self.workspace)
479
 
482
 
483
+    @property
484
+    def frontend_url(self):
485
+        root_frontend_url = get_root_frontend_url(self.config)
486
+        workspace_frontend_url = WORKSPACE_FRONTEND_URL_SCHEMA.format(
487
+            workspace_id=self.workspace_id,
488
+        )
489
+        return root_frontend_url + workspace_frontend_url
490
+
480
 
491
 
481
 class UserRoleWorkspaceInContext(object):
492
 class UserRoleWorkspaceInContext(object):
482
     """
493
     """
675
         assert self._user
686
         assert self._user
676
         return not self.content.has_new_information_for(self._user)
687
         return not self.content.has_new_information_for(self._user)
677
 
688
 
689
+    @property
690
+    def frontend_url(self):
691
+        root_frontend_url = get_root_frontend_url(self.config)
692
+        content_frontend_url = CONTENT_FRONTEND_URL_SCHEMA.format(
693
+            workspace_id=self.workspace_id,
694
+            content_type=self.content_type,
695
+            content_id=self.content_id,
696
+        )
697
+        return root_frontend_url + content_frontend_url
698
+
678
 
699
 
679
 class RevisionInContext(object):
700
 class RevisionInContext(object):
680
     """
701
     """

+ 3 - 1
backend/tracim_backend/templates/mail/content_update_body_html.mak View File

45
             ${main_title}
45
             ${main_title}
46
             &mdash;&nbsp;<span style="font-weight: bold; color: #999; font-weight: bold;">
46
             &mdash;&nbsp;<span style="font-weight: bold; color: #999; font-weight: bold;">
47
               ${status_label|n}
47
               ${status_label|n}
48
-              <img alt="status_icon" src="${status_icon_url}" style="vertical-align: middle;">
48
+              <img alt="" src="${status_icon_url}" style="vertical-align: middle;">
49
             </span>
49
             </span>
50
         </td>
50
         </td>
51
       </tr>
51
       </tr>
55
     <div id="content-body">
55
     <div id="content-body">
56
         <div>${content_text|n}</div>
56
         <div>${content_text|n}</div>
57
         <div href='' id="call-to-action-container">
57
         <div href='' id="call-to-action-container">
58
+            <span style=""> <a href="${call_to_action_url}"
59
+            id="call-to-action-button">${call_to_action_text}</a> </span> </div>
58
         </div>
60
         </div>
59
     </div>
61
     </div>
60
     
62
     

+ 1 - 2
backend/tracim_backend/templates/mail/created_account_body_html.mak View File

68
         </div>
68
         </div>
69
         <div id="call-to-action-container">
69
         <div id="call-to-action-container">
70
 
70
 
71
-            ${_('To go to {website_title}, please click on following link'.format(
71
+            ${_('To go to {website_title}, please click on following link :'.format(
72
                 website_title=config.WEBSITE_TITLE
72
                 website_title=config.WEBSITE_TITLE
73
             ))}
73
             ))}
74
-
75
             <span style="">
74
             <span style="">
76
                 <a href="${login_url}" id='call-to-action-button'>${login_url}</a>
75
                 <a href="${login_url}" id='call-to-action-button'>${login_url}</a>
77
             </span>
76
             </span>

+ 1 - 0
backend/tracim_backend/tests/library/test_webdav.py View File

29
         :return:
29
         :return:
30
         """
30
         """
31
         tracim_settings = {
31
         tracim_settings = {
32
+            'website.base_url': 'http://localhost:6543',
32
             'sqlalchemy.url': 'sqlite:///:memory:',
33
             'sqlalchemy.url': 'sqlite:///:memory:',
33
             'user.auth_token.validity': '604800',
34
             'user.auth_token.validity': '604800',
34
             'depot_storage_dir': '/tmp/test/depot',
35
             'depot_storage_dir': '/tmp/test/depot',

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

49
     user_id = marshmallow.fields.Int(dump_only=True, example=3)
49
     user_id = marshmallow.fields.Int(dump_only=True, example=3)
50
     avatar_url = marshmallow.fields.Url(
50
     avatar_url = marshmallow.fields.Url(
51
         allow_none=True,
51
         allow_none=True,
52
-        example="/api/v2/assets/avatars/suri-cate.jpg",
52
+        example="/api/v2/asset/avatars/suri-cate.jpg",
53
         description="avatar_url is the url to the image file. "
53
         description="avatar_url is the url to the image file. "
54
                     "If no avatar, then set it to null "
54
                     "If no avatar, then set it to null "
55
                     "(and frontend will interpret this with a default avatar)",
55
                     "(and frontend will interpret this with a default avatar)",

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

39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
40
 
40
 
41
 
41
 
42
-
43
 class UserController(Controller):
42
 class UserController(Controller):
44
 
43
 
45
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
44
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])

+ 67 - 0
backend/tracim_backend/views/frontend.py View File

1
+import os
2
+
3
+from pyramid.renderers import render_to_response
4
+from pyramid.config import Configurator
5
+from tracim_backend.exceptions import PageNotFound
6
+from tracim_backend.models.applications import applications
7
+from tracim_backend.views import BASE_API_V2
8
+from tracim_backend.lib.utils.request import TracimRequest
9
+from tracim_backend.views.controllers import Controller
10
+import spectra
11
+
12
+INDEX_PAGE_NAME = 'index.mak'
13
+APP_FRONTEND_PATH = 'app/{minislug}.app.js'
14
+
15
+
16
+class FrontendController(Controller):
17
+
18
+    def __init__(self, dist_folder_path: str):
19
+        self.dist_folder_path = dist_folder_path
20
+
21
+    def _get_index_file_path(self):
22
+        index_file_path = os.path.join(self.dist_folder_path, INDEX_PAGE_NAME)
23
+        if not os.path.exists(index_file_path):
24
+            raise FileNotFoundError()
25
+        return index_file_path
26
+
27
+    def not_found_view(self, context, request: TracimRequest):
28
+
29
+        if request.path.startswith(BASE_API_V2):
30
+            raise PageNotFound('{} is not a valid path'.format(request.path)) from context  # nopep8
31
+        return self.index(context, request)
32
+
33
+    def index(self, context, request: TracimRequest):
34
+        app_config = request.registry.settings['CFG']
35
+        # TODO - G.M - 2018-08-07 - Refactor autogen valid app list for frontend
36
+        frontend_apps = []
37
+        for app in applications:
38
+            app_frontend_path = APP_FRONTEND_PATH.replace('{minislug}',
39
+                                                          app.minislug)  # nopep8
40
+            app_path = os.path.join(self.dist_folder_path,
41
+                                    app_frontend_path)  # nopep8
42
+            if os.path.exists(app_path):
43
+                frontend_apps.append(app)
44
+        return render_to_response(
45
+            self._get_index_file_path(),
46
+            {
47
+                'colors': {
48
+                    'primary': spectra.html('#7d4e24'),
49
+                },
50
+                'applications': frontend_apps,
51
+            }
52
+        )
53
+
54
+    def bind(self, configurator: Configurator) -> None:
55
+
56
+        configurator.add_notfound_view(self.not_found_view)
57
+        # index.html for /index.html and /
58
+        configurator.add_route('root', '/', request_method='GET')
59
+        configurator.add_view(self.index, route_name='root')
60
+        configurator.add_route('index', INDEX_PAGE_NAME, request_method='GET')
61
+        configurator.add_view(self.index, route_name='index')
62
+
63
+        for dirname in os.listdir(self.dist_folder_path):
64
+            configurator.add_static_view(
65
+                name=dirname,
66
+                path=os.path.join(self.dist_folder_path, dirname)
67
+            )

+ 20 - 46
build_full_frontend.sh View File

8
     windoz="windoz"
8
     windoz="windoz"
9
 fi
9
 fi
10
 
10
 
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
+echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}"
12
+
13
+# get the new sources
14
+git pull origin develop
15
+
16
+# create folder frontend/dist/app/ if no exists
17
+if [ ! -d "frontend/dist/app/" ]; then
18
+  mkdir frontend/dist/app/
19
+fi
12
 
20
 
13
 # Tracim Lib
21
 # Tracim Lib
14
-log "build frontend_lib"
15
 (
22
 (
23
+  log "build frontend_lib"
16
   cd frontend_lib || exit
24
   cd frontend_lib || exit
17
   npm run buildtracimlib$windoz
25
   npm run buildtracimlib$windoz
18
 )
26
 )
19
 
27
 
20
 
28
 
21
 # app Html Document
29
 # app Html Document
22
-log "build frontend_app_html-document"
23
 (
30
 (
24
   cd frontend_app_html-document || exit
31
   cd frontend_app_html-document || exit
25
-  npm run build$windoz
32
+  ./build_html-document.sh
26
 )
33
 )
27
 
34
 
28
-log "copying built file to frontend/"
29
-cp frontend_app_html-document/dist/html-document.app.js frontend/dist/app/
30
-
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
33
-
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
36
-
37
 
35
 
38
 # app Thread
36
 # app Thread
39
-log "build frontend_app_thread"
40
 (
37
 (
41
   cd frontend_app_thread || exit
38
   cd frontend_app_thread || exit
42
-  npm run build$windoz
39
+  ./build_thread.sh
43
 )
40
 )
44
 
41
 
45
-log "copying built file to frontend/"
46
-cp frontend_app_thread/dist/thread.app.js frontend/dist/app/
47
-
48
-log "copying Thread en translation.json"
49
-cp frontend_app_thread/i18next.scanner/en/translation.json frontend/dist/app/thread_en_translation.json
50
-
51
-log "copying Thread fr translation.json"
52
-cp frontend_app_thread/i18next.scanner/fr/translation.json frontend/dist/app/thread_fr_translation.json
53
-
54
 
42
 
55
 # app Workspace
43
 # app Workspace
56
-log "build frontend_app_workspace"
57
 (
44
 (
58
   cd frontend_app_workspace || exit
45
   cd frontend_app_workspace || exit
59
-  npm run build$windoz
46
+  ./build_workspace.sh
60
 )
47
 )
61
 
48
 
62
-log "copying built file to frontend/"
63
-cp frontend_app_workspace/dist/workspace.app.js frontend/dist/app/
64
-
65
-log "copying Thread en translation.json"
66
-cp frontend_app_workspace/i18next.scanner/en/translation.json frontend/dist/app/workspace_en_translation.json
67
-
68
-log "copying Thread fr translation.json"
69
-cp frontend_app_workspace/i18next.scanner/fr/translation.json frontend/dist/app/workspace_fr_translation.json
70
-
71
 # app Admin Workspace User
49
 # app Admin Workspace User
72
-log "build frontend_app_admin_workspace_user"
73
 (
50
 (
74
   cd frontend_app_admin_workspace_user || exit
51
   cd frontend_app_admin_workspace_user || exit
75
-  npm run build$windoz
52
+  ./build_admin_workspace_user.sh
76
 )
53
 )
77
 
54
 
78
-log "copying built file to frontend/"
79
-cp frontend_app_admin_workspace_user/dist/admin_workspace_user.app.js frontend/dist/app/
80
-
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
83
-
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
55
+# build Tracim
56
+(
57
+  cd frontend || exit
58
+  npm run build
59
+)
86
 
60
 
87
-log "frontend fully built"
61
+log "-- frontend build successful."

+ 6 - 0
color.json.sample View File

1
+{
2
+  "primary": "#7d4e24",
3
+  "html-document": "#3f52e3",
4
+  "thread": "#ad4cf9",
5
+  "file": "#ff9900"
6
+}

+ 3 - 1
frontend/.gitignore View File

1
 # Created by .ignore support plugin (hsz.mobi)
1
 # Created by .ignore support plugin (hsz.mobi)
2
-.idea/
3
 .git/
2
 .git/
4
 node_modules/
3
 node_modules/
4
+dist/asset/tracim.app.entry.js
5
+dist/asset/tracim.vendor.bundle.js
6
+dist/asset/images/

frontend/dist/dev/bootstrap-4.0.0-beta.2.js → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.2.js View File


frontend/dist/dev/bootstrap-4.0.0-beta.css → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css View File


frontend/dist/dev/jquery-3.2.1.js → frontend/dist/asset/bootstrap/jquery-3.2.1.js View File


frontend/dist/dev/popper-1.12.3.js → frontend/dist/asset/bootstrap/popper-1.12.3.js View File


frontend/dist/font/font-awesome-4.7.0/HELP-US-OUT.txt → frontend/dist/asset/font/font-awesome-4.7.0/HELP-US-OUT.txt View File


frontend/dist/font/font-awesome-4.7.0/css/font-awesome.css → frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.css View File


frontend/dist/font/font-awesome-4.7.0/css/font-awesome.min.css → frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.min.css View File


frontend/dist/font/font-awesome-4.7.0/fonts/FontAwesome.otf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf View File


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot View File


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg View File


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf View File


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff View File


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 View File


frontend/dist/font/font-awesome-4.7.0/less/animated.less → frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less View File


frontend/dist/font/font-awesome-4.7.0/less/bordered-pulled.less → frontend/dist/asset/font/font-awesome-4.7.0/less/bordered-pulled.less View File


frontend/dist/font/font-awesome-4.7.0/less/core.less → frontend/dist/asset/font/font-awesome-4.7.0/less/core.less View File


frontend/dist/font/font-awesome-4.7.0/less/fixed-width.less → frontend/dist/asset/font/font-awesome-4.7.0/less/fixed-width.less View File


frontend/dist/font/font-awesome-4.7.0/less/font-awesome.less → frontend/dist/asset/font/font-awesome-4.7.0/less/font-awesome.less View File


frontend/dist/font/font-awesome-4.7.0/less/icons.less → frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less View File


frontend/dist/font/font-awesome-4.7.0/less/larger.less → frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less View File


frontend/dist/font/font-awesome-4.7.0/less/list.less → frontend/dist/asset/font/font-awesome-4.7.0/less/list.less View File


frontend/dist/font/font-awesome-4.7.0/less/mixins.less → frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less View File


frontend/dist/font/font-awesome-4.7.0/less/path.less → frontend/dist/asset/font/font-awesome-4.7.0/less/path.less View File


frontend/dist/font/font-awesome-4.7.0/less/rotated-flipped.less → frontend/dist/asset/font/font-awesome-4.7.0/less/rotated-flipped.less View File


frontend/dist/font/font-awesome-4.7.0/less/screen-reader.less → frontend/dist/asset/font/font-awesome-4.7.0/less/screen-reader.less View File


frontend/dist/font/font-awesome-4.7.0/less/stacked.less → frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less View File


frontend/dist/font/font-awesome-4.7.0/less/variables.less → frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less View File


frontend/dist/font/font-awesome-4.7.0/scss/_animated.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_bordered-pulled.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_bordered-pulled.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_core.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_fixed-width.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_fixed-width.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_icons.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_larger.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_list.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_mixins.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_path.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_rotated-flipped.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_rotated-flipped.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_screen-reader.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_screen-reader.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_stacked.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_variables.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/font-awesome.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/font-awesome.scss View File


frontend/dist/appInterface.js → frontend/dist/asset/tracim/appInterface.js View File


frontend/dist/tinymceInit.js → frontend/dist/asset/tracim/tinymceInit.js View File


BIN
frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg View File


+ 50 - 52
frontend/dist/index.html View File

1
 <!DOCTYPE html>
1
 <!DOCTYPE html>
2
 <html>
2
 <html>
3
-  <head>
4
-    <meta charset='utf-8' />
5
-    <meta name="viewport" content="width=device-width, user-scalable=no">
6
-    <title>Tracim</title>
7
-    <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
3
+<head>
4
+  <meta charset='utf-8' />
5
+  <meta name="viewport" content="width=device-width, user-scalable=no">
6
+  <title>Tracim</title>
7
+  <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
8
 
8
 
9
-    <link rel="stylesheet" type="text/css" href="/font/font-awesome-4.7.0/css/font-awesome.css">
10
-    <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
-    <!--
12
-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13
-    -->
14
-    <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
15
-    <link rel="stylesheet" type="text/css" href="/dev/bootstrap-4.0.0-beta.css">
9
+  <link rel="stylesheet" type="text/css" href="/asset/font/font-awesome-4.7.0/css/font-awesome.css">
10
+  <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
+  <!--
12
+  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13
+  -->
14
+  <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
15
+  <link rel="stylesheet" type="text/css" href="/asset/bootstrap/bootstrap-4.0.0-beta.css">
16
 
16
 
17
-    <style>
18
-      /* code bellow will be generated by backend */
19
-      .primaryColorFont { color: #7d4e24; }
20
-      .primaryColorFontDarken { color: #572800; }
21
-      .primaryColorFontLighten { color: #a3744a; }
22
-      .whiteFontColor {color: #fdfdfd;}
17
+  <style>
18
+    .primaryColorFont { color: #7d4e24; }
19
+    .primaryColorFontDarken { color: #522c00; }
20
+    .primaryColorFontLighten { color: #a37346; }
23
 
21
 
24
-      .primaryColorFontHover:hover { color: #7d4e24; }
25
-      .primaryColorFontDarkenHover:hover { color: #572800; }
26
-      .primaryColorFontLightenHover:hover { color: #a3744a; }
22
+    .primaryColorFontHover:hover { color: #7d4e24; }
23
+    .primaryColorFontDarkenHover:hover { color: #522c00; }
24
+    .primaryColorFontLightenHover:hover { color: #a37346; }
27
 
25
 
28
-      .primaryColorBg { background-color: #7d4e24; }
29
-      .primaryColorBgDarken { background-color: #572800; }
30
-      .primaryColorBgLighten { background-color: #a3744a; }
31
 
26
 
32
-      .primaryColorBgHover:hover { background-color: #7d4e24; }
33
-      .primaryColorBgDarkenHover:hover { background-color: #572800; }
34
-      .primaryColorBgLightenHover:hover { background-color: #a3744a; }
27
+    .primaryColorBg { background-color: #7d4e24; }
28
+    .primaryColorBgDarken { background-color: #522c00; }
29
+    .primaryColorBgLighten { background-color: #a37346; }
35
 
30
 
36
-      .primaryColorBorder { border-color: #7d4e24; }
37
-      .primaryColorBorderDarken { border-color: #572800; }
38
-      .primaryColorBorderLighten { border-color: #a3744a; }
31
+    .primaryColorBgHover:hover { background-color: #7d4e24; }
32
+    .primaryColorBgDarkenHover:hover { background-color: #522c00; }
33
+    .primaryColorBgLightenHover:hover { background-color: #a37346; }
39
 
34
 
40
-      .primaryColorBorderHover:hover { border-color: #7d4e24; }
41
-      .primaryColorBorderDarkenHover:hover { border-color: #572800; }
42
-      .primaryColorBorderLightenHover:hover { border-color: #a3744a; }
43
 
35
 
44
-    </style>
45
-  </head>
36
+    .primaryColorBorder { border-color: #7d4e24; }
37
+    .primaryColorBorderDarken { border-color: #522c00; }
38
+    .primaryColorBorderLighten { border-color: #a37346; }
46
 
39
 
47
-  <body>
48
-    <div id='content'></div>
40
+    .primaryColorBorderHover:hover { border-color: #7d4e24; }
41
+    .primaryColorBorderDarkenHover:hover { border-color: #522c00; }
42
+    .primaryColorBorderLightenHover:hover { border-color: #a37346; }
43
+  </style>
44
+</head>
49
 
45
 
50
-    <script src='/tracim.vendor.bundle.js'></script>
51
-    <script src='/tracim.app.entry.js'></script>
46
+<body>
47
+<div id='content'></div>
52
 
48
 
53
-    <script src='/app/workspace.app.js'></script>
54
-    <script src='/app/html-document.app.js'></script>
55
-    <script src='/app/thread.app.js'></script>
56
-    <!-- <script src='/app/file.app.js'></script> -->
57
-    <script src='/app/admin_workspace_user.app.js'></script>
49
+<script type='text/javascript' src='/asset/tracim.vendor.bundle.js'></script>
50
+<script type='text/javascript' src='/asset/tracim.app.entry.js'></script>
58
 
51
 
59
-    <script src="/dev/jquery-3.2.1.js"></script>
60
-    <script src="/dev/popper-1.12.3.js"></script>
61
-    <script src="/dev/bootstrap-4.0.0-beta.2.js"></script>
52
+<script type='text/javascript' src='/app/workspace.app.js'></script>
53
+<script type='text/javascript' src='/app/html-document.app.js'></script>
54
+<script type='text/javascript' src='/app/thread.app.js'></script>
55
+<script type='text/javascript' src='/app/admin_workspace_user.app.js'></script>
62
 
56
 
63
-    <script type="text/javascript" src="/asset/tinymce/js/tinymce/jquery.tinymce.min.js"></script>
64
-    <script type="text/javascript" src="/asset/tinymce/js/tinymce/tinymce.min.js"></script>
57
+<script type='text/javascript' src='/asset/bootstrap/jquery-3.2.1.js'></script>
58
+<script type='text/javascript' src='/asset/bootstrap/popper-1.12.3.js'></script>
59
+<script type='text/javascript' src='/asset/bootstrap/bootstrap-4.0.0-beta.2.js'></script>
65
 
60
 
66
-    <script type='text/javascript' src='/appInterface.js'></script>
67
-    <script type='text/javascript' src='/tinymceInit.js'></script>
68
-  </body>
61
+<script type='text/javascript' src='/asset/tinymce/js/tinymce/jquery.tinymce.min.js'></script>
62
+<script type='text/javascript' src='/asset/tinymce/js/tinymce/tinymce.min.js'></script>
63
+
64
+<script type='text/javascript' src='/asset/tracim/appInterface.js'></script>
65
+<script type='text/javascript' src='/asset/tracim/tinymceInit.js'></script>
66
+</body>
69
 </html>
67
 </html>

+ 80 - 0
frontend/dist/index.mak View File

1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+    <meta charset='utf-8' />
5
+    <meta name="viewport" content="width=device-width, user-scalable=no">
6
+    <title>Tracim</title>
7
+    <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
8
+
9
+    <link rel="stylesheet" type="text/css" href="/asset/font/font-awesome-4.7.0/css/font-awesome.css">
10
+    <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
+    <!--
12
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13
+    -->
14
+    <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
15
+    <link rel="stylesheet" type="text/css" href="/asset/bootstrap/bootstrap-4.0.0-beta.css">
16
+
17
+    <style>
18
+      <%
19
+        primary = colors['primary']
20
+        html_class = '.primaryColorFont{state}'
21
+        param = 'color'
22
+        color_change_value = 15
23
+      %>
24
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
25
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
26
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
27
+      <% html_class = '.primaryColorFont{state}Hover:hover' %>
28
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
29
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
30
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
31
+
32
+      <%
33
+        html_class = '.primaryColorBg{state}'
34
+        param = 'background-color'
35
+      %>
36
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
37
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
38
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
39
+      <% html_class = '.primaryColorBg{state}Hover:hover'%>
40
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
41
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
42
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
43
+
44
+      <%
45
+        param = 'border-color'
46
+        html_class = '.primaryColorBorder{state}'
47
+      %>
48
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
49
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
50
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
51
+      <% html_class = '.primaryColorBorder{state}Hover:hover' %>
52
+      ${html_class.replace('{state}', '')} { ${param}: ${primary.hexcode}; }
53
+      ${html_class.replace('{state}', 'Darken')} { ${param}: ${primary.darken(color_change_value).hexcode}; }
54
+      ${html_class.replace('{state}', 'Lighten')} { ${param}: ${primary.brighten(color_change_value).hexcode}; }
55
+    </style>
56
+  </head>
57
+
58
+  <body>
59
+    <div id='content'></div>
60
+
61
+    <script type='text/javascript' src='/asset/tracim.vendor.bundle.js'></script>
62
+    <script type='text/javascript' src='/asset/tracim.app.entry.js'></script>
63
+
64
+    <script type='text/javascript' src='/app/workspace.app.js'></script>
65
+    % for app in applications:
66
+    <script type='text/javascript' src='/app/${app.minislug}.app.js'></script>
67
+    %endfor
68
+    <script type='text/javascript' src='/app/admin_workspace_user.app.js'></script>
69
+
70
+    <script type='text/javascript' src='/asset/bootstrap/jquery-3.2.1.js'></script>
71
+    <script type='text/javascript' src='/asset/bootstrap/popper-1.12.3.js'></script>
72
+    <script type='text/javascript' src='/asset/bootstrap/bootstrap-4.0.0-beta.2.js'></script>
73
+
74
+    <script type='text/javascript' src='/asset/tinymce/js/tinymce/jquery.tinymce.min.js'></script>
75
+    <script type='text/javascript' src='/asset/tinymce/js/tinymce/tinymce.min.js'></script>
76
+
77
+    <script type='text/javascript' src='/asset/tracim/appInterface.js'></script>
78
+    <script type='text/javascript' src='/asset/tracim/tinymceInit.js'></script>
79
+  </body>
80
+</html>

+ 5 - 12
frontend/i18next.scanner/en/translation.json View File

37
   "Change your status": "Change your status",
37
   "Change your status": "Change your status",
38
   "subscriber": "subscriber",
38
   "subscriber": "subscriber",
39
   "unsubscribed": "unsubscribed",
39
   "unsubscribed": "unsubscribed",
40
-  "Start a new Thread": "Start a new Thread",
41
-  "Writing a document": "Writing a document",
42
-  "Upload a file": "Upload a file",
43
-  "Start a videoconference": "Start a videoconference",
44
-  "View the Calendar": "View the Calendar",
45
   "Recent activity": "Recent activity",
40
   "Recent activity": "Recent activity",
46
   "Mark everything as read": "Mark everything as read",
41
   "Mark everything as read": "Mark everything as read",
47
   "See more": "See more",
42
   "See more": "See more",
50
   "Enter the name or email of the member": "Enter the name or email of the member",
45
   "Enter the name or email of the member": "Enter the name or email of the member",
51
   "Create an account": "Create an account",
46
   "Create an account": "Create an account",
52
   "Choose the role of the member": "Choose the role of the member",
47
   "Choose the role of the member": "Choose the role of the member",
53
-  "Supervisor": "Supervisor",
54
-  "Content Manager": "Content Manager",
55
-  "Contributor": "Contributor",
56
-  "Reader": "Reader",
57
   "Validate": "Validate",
48
   "Validate": "Validate",
58
   "Implement Tracim in your explorer": "Implement Tracim in your explorer",
49
   "Implement Tracim in your explorer": "Implement Tracim in your explorer",
59
   "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Find all your documents deposited online directly on your computer via the workstation, without going through the software.",
50
   "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Find all your documents deposited online directly on your computer via the workstation, without going through the software.",
60
   "Workspace Calendar": "Workspace Calendar",
51
   "Workspace Calendar": "Workspace Calendar",
61
   "Each workspace has its own calendar.": "Each workspace has its own calendar.",
52
   "Each workspace has its own calendar.": "Each workspace has its own calendar.",
62
   "Email Adress": "Email Adress",
53
   "Email Adress": "Email Adress",
63
-  "Explore the workspace": "Explore the workspace",
64
   "Old password": "Old password",
54
   "Old password": "Old password",
65
   "New password": "New password",
55
   "New password": "New password",
66
   "Name:": "Name:",
56
   "Name:": "Name:",
67
   "Email Adress:": "Email Adress:",
57
   "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",
58
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
70
   "Connection": "Connection",
59
   "Connection": "Connection",
71
   "Forgotten password ?": "Forgotten password ?",
60
   "Forgotten password ?": "Forgotten password ?",
72
-  "currently, you are ": "currently, you are "
61
+  "An error has happened when fetching workspace detail": "An error has happened when fetching workspace detail",
62
+  "An error has happened while fetching member list": "An error has happened while fetching member list",
63
+  "An error has happened while fetching recent activity list": "An error has happened while fetching recent activity list",
64
+  "An error has happened while fetching read status list": "An error has happened while fetching read status list",
65
+  "Hi {{name}} ! Currently, you are ": "Hi {{name}} ! Currently, you are "
73
 }
66
 }

+ 5 - 12
frontend/i18next.scanner/fr/translation.json View File

37
   "Change your status": "Changer de status",
37
   "Change your status": "Changer de status",
38
   "subscriber": "Abonné(e)",
38
   "subscriber": "Abonné(e)",
39
   "unsubscribed": "Non Abonné(e)",
39
   "unsubscribed": "Non Abonné(e)",
40
-  "Start a new Thread": "Débuter une nouvelle discussion",
41
-  "Writing a document": "Rédiger un document",
42
-  "Upload a file": "Importer un fichier",
43
-  "Start a videoconference": "Débuter une visioconférence",
44
-  "View the Calendar": "Voir le Calendrier",
45
-  "Explore the workspace": "Explorer l'espace de travail",
46
   "Recent activity": "Activité récente",
40
   "Recent activity": "Activité récente",
47
   "Mark everything as read": "Tout marquer comme lu",
41
   "Mark everything as read": "Tout marquer comme lu",
48
   "See more": "Voir plus",
42
   "See more": "Voir plus",
50
   "Add a member": "Ajouter un membre",
44
   "Add a member": "Ajouter un membre",
51
   "Enter the name or email of the member": "Renseigner le nom ou l'email de l'utilisateur",
45
   "Enter the name or email of the member": "Renseigner le nom ou l'email de l'utilisateur",
52
   "Create an account": "Créer un compte",
46
   "Create an account": "Créer un compte",
53
-  "Supervisor": "Responsable",
54
-  "Content Manager": "Gestionnaire de contenu",
55
-  "Contributor": "Contributeur",
56
-  "Reader": "Lecteur",
57
   "Validate": "Validé",
47
   "Validate": "Validé",
58
   "Implement Tracim in your explorer": "Implémenter Tracim dans votre explorateur",
48
   "Implement Tracim in your explorer": "Implémenter Tracim dans votre explorateur",
59
   "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Retrouvez tous vos documents déposés en ligne directement sur votre ordinateur via le poste de travail, sans passer par le logiciel.",
49
   "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Retrouvez tous vos documents déposés en ligne directement sur votre ordinateur via le poste de travail, sans passer par le logiciel.",
65
   "New password": "Nouveau mot de passe",
55
   "New password": "Nouveau mot de passe",
66
   "Name:": "Nom :",
56
   "Name:": "Nom :",
67
   "Email Adress:": "Adresse mail :",
57
   "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.",
58
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
70
   "Connection": "Connexion",
59
   "Connection": "Connexion",
71
   "Forgotten password ?": "Mot de passe oublié ?",
60
   "Forgotten password ?": "Mot de passe oublié ?",
72
-  "currently, you are ": "actuellement, vous êtes "
61
+  "An error has happened when fetching workspace detail": "__NOT_TRANSLATED__",
62
+  "An error has happened while fetching member list": "__NOT_TRANSLATED__",
63
+  "An error has happened while fetching recent activity list": "__NOT_TRANSLATED__",
64
+  "An error has happened while fetching read status list": "__NOT_TRANSLATED__",
65
+  "Hi {{name}} ! Currently, you are ": "Bonjour {{name}} ! Vous êtes actuellement"
73
 }
66
 }

+ 53 - 3
frontend/src/action-creator.async.js View File

6
   USER_LOGOUT,
6
   USER_LOGOUT,
7
   USER_ROLE,
7
   USER_ROLE,
8
   USER_CONNECTED,
8
   USER_CONNECTED,
9
+  USER_KNOWN_MEMBER_LIST,
9
   setUserRole,
10
   setUserRole,
10
   WORKSPACE,
11
   WORKSPACE,
11
   WORKSPACE_LIST,
12
   WORKSPACE_LIST,
12
   WORKSPACE_DETAIL,
13
   WORKSPACE_DETAIL,
13
   WORKSPACE_MEMBER_LIST,
14
   WORKSPACE_MEMBER_LIST,
15
+  WORKSPACE_MEMBER_ADD,
14
   FOLDER,
16
   FOLDER,
15
   setFolderData,
17
   setFolderData,
16
   APP_LIST,
18
   APP_LIST,
148
   if (fetchGetUserRole.status === 200) dispatch(setUserRole(fetchGetUserRole.json))
150
   if (fetchGetUserRole.status === 200) dispatch(setUserRole(fetchGetUserRole.json))
149
 }
151
 }
150
 
152
 
153
+export const getUserKnownMember = (user, userNameToSearch) => dispatch => {
154
+  return fetchWrapper({
155
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/known_members?acp=${userNameToSearch}`,
156
+    param: {
157
+      headers: {
158
+        ...FETCH_CONFIG.headers,
159
+        'Authorization': 'Basic ' + user.auth
160
+      },
161
+      method: 'GET'
162
+    },
163
+    actionName: USER_KNOWN_MEMBER_LIST,
164
+    dispatch
165
+  })
166
+}
167
+
168
+export const putUserWorkspaceRead = (user, idWorkspace) => dispatch => {
169
+  return fetchWrapper({
170
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces/${idWorkspace}/read`,
171
+    param: {
172
+      headers: {
173
+        ...FETCH_CONFIG.headers,
174
+        'Authorization': 'Basic ' + user.auth
175
+      },
176
+      method: 'PUT'
177
+    },
178
+    actionName: USER_KNOWN_MEMBER_LIST,
179
+    dispatch
180
+  })
181
+}
182
+
151
 export const getWorkspaceList = user => dispatch => {
183
 export const getWorkspaceList = user => dispatch => {
152
   return fetchWrapper({
184
   return fetchWrapper({
153
     url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces`,
185
     url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces`,
238
   })
270
   })
239
 }
271
 }
240
 
272
 
273
+export const postWorkspaceMember = (user, idWorkspace, newMember) => dispatch => {
274
+  return fetchWrapper({
275
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/members`,
276
+    param: {
277
+      headers: {
278
+        ...FETCH_CONFIG.headers,
279
+        'Authorization': 'Basic ' + user.auth
280
+      },
281
+      method: 'POST',
282
+      body: JSON.stringify({
283
+        user_id: newMember.id,
284
+        user_email_or_public_name: newMember.name,
285
+        role: newMember.role
286
+      })
287
+    },
288
+    actionName: WORKSPACE_MEMBER_ADD,
289
+    dispatch
290
+  })
291
+}
292
+
241
 export const getFolderContent = (idWorkspace, idFolder) => async dispatch => {
293
 export const getFolderContent = (idWorkspace, idFolder) => async dispatch => {
242
   const fetchGetFolderContent = await fetchWrapper({
294
   const fetchGetFolderContent = await fetchWrapper({
243
     url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/?parent_id=${idFolder}`,
295
     url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/contents/?parent_id=${idFolder}`,
252
 }
304
 }
253
 
305
 
254
 export const getAppList = user => dispatch => {
306
 export const getAppList = user => dispatch => {
255
-  console.log(user)
256
   return fetchWrapper({
307
   return fetchWrapper({
257
     url: `${FETCH_CONFIG.apiUrl}/system/applications`,
308
     url: `${FETCH_CONFIG.apiUrl}/system/applications`,
258
     param: {
309
     param: {
260
         ...FETCH_CONFIG.headers,
311
         ...FETCH_CONFIG.headers,
261
         'Authorization': 'Basic ' + user.auth
312
         'Authorization': 'Basic ' + user.auth
262
       },
313
       },
263
-      method: 'GET',
264
-      'Authorization': 'Basic ' + user.auth
314
+      method: 'GET'
265
     },
315
     },
266
     actionName: APP_LIST,
316
     actionName: APP_LIST,
267
     dispatch
317
     dispatch

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

17
 export const USER = 'User'
17
 export const USER = 'User'
18
 export const USER_LOGIN = `${USER}/Login`
18
 export const USER_LOGIN = `${USER}/Login`
19
 export const USER_LOGOUT = `${USER}/Logout`
19
 export const USER_LOGOUT = `${USER}/Logout`
20
-export const USER_DATA = `${USER}/Data`
21
-export const USER_ROLE = `${USER}/Role`
22
 export const USER_CONNECTED = `${USER}/Connected`
20
 export const USER_CONNECTED = `${USER}/Connected`
23
 export const USER_DISCONNECTED = `${USER}/Disconnected`
21
 export const USER_DISCONNECTED = `${USER}/Disconnected`
24
-export const USER_LANG = `${USER}/Lang`
25
 export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
22
 export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
26
 export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
23
 export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
24
+export const USER_DATA = `${USER}/Data`
27
 export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
25
 export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
26
+export const USER_ROLE = `${USER}/Role`
28
 export const setUserRole = userRole => ({ type: `${SET}/${USER}/Role`, userRole }) // this actually update workspaceList state
27
 export const setUserRole = userRole => ({ type: `${SET}/${USER}/Role`, userRole }) // this actually update workspaceList state
29
-export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
30
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
28
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
31
   ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
29
   ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
30
+export const USER_LANG = `${USER}/Lang`
31
+export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
32
+export const USER_KNOWN_MEMBER = `${USER}/KnownMember`
33
+export const USER_KNOWN_MEMBER_LIST = `${USER_KNOWN_MEMBER}/List`
32
 
34
 
33
 export const WORKSPACE = 'Workspace'
35
 export const WORKSPACE = 'Workspace'
34
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
36
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
45
 export const WORKSPACE_MEMBER = `${WORKSPACE}/Member`
47
 export const WORKSPACE_MEMBER = `${WORKSPACE}/Member`
46
 export const WORKSPACE_MEMBER_LIST = `${WORKSPACE_MEMBER}/List`
48
 export const WORKSPACE_MEMBER_LIST = `${WORKSPACE_MEMBER}/List`
47
 export const setWorkspaceMemberList = workspaceMemberList => ({ type: `${SET}/${WORKSPACE_MEMBER_LIST}`, workspaceMemberList })
49
 export const setWorkspaceMemberList = workspaceMemberList => ({ type: `${SET}/${WORKSPACE_MEMBER_LIST}`, workspaceMemberList })
50
+export const WORKSPACE_MEMBER_ADD = `${WORKSPACE_MEMBER}/${ADD}`
48
 
51
 
49
 export const WORKSPACE_RECENT_ACTIVITY = `${WORKSPACE}/RecentActivity/List`
52
 export const WORKSPACE_RECENT_ACTIVITY = `${WORKSPACE}/RecentActivity/List`
50
 export const WORKSPACE_RECENT_ACTIVITY_LIST = `${WORKSPACE_RECENT_ACTIVITY}/List`
53
 export const WORKSPACE_RECENT_ACTIVITY_LIST = `${WORKSPACE_RECENT_ACTIVITY}/List`
51
 export const setWorkspaceRecentActivityList = workspaceRecentActivityList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_LIST}`, workspaceRecentActivityList })
54
 export const setWorkspaceRecentActivityList = workspaceRecentActivityList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_LIST}`, workspaceRecentActivityList })
55
+export const WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST = `${WORKSPACE_RECENT_ACTIVITY}/ForUser/List`
56
+export const setWorkspaceRecentActivityForUserList = workspaceRecentActivityForUserList => ({ type: `${SET}/${WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST}`, workspaceRecentActivityForUserList })
52
 
57
 
53
 export const WORKSPACE_READ_STATUS = `${WORKSPACE}/ReadStatus`
58
 export const WORKSPACE_READ_STATUS = `${WORKSPACE}/ReadStatus`
54
 export const WORKSPACE_READ_STATUS_LIST = `${WORKSPACE_READ_STATUS}/List`
59
 export const WORKSPACE_READ_STATUS_LIST = `${WORKSPACE_READ_STATUS}/List`

+ 3 - 1
frontend/src/component/Dashboard/ContentTypeBtn.styl View File

1
+@import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
+
1
 .contentTypeBtn
3
 .contentTypeBtn
2
   display flex
4
   display flex
3
   flex-direction column
5
   flex-direction column
4
   justify-content center
6
   justify-content center
5
-  margin 0 15px
7
+  margin 15px
6
   border-radius 10px
8
   border-radius 10px
7
   padding 15px
9
   padding 15px
8
   width 230px
10
   width 230px

+ 58 - 21
frontend/src/component/Dashboard/MemberList.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import PropTypes from 'prop-types'
2
 import PropTypes from 'prop-types'
3
-import { Checkbox } from 'tracim_frontend_lib'
3
+// import { Checkbox } from 'tracim_frontend_lib'
4
 
4
 
5
 require('./MemberList.styl')
5
 require('./MemberList.styl')
6
 
6
 
7
 export class MemberList extends React.Component {
7
 export class MemberList extends React.Component {
8
   constructor (props) {
8
   constructor (props) {
9
     super(props)
9
     super(props)
10
-
11
     this.state = {
10
     this.state = {
12
-      displayNewMemberList: true,
13
-      createAccountCheckbox: false
11
+      displayNewMemberList: true
14
     }
12
     }
15
   }
13
   }
16
 
14
 
21
   handleClickCheckboxCreateAccount = e => {
19
   handleClickCheckboxCreateAccount = e => {
22
     e.preventDefault()
20
     e.preventDefault()
23
     e.stopPropagation()
21
     e.stopPropagation()
24
-    this.setState(prev => ({createAccountCheckbox: !prev.createAccountCheckbox}))
22
+    this.props.onChangeCreateAccount(!this.props.createAccount)
23
+  }
24
+
25
+  handleClickBtnValidate = () => {
26
+    this.props.onClickValidateNewMember()
27
+    this.setState({displayNewMemberList: true})
25
   }
28
   }
26
 
29
 
27
   render () {
30
   render () {
40
               <div>
43
               <div>
41
                 <ul className='memberlist__list'>
44
                 <ul className='memberlist__list'>
42
                   {props.memberList.map(m =>
45
                   {props.memberList.map(m =>
43
-                    <li className='memberlist__list__item primaryColorBgLightenHover' key={m.id}>
46
+                    <li className='memberlist__list__item' key={m.id}>
44
                       <div className='memberlist__list__item__avatar'>
47
                       <div className='memberlist__list__item__avatar'>
45
                         {m.avatarUrl ? <img src={m.avatarUrl} /> : <img src='NYI' />}
48
                         {m.avatarUrl ? <img src={m.avatarUrl} /> : <img src='NYI' />}
46
                       </div>
49
                       </div>
78
               </div>
81
               </div>
79
             )
82
             )
80
             : (
83
             : (
81
-              <form className='memberlist__form'>
84
+              <div className='memberlist__form'>
82
                 <div className='memberlist__form__close d-flex justify-content-end'>
85
                 <div className='memberlist__form__close d-flex justify-content-end'>
83
                   <i className='fa fa-times' onClick={this.handleClickCloseAddMemberBtn} />
86
                   <i className='fa fa-times' onClick={this.handleClickCloseAddMemberBtn} />
84
                 </div>
87
                 </div>
94
                       className='name__input form-control'
97
                       className='name__input form-control'
95
                       id='addmember'
98
                       id='addmember'
96
                       placeholder='Nom ou Email'
99
                       placeholder='Nom ou Email'
97
-                      onChange={props.onChangeName}
100
+                      value={props.nameOrEmail}
101
+                      onChange={e => props.onChangeNameOrEmail(e.target.value)}
102
+                      autoComplete='off'
98
                     />
103
                     />
104
+
105
+                    {props.searchedKnownMemberList.length > 0 &&
106
+                      <div className='autocomplete primaryColorBorder'>
107
+                        {props.searchedKnownMemberList.filter((u, i) => i < 5).map(u => // only displays the first 5
108
+                          <div
109
+                            className='autocomplete__item primaryColorBgHover'
110
+                            onClick={() => props.onClickKnownMember(u)}
111
+                            key={u.user_id}
112
+                          >
113
+                            <div className='autocomplete__item__avatar primaryColorBorder'>
114
+                              <img src={u.avatar_url} />
115
+                            </div>
116
+
117
+                            <div className='autocomplete__item__name'>
118
+                              {u.public_name}
119
+                            </div>
120
+                          </div>
121
+                        )}
122
+                      </div>
123
+                    }
99
                   </div>
124
                   </div>
100
 
125
 
126
+                  {/*
127
+                  // @TODO validate with DA that this checkbox is useless since the backend handle everything
101
                   <div className='memberlist__form__member__create'>
128
                   <div className='memberlist__form__member__create'>
102
                     <div className='memberlist__form__member__create__checkbox mr-3'>
129
                     <div className='memberlist__form__member__create__checkbox mr-3'>
103
                       <Checkbox
130
                       <Checkbox
111
                       {props.t('Create an account')}
138
                       {props.t('Create an account')}
112
                     </div>
139
                     </div>
113
                   </div>
140
                   </div>
141
+                  */}
114
                 </div>
142
                 </div>
115
 
143
 
116
                 <div className='memberlist__form__role'>
144
                 <div className='memberlist__form__role'>
120
 
148
 
121
                   <ul className='memberlist__form__role__list'>
149
                   <ul className='memberlist__form__role__list'>
122
                     {props.roleList.map(r =>
150
                     {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}`} />
151
+                      <li key={r.slug}>
152
+                        <label className='memberlist__form__role__list__item' htmlFor={r.slug}>
153
+                          <div className='item__radiobtn mr-3'>
154
+                            <input
155
+                              id={r.slug}
156
+                              type='radio'
157
+                              name='role'
158
+                              value={r.slug}
159
+                              checked={r.slug === props.role}
160
+                              onChange={() => props.onChangeRole(r.slug)}
161
+                            />
131
                           </div>
162
                           </div>
132
 
163
 
133
-                          <div className='item__text__name'>
134
-                            {r.label}
164
+                          <div className='item__text'>
165
+                            <div className='item__text__icon mr-2' style={{color: r.hexcolor}}>
166
+                              <i className={`fa fa-${r.faIcon}`} />
167
+                            </div>
168
+
169
+                            <div className='item__text__name'>
170
+                              {r.label}
171
+                            </div>
135
                           </div>
172
                           </div>
136
-                        </div>
173
+                        </label>
137
                       </li>
174
                       </li>
138
                     )}
175
                     )}
139
 
176
 
141
                 </div>
178
                 </div>
142
 
179
 
143
                 <div className='memberlist__form__submitbtn'>
180
                 <div className='memberlist__form__submitbtn'>
144
-                  <button className='btn btn-outline-primary'>
181
+                  <button className='btn btn-outline-primary' onClick={this.handleClickBtnValidate}>
145
                     {props.t('Validate')}
182
                     {props.t('Validate')}
146
                   </button>
183
                   </button>
147
                 </div>
184
                 </div>
148
-              </form>
185
+              </div>
149
             )
186
             )
150
           }
187
           }
151
         </div>
188
         </div>

+ 56 - 1
frontend/src/component/Dashboard/MemberList.styl View File

1
+@import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
+
1
 .memberlist
3
 .memberlist
2
   margin 0 0 50px 0
4
   margin 0 0 50px 0
3
   width 35%
5
   width 35%
76
         cursor pointer
78
         cursor pointer
77
     &__member
79
     &__member
78
       &__name
80
       &__name
81
+        position relative
82
+        margin 0 0 20px 0
79
         .name__label
83
         .name__label
80
           margin 30px 0 20px 0
84
           margin 30px 0 20px 0
81
           label()
85
           label()
86
+        .autocomplete
87
+          position absolute
88
+          min-width 300px
89
+          background-color off-white
90
+          border-radius 10px
91
+          border-width 1px
92
+          border-style solid
93
+          &__item
94
+            display flex
95
+            align-items center
96
+            cursor pointer
97
+            padding 5px 8px
98
+            &:first-child
99
+              border-top-left-radius 10px
100
+              border-top-right-radius 10px
101
+            &:last-child
102
+              border-bottom-left-radius 10px
103
+              border-bottom-right-radius 10px
104
+            &__avatar
105
+              width 45px
106
+              height 45px
107
+              border-radius 50%
108
+              border-width 1px
109
+              border-style solid
110
+            &__name
111
+              margin-left 15px
82
       .name__input
112
       .name__input
83
-        margin-bottom 20px
84
         border 1px solid grey
113
         border 1px solid grey
85
         border-radius 10px
114
         border-radius 10px
86
         padding 10px
115
         padding 10px
106
           display flex
135
           display flex
107
           align-items center
136
           align-items center
108
           margin 10px 25px 10px 0
137
           margin 10px 25px 10px 0
138
+          cursor pointer
109
           .item
139
           .item
110
             &__text
140
             &__text
111
               display flex
141
               display flex
115
       & > button
145
       & > button
116
         padding 8px 30px
146
         padding 8px 30px
117
         cursor pointer
147
         cursor pointer
148
+
149
+@media (min-width min-sm) and (max-width: max-lg)
150
+  .memberlist
151
+    width 50%
152
+
153
+@media (min-width: min-sm) and (max-width: max-sm)
154
+  .memberlist
155
+    margin 50px 0
156
+    width 90%
157
+
158
+@media (max-width: max-xs)
159
+  .memberlist
160
+    margin 50px 0
161
+    width 100%
162
+    &__title
163
+      margin-left 10px
164
+    &__wrapper
165
+      height auto
166
+    &__list
167
+      height auto
168
+      overflow-Y visible
169
+      &__item:nth-last-child(1)
170
+        border-bottom 1px solid grey
171
+    &__btnadd
172
+      border-top 0

+ 66 - 0
frontend/src/component/Dashboard/MoreInfo.jsx View File

1
+import React from 'react'
2
+
3
+require('./MoreInfo.styl')
4
+
5
+export const MoreInfo = props =>
6
+  <div className='moreinfo'>
7
+    <div className='moreinfo__webdav genericBtnInfoDashboard'>
8
+      <div
9
+        className='moreinfo__webdav__btn genericBtnInfoDashboard__btn'
10
+        onClick={props.onClickToggleWebdav}
11
+      >
12
+        <div className='moreinfo__webdav__btn__icon genericBtnInfoDashboard__btn__icon'>
13
+          <i className='fa fa-windows' />
14
+        </div>
15
+
16
+        <div className='moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
17
+          {props.t('Implement Tracim in your explorer')}
18
+        </div>
19
+      </div>
20
+
21
+      {props.displayWebdavBtn === true &&
22
+      <div className='moreinfo__webdav__information genericBtnInfoDashboard__info'>
23
+        <div className='moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
24
+          {props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
25
+        </div>
26
+
27
+        <div className='moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
28
+          http://algoo.trac.im/webdav/
29
+        </div>
30
+      </div>
31
+      }
32
+    </div>
33
+
34
+    <div className='moreinfo__calendar genericBtnInfoDashboard'>
35
+      <div className='moreinfo__calendar__wrapperBtn'>
36
+        <div
37
+          className='moreinfo__calendar__btn genericBtnInfoDashboard__btn'
38
+          onClick={props.onClickToggleCalendar}
39
+        >
40
+          <div className='moreinfo__calendar__btn__icon genericBtnInfoDashboard__btn__icon'>
41
+            <i className='fa fa-calendar' />
42
+          </div>
43
+
44
+          <div className='moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text d-flex align-self-center'>
45
+            {props.t('Workspace Calendar')}
46
+          </div>
47
+        </div>
48
+      </div>
49
+
50
+      <div className='moreinfo__calendar__wrapperText'>
51
+        {props.displayCalendarBtn === true &&
52
+        <div className='moreinfo__calendar__information genericBtnInfoDashboard__info'>
53
+          <div className='moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
54
+            {props.t('Each workspace has its own calendar.')}
55
+          </div>
56
+
57
+          <div className='moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
58
+            http://algoo.trac.im/calendar/
59
+          </div>
60
+        </div>
61
+        }
62
+      </div>
63
+    </div>
64
+  </div>
65
+
66
+export default MoreInfo

+ 30 - 0
frontend/src/component/Dashboard/MoreInfo.styl View File

1
+.moreinfo
2
+  display flex
3
+  justify-content space-between
4
+  flexwrap wrap
5
+  &__webdav
6
+    margin 0 15px 40px 0
7
+    &__btn
8
+      width 300px
9
+    &__information
10
+      width 300px
11
+  &__calendar
12
+    display none
13
+    margin-bottom 100px
14
+    &__wrapperBtn
15
+      margin-right 290px
16
+    &__btn
17
+      width 300px
18
+    &__information
19
+      width 300px
20
+
21
+@media (min-width: min-sm) and (max-width: max-sm)
22
+  .moreinfo__webdav__information
23
+    width 500px
24
+
25
+@media (max-width: max-xs)
26
+  .moreinfo
27
+    &__webdav
28
+      margin-left 10px
29
+      &__information
30
+        width 350px

+ 19 - 10
frontend/src/component/Dashboard/RecentActivity.jsx View File

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

+ 67 - 0
frontend/src/component/Dashboard/RecentActivity.styl View File

1
+@import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
+
3
+.activity
4
+  margin 0 35px 50px 0
5
+  width 60%
6
+  &__wrapper
7
+    border 1px solid grey
8
+    height 480px
9
+    overflow-y scroll
10
+  &__header
11
+    display flex
12
+    justify-content space-between
13
+    align-items center
14
+    margin-bottom 20px
15
+    height 44px
16
+    &__allread
17
+      padding 10px 25px
18
+      font-size 18px
19
+      cursor pointer
20
+  &__workspace
21
+    display flex
22
+    align-items center
23
+    border-bottom 1px solid grey
24
+    padding 15px
25
+    cursor pointer
26
+    &:hover
27
+      background-color fourthColor
28
+    &:nth-child(even)
29
+      background-color grey-hover
30
+      &:hover
31
+        background-color fourthColor
32
+    &__icon
33
+      margin 0 25px
34
+      font-size 25px
35
+    &__name
36
+      font-size 18px
37
+      font-weight 500
38
+      span
39
+        font-weight 400
40
+  &__more
41
+    &__btn
42
+      margin 15px
43
+      padding 10px 25px
44
+      cursor pointer
45
+
46
+@media (min-width min-sm) and (max-width: max-lg)
47
+  .activity
48
+    width 100%
49
+
50
+@media (min-width: min-md) and (max-width: max-md)
51
+  .activity
52
+    margin 25px 15px 25px 0
53
+
54
+@media (min-width: min-sm) and (max-width: max-sm)
55
+  .activity
56
+    margin 25px 15px 25px 0
57
+
58
+@media (max-width: max-xs)
59
+  .activity
60
+    margin 25px 0
61
+    width 100%
62
+    &__header
63
+      display block
64
+      height auto
65
+      margin 0 15px 20px 15px
66
+      &__title
67
+        margin-bottom 20px

+ 75 - 0
frontend/src/component/Dashboard/UserStatus.jsx View File

1
+import React from 'react'
2
+import {ROLE} from '../../helper.js'
3
+
4
+require('./UserStatus.styl')
5
+
6
+// @TODO Côme - 2018/08/07 - since api yet doesn't handle notification subscriptions, this file is WIP
7
+export const UserStatus = props =>
8
+  <div className='userstatus'>
9
+    <div className='userstatus__role'>
10
+      <div className='userstatus__role__msg'>
11
+        {props.t('Hi {{name}} ! Currently, you are ', {name: props.user.public_name})}
12
+      </div>
13
+
14
+      {(() => {
15
+        const myself = props.curWs.memberList.find(m => m.id === props.user.user_id)
16
+        if (myself === undefined) return
17
+
18
+        const myRole = ROLE.find(r => r.slug === myself.role)
19
+
20
+        return (
21
+          <div className='userstatus__role__definition'>
22
+            <div className='userstatus__role__definition__icon'>
23
+              <i className={`fa fa-${myRole.faIcon}`} />
24
+            </div>
25
+
26
+            <div className='userstatus__role__definition__text'>
27
+              {myRole.label}
28
+            </div>
29
+          </div>
30
+        )
31
+      })()}
32
+    </div>
33
+
34
+    <div className='userstatus__notification'>
35
+      <div className='userstatus__notification__text'>
36
+        {props.t("You have subscribed to this workspace's notifications")} (NYI)
37
+      </div>
38
+
39
+      {props.displayNotifBtn
40
+        ? (
41
+          <div className='userstatus__notification__subscribe dropdown'>
42
+            <button
43
+              className='userstatus__notification__subscribe__btn btn btn-outline-primary dropdown-toggle'
44
+              type='button'
45
+              id='dropdownMenuButton'
46
+              data-toggle='dropdown'
47
+              aria-haspopup='true'
48
+              aria-expanded='false'
49
+            >
50
+              {props.t('subscribed')}
51
+            </button>
52
+
53
+            <div className='userstatus__notification__subscribe__submenu dropdown-menu'>
54
+              <div className='userstatus__notification__subscribe__submenu__item dropdown-item'>
55
+                {props.t('subscriber')}
56
+              </div>
57
+              <div className='userstatus__notification__subscribe__submenu__item dropdown-item dropdown-item'>
58
+                {props.t('unsubscribed')}
59
+              </div>
60
+            </div>
61
+          </div>
62
+        )
63
+        : (
64
+          <div
65
+            className='userstatus__notification__btn btn btn-outline-primary'
66
+            onClick={props.onClickToggleNotifBtn}
67
+          >
68
+            {props.t('Change your status')}
69
+          </div>
70
+        )
71
+      }
72
+    </div>
73
+  </div>
74
+
75
+export default UserStatus

+ 30 - 0
frontend/src/component/Dashboard/UserStatus.styl View File

1
+@import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
+
3
+.userstatus
4
+  width 35%
5
+  &__role
6
+    margin 20px 0
7
+    font-size 18px
8
+    &__msg
9
+      margin-right 15px
10
+    &__definition
11
+      display flex
12
+      &__icon
13
+        margin-right 15px
14
+        color gestionnaire
15
+  &__notification
16
+    font-size 18px
17
+    &__btn
18
+      margin 20px 0
19
+      border 1px solid thirdColor
20
+      padding 10px 15px
21
+      cursor pointer
22
+    &__subscribe
23
+      &__btn
24
+        margin 20px 0
25
+        border 1px solid thirdColor
26
+        padding 10px 15px
27
+      &__submenu
28
+        padding 0
29
+        &__item
30
+          padding 10px

+ 8 - 8
frontend/src/component/Sidebar/WorkspaceListItem.jsx View File

7
 
7
 
8
 const WorkspaceListItem = props => {
8
 const WorkspaceListItem = props => {
9
   return (
9
   return (
10
-    <li className='sidebar__navigation__workspace__item'>
10
+    <li className='sidebar__content__navigation__workspace__item'>
11
       <div
11
       <div
12
-        className='sidebar__navigation__workspace__item__wrapper primaryColorBg primaryColorBgDarkenHover primaryColorBorder'
12
+        className='sidebar__content__navigation__workspace__item__wrapper primaryColorBg primaryColorBgDarkenHover primaryColorBorder'
13
         onClick={props.onClickTitle}
13
         onClick={props.onClickTitle}
14
       >
14
       >
15
-        <div className='sidebar__navigation__workspace__item__number'>
15
+        <div className='sidebar__content__navigation__workspace__item__number'>
16
           {props.label.substring(0, 2).toUpperCase()}
16
           {props.label.substring(0, 2).toUpperCase()}
17
         </div>
17
         </div>
18
 
18
 
19
-        <div className='sidebar__navigation__workspace__item__name' title={props.label}>
19
+        <div className='sidebar__content__navigation__workspace__item__name' title={props.label}>
20
           {props.label}
20
           {props.label}
21
         </div>
21
         </div>
22
 
22
 
23
-        <div className='sidebar__navigation__workspace__item__icon'>
23
+        <div className='sidebar__content__navigation__workspace__item__icon'>
24
           <i className={classnames(props.isOpenInSidebar ? 'fa fa-chevron-up' : 'fa fa-chevron-down')} />
24
           <i className={classnames(props.isOpenInSidebar ? 'fa fa-chevron-up' : 'fa fa-chevron-down')} />
25
         </div>
25
         </div>
26
       </div>
26
       </div>
27
 
27
 
28
       <AnimateHeight duration={500} height={props.isOpenInSidebar ? 'auto' : 0}>
28
       <AnimateHeight duration={500} height={props.isOpenInSidebar ? 'auto' : 0}>
29
         <ul
29
         <ul
30
-          className='sidebar__navigation__workspace__item__submenu'
30
+          className='sidebar__content__navigation__workspace__item__submenu'
31
           id={`sidebarSubMenu_${props.number}`}
31
           id={`sidebarSubMenu_${props.number}`}
32
         >
32
         >
33
           { props.allowedApp.map(aa =>
33
           { props.allowedApp.map(aa =>
37
             >
37
             >
38
               <Link to={aa.route}>
38
               <Link to={aa.route}>
39
                 <div className={classnames(
39
                 <div className={classnames(
40
-                  'sidebar__navigation__workspace__item__submenu__dropdown primaryColorBgLighten primaryColorBgHover primaryColorBorderDarken',
40
+                  'sidebar__content__navigation__workspace__item__submenu__dropdown primaryColorBgLighten primaryColorBgHover primaryColorBorderDarken',
41
                   {'activeFilter': props.activeFilterList.includes(aa.slug)}
41
                   {'activeFilter': props.activeFilterList.includes(aa.slug)}
42
                 )}>
42
                 )}>
43
                   <div className='dropdown__icon'>
43
                   <div className='dropdown__icon'>
44
                     <i className={classnames(`fa fa-${aa.faIcon}`)} style={{backgroudColor: aa.hexcolor}} />
44
                     <i className={classnames(`fa fa-${aa.faIcon}`)} style={{backgroudColor: aa.hexcolor}} />
45
                   </div>
45
                   </div>
46
 
46
 
47
-                  <div className='sidebar__navigation__workspace__item__submenu__dropdown__showdropdown'>
47
+                  <div className='sidebar__content__navigation__workspace__item__submenu__dropdown__showdropdown'>
48
                     <div className='dropdown__title' id='navbarDropdown'>
48
                     <div className='dropdown__title' id='navbarDropdown'>
49
                       <div className='dropdown__title__text'>
49
                       <div className='dropdown__title__text'>
50
                         {aa.label/* [props.lang.id] */}
50
                         {aa.label/* [props.lang.id] */}

+ 1 - 1
frontend/src/component/common/Input/SubDropdownCreateButton.jsx View File

15
                 style={{color: app.hexcolor}}
15
                 style={{color: app.hexcolor}}
16
               />
16
               />
17
             </div>
17
             </div>
18
-            <div className='subdropdown__link__folder__text'>
18
+            <div className={`subdropdown__link__${app.slug}__text`}>
19
               {app.creationLabel}
19
               {app.creationLabel}
20
             </div>
20
             </div>
21
           </div>
21
           </div>

+ 75 - 62
frontend/src/container/AdminWorkspacePage.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
-import PropTypes from 'prop-types'
3
 import Sidebar from './Sidebar.jsx'
2
 import Sidebar from './Sidebar.jsx'
4
 import {
3
 import {
5
   Delimiter,
4
   Delimiter,
27
               This page informs all workspaces of the instances
26
               This page informs all workspaces of the instances
28
             </div>
27
             </div>
29
 
28
 
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>
29
+            { /*
30
+              Alexi Cauvin 08/08/2018 => desactivate create workspace button due to redundancy
31
+
32
+              <div className='adminWorkspacePage__createworkspace'>
33
+                <button className='adminWorkspacePage__createworkspace__btncreate btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover'>
34
+                  {this.props.t('Create a workspace')}
35
+                </button>
36
+              </div>
37
+            */ }
35
 
38
 
36
             <Delimiter customClass={'adminWorkspacePage__delimiter'} />
39
             <Delimiter customClass={'adminWorkspacePage__delimiter'} />
37
 
40
 
38
             <div className='adminWorkspacePage__workspaceTable'>
41
             <div className='adminWorkspacePage__workspaceTable'>
39
 
42
 
40
-              <table class='table'>
43
+              <table className='table'>
41
                 <thead>
44
                 <thead>
42
                   <tr>
45
                   <tr>
43
-                    <th scope='col'># ID</th>
46
+                    <th scope='col'>ID</th>
44
                     <th scope='col'>Workspace</th>
47
                     <th scope='col'>Workspace</th>
45
                     <th scope='col'>Description</th>
48
                     <th scope='col'>Description</th>
46
-                    <th scope='col'>Users</th>
47
-                    <th scope='col'>Calendar</th>
49
+                    <th scope='col'>Member's number</th>
50
+                    { /*
51
+                      <th scope='col'>Calendar</th>
52
+                    */ }
48
                   </tr>
53
                   </tr>
49
                 </thead>
54
                 </thead>
50
                 <tbody>
55
                 <tbody>
51
                   <tr>
56
                   <tr>
52
-                    <th>#1</th>
57
+                    <th>1</th>
53
                     <td>Design v_2</td>
58
                     <td>Design v_2</td>
54
                     <td>Workspace about tracim v2 design</td>
59
                     <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>
60
+                    <td>8 members</td>
61
+                    { /*
62
+                      <td className='d-flex align-items-center flex-wrap'>
63
+                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
64
+                          <i className='fa fa-fw fa-check-square-o' />
65
+                        </div>
66
+                        Enable
67
+                      </td>
68
+                    */ }
62
                   </tr>
69
                   </tr>
63
                   <tr>
70
                   <tr>
64
-                    <th>#2</th>
71
+                    <th>2</th>
65
                     <td>New features</td>
72
                     <td>New features</td>
66
                     <td>Add a new features : Annotated files</td>
73
                     <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
+                    <td>5 members</td>
75
+                    { /*
76
+                      <td className='d-flex align-items-center flex-wrap'>
77
+                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
78
+                          <i className='fa fa-fw fa-square-o' />
79
+                        </div>
80
+                        Disable
81
+                      </td>
82
+                    */ }
74
                   </tr>
83
                   </tr>
75
                   <tr>
84
                   <tr>
76
-                    <th>#3</th>
85
+                    <th>3</th>
77
                     <td>Fix Backend</td>
86
                     <td>Fix Backend</td>
78
                     <td>workspace referring to multiple issues on the backend </td>
87
                     <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>
88
+                    <td>3 members</td>
89
+                    { /*
90
+                      <td className='d-flex align-items-center flex-wrap'>
91
+                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
92
+                          <i className='fa fa-fw fa-check-square-o' />
93
+                        </div>
94
+                        Enable
95
+                      </td>
96
+                    */ }
86
                   </tr>
97
                   </tr>
87
                   <tr>
98
                   <tr>
88
-                    <th>#4</th>
99
+                    <th>4</th>
89
                     <td>Design v_2</td>
100
                     <td>Design v_2</td>
90
                     <td>Workspace about tracim v2 design</td>
101
                     <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>
102
+                    <td>8 members</td>
103
+                    { /*
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
+                    */ }
98
                   </tr>
111
                   </tr>
99
                   <tr>
112
                   <tr>
100
-                    <th>#5</th>
113
+                    <th>5</th>
101
                     <td>New features</td>
114
                     <td>New features</td>
102
                     <td>Add a new features : Annotated files</td>
115
                     <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>
116
+                    <td>5 members</td>
117
+                    { /*
118
+                      <td className='d-flex align-items-center flex-wrap'>
119
+                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
120
+                          <i className='fa fa-fw fa-square-o' />
121
+                        </div>
122
+                        Disable
123
+                      </td>
124
+                    */ }
110
                   </tr>
125
                   </tr>
111
                   <tr>
126
                   <tr>
112
-                    <th>#6</th>
127
+                    <th>6</th>
113
                     <td>Fix Backend</td>
128
                     <td>Fix Backend</td>
114
                     <td>workspace referring to multiple issues on the backend </td>
129
                     <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>
130
+                    <td>3 members</td>
131
+                    { /*
132
+                      <td className='d-flex align-items-center flex-wrap'>
133
+                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
134
+                          <i className='fa fa-fw fa-check-square-o' />
135
+                        </div>
136
+                        Enable
137
+                      </td>
138
+                    */ }
122
                   </tr>
139
                   </tr>
123
                 </tbody>
140
                 </tbody>
124
               </table>
141
               </table>
131
   }
148
   }
132
 }
149
 }
133
 
150
 
134
-AdminWorkspacePage.propTypes = {
135
-  availableApp: PropTypes.array.isRequired
136
-}
137
-
138
 export default translate()(AdminWorkspacePage)
151
 export default translate()(AdminWorkspacePage)

+ 3 - 3
frontend/src/container/AppFullscreenManager.jsx View File

9
   constructor (props) {
9
   constructor (props) {
10
     super(props)
10
     super(props)
11
     this.state = {
11
     this.state = {
12
-      AmIMounted: false
12
+      isMounted: false
13
     }
13
     }
14
   }
14
   }
15
 
15
 
16
-  componentDidMount = () => this.setState({AmIMounted: true})
16
+  componentDidMount = () => this.setState({isMounted: true})
17
 
17
 
18
   render () {
18
   render () {
19
     const { props } = this
19
     const { props } = this
22
       <div className='AppFullScreenManager'>
22
       <div className='AppFullScreenManager'>
23
         <div id='appFullscreenContainer' />
23
         <div id='appFullscreenContainer' />
24
 
24
 
25
-        {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.isMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
26
           <div className='emptyDiForRoute'>
26
           <div className='emptyDiForRoute'>
27
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
27
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
28
               props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})
28
               props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})

+ 144 - 157
frontend/src/container/Dashboard.jsx View File

10
   getWorkspaceDetail,
10
   getWorkspaceDetail,
11
   getWorkspaceMemberList,
11
   getWorkspaceMemberList,
12
   getWorkspaceRecentActivityList,
12
   getWorkspaceRecentActivityList,
13
-  getWorkspaceReadStatusList
13
+  getWorkspaceReadStatusList,
14
+  getUserKnownMember,
15
+  postWorkspaceMember,
16
+  putUserWorkspaceRead
14
 } from '../action-creator.async.js'
17
 } from '../action-creator.async.js'
15
 import {
18
 import {
16
   newFlashMessage,
19
   newFlashMessage,
17
   setWorkspaceDetail,
20
   setWorkspaceDetail,
18
   setWorkspaceMemberList,
21
   setWorkspaceMemberList,
19
   setWorkspaceRecentActivityList,
22
   setWorkspaceRecentActivityList,
23
+  setWorkspaceRecentActivityForUserList,
20
   setWorkspaceReadStatusList
24
   setWorkspaceReadStatusList
21
 } from '../action-creator.sync.js'
25
 } from '../action-creator.sync.js'
22
-import { ROLE } from '../helper.js'
26
+import { ROLE, PAGE } from '../helper.js'
27
+import UserStatus from '../component/Dashboard/UserStatus.jsx'
23
 import ContentTypeBtn from '../component/Dashboard/ContentTypeBtn.jsx'
28
 import ContentTypeBtn from '../component/Dashboard/ContentTypeBtn.jsx'
24
 import RecentActivity from '../component/Dashboard/RecentActivity.jsx'
29
 import RecentActivity from '../component/Dashboard/RecentActivity.jsx'
25
 import MemberList from '../component/Dashboard/MemberList.jsx'
30
 import MemberList from '../component/Dashboard/MemberList.jsx'
31
+import MoreInfo from '../component/Dashboard/MoreInfo.jsx'
26
 
32
 
27
 class Dashboard extends React.Component {
33
 class Dashboard extends React.Component {
28
   constructor (props) {
34
   constructor (props) {
29
     super(props)
35
     super(props)
30
     this.state = {
36
     this.state = {
31
       workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt everytime
37
       workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null, // this is used to avoid handling the parseInt everytime
38
+      newMember: {
39
+        id: '',
40
+        avatarUrl: '',
41
+        nameOrEmail: '',
42
+        // createAccount: false, // @TODO ask DA about this checkbox if it is still usefull (since backend handles it all)
43
+        role: ''
44
+      },
45
+      searchedKnownMemberList: [],
32
       displayNewMemberDashboard: false,
46
       displayNewMemberDashboard: false,
33
       displayNotifBtn: false,
47
       displayNotifBtn: false,
34
       displayWebdavBtn: false,
48
       displayWebdavBtn: false,
41
 
55
 
42
     const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
56
     const fetchWorkspaceDetail = await props.dispatch(getWorkspaceDetail(props.user, state.workspaceIdInUrl))
43
     switch (fetchWorkspaceDetail.status) {
57
     switch (fetchWorkspaceDetail.status) {
44
-      case 200:
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
58
+      case 200: props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
59
+      default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('workspace detail')}`, 'warning')); break
48
     }
60
     }
61
+    this.loadMemberList()
62
+    this.loadRecentActivity()
63
+  }
64
+
65
+  loadMemberList = async () => {
66
+    const { props, state } = this
49
 
67
 
50
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
68
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
51
     switch (fetchWorkspaceMemberList.status) {
69
     switch (fetchWorkspaceMemberList.status) {
52
-      case 200:
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
70
+      case 200: props.dispatch(setWorkspaceMemberList(fetchWorkspaceMemberList.json)); break
71
+      default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('member list')}`, 'warning')); break
56
     }
72
     }
73
+  }
74
+
75
+  loadRecentActivity = async () => {
76
+    const { props, state } = this
57
 
77
 
58
     const fetchWorkspaceRecentActivityList = await props.dispatch(getWorkspaceRecentActivityList(props.user, state.workspaceIdInUrl))
78
     const fetchWorkspaceRecentActivityList = await props.dispatch(getWorkspaceRecentActivityList(props.user, state.workspaceIdInUrl))
79
+    const fetchWorkspaceReadStatusList = await props.dispatch(getWorkspaceReadStatusList(props.user, state.workspaceIdInUrl))
80
+
59
     switch (fetchWorkspaceRecentActivityList.status) {
81
     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
82
+      case 200: props.dispatch(setWorkspaceRecentActivityList(fetchWorkspaceRecentActivityList.json)); break
83
+      default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('recent activity list')}`, 'warning')); break
64
     }
84
     }
65
 
85
 
66
-    const fetchWorkspaceReadStatusList = await props.dispatch(getWorkspaceReadStatusList(props.user, state.workspaceIdInUrl))
67
     switch (fetchWorkspaceReadStatusList.status) {
86
     switch (fetchWorkspaceReadStatusList.status) {
87
+      case 200: props.dispatch(setWorkspaceReadStatusList(fetchWorkspaceReadStatusList.json)); break
88
+      default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('read status list')}`, 'warning')); break
89
+    }
90
+
91
+    const readStatusForUserList = fetchWorkspaceReadStatusList.json.filter(c => c.read_by_user).map(c => c.content_id)
92
+    const recentActivityForUserList = fetchWorkspaceRecentActivityList.json.filter(content => !readStatusForUserList.includes(content.content_id))
93
+
94
+    props.dispatch(setWorkspaceRecentActivityForUserList(recentActivityForUserList))
95
+  }
96
+
97
+  handleToggleNewMemberDashboard = () => this.setState(prevState => ({displayNewMemberDashboard: !prevState.displayNewMemberDashboard}))
98
+
99
+  handleToggleNotifBtn = () => this.setState(prevState => ({displayNotifBtn: !prevState.displayNotifBtn}))
100
+
101
+  handleToggleWebdavBtn = () => this.setState(prevState => ({displayWebdavBtn: !prevState.displayWebdavBtn}))
102
+
103
+  handleToggleCalendarBtn = () => this.setState(prevState => ({displayCalendarBtn: !prevState.displayCalendarBtn}))
104
+
105
+  handleClickRecentContent = (idContent, typeContent) => this.props.history.push(PAGE.WORKSPACE.CONTENT(this.props.curWs.id, typeContent, idContent))
106
+
107
+  handleClickMarkRecentActivityAsRead = async () => {
108
+    const { props } = this
109
+    const fetchUserWorkspaceAllRead = await props.dispatch(putUserWorkspaceRead(props.user, props.curWs.id))
110
+    switch (fetchUserWorkspaceAllRead.status) {
111
+      case 204: this.loadRecentActivity(); break
112
+      default: props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching "mark all as read"')}`, 'warning')); break
113
+    }
114
+  }
115
+
116
+  handleClickSeeMore = async () => {
117
+    console.log('nyi')
118
+  }
119
+
120
+  handleSearchUser = async userNameToSearch => {
121
+    const { props } = this
122
+    const fetchUserKnownMemberList = await props.dispatch(getUserKnownMember(props.user, userNameToSearch))
123
+    switch (fetchUserKnownMemberList.status) {
68
       case 200:
124
       case 200:
69
-        props.dispatch(setWorkspaceReadStatusList(fetchWorkspaceReadStatusList.json)); break
125
+        this.setState({searchedKnownMemberList: fetchUserKnownMemberList.json}); break
70
       default:
126
       default:
71
-        props.dispatch(newFlashMessage(props.t('An error has happened while fetching read status list'), 'warning')); break
127
+        props.dispatch(newFlashMessage(`${props.t('An error has happened while fetching')} ${props.t('known members list')}`, 'warning')); break
72
     }
128
     }
73
   }
129
   }
74
 
130
 
75
-  handleToggleNewMemberDashboard = () => this.setState(prevState => ({
76
-    displayNewMemberDashboard: !prevState.displayNewMemberDashboard
77
-  }))
131
+  handleChangeNewMemberNameOrEmail = newNameOrEmail => {
132
+    if (newNameOrEmail.length >= 2) this.handleSearchUser(newNameOrEmail)
133
+    this.setState(prev => ({newMember: {...prev.newMember, nameOrEmail: newNameOrEmail}}))
134
+  }
135
+
136
+  handleClickKnownMember = knownMember => {
137
+    this.setState(prev => ({
138
+      newMember: {
139
+        ...prev.newMember,
140
+        id: knownMember.user_id,
141
+        nameOrEmail: knownMember.public_name,
142
+        avatarUrl: knownMember.avatar_url
143
+      },
144
+      searchedKnownMemberList: []
145
+    }))
146
+  }
147
+
148
+  // handleChangeNewMemberCreateAccount = newCreateAccount => this.setState(prev => ({newMember: {...prev.newMember, createAccount: newCreateAccount}}))
149
+
150
+  handleChangeNewMemberRole = newRole => this.setState(prev => ({newMember: {...prev.newMember, role: newRole}}))
151
+
152
+  handleClickValidateNewMember = async () => {
153
+    const { props, state } = this
154
+
155
+    if (state.newMember.nameOrEmail === '') {
156
+      props.dispatch(newFlashMessage(props.t('Please set a name or email'), 'warning'))
157
+      return
158
+    }
78
 
159
 
79
-  handleToggleNotifBtn = () => this.setState(prevState => ({
80
-    displayNotifBtn: !prevState.displayNotifBtn
81
-  }))
160
+    if (state.newMember.role === '') {
161
+      props.dispatch(newFlashMessage(props.t('Please set a role'), 'warning'))
162
+      return
163
+    }
82
 
164
 
83
-  handleToggleWebdavBtn = () => this.setState(prevState => ({
84
-    displayWebdavBtn: !prevState.displayWebdavBtn
85
-  }))
165
+    const fetchWorkspaceNewMember = await props.dispatch(postWorkspaceMember(props.user, props.curWs.id, {
166
+      id: state.newMember.id,
167
+      name: state.newMember.nameOrEmail,
168
+      role: state.newMember.role
169
+    }))
86
 
170
 
87
-  handleToggleCalendarBtn = () => this.setState(prevState => ({
88
-    displayCalendarBtn: !prevState.displayCalendarBtn
89
-  }))
171
+    switch (fetchWorkspaceNewMember.status) {
172
+      case 200:
173
+        this.loadMemberList(); break
174
+      default:
175
+        props.dispatch(newFlashMessage(props.t('An error has happened while adding the member'), 'warning')); break
176
+    }
177
+  }
90
 
178
 
91
   render () {
179
   render () {
92
     const { props, state } = this
180
     const { props, state } = this
118
                 </div>
206
                 </div>
119
               </div>
207
               </div>
120
 
208
 
121
-              <div className='dashboard__userstatut'>
122
-                <div className='dashboard__userstatut__role'>
123
-                  <div className='dashboard__userstatut__role__msg'>
124
-                    {props.t(`Hi ! ${props.user.public_name} `)}{props.t('currently, you are ')}
125
-                  </div>
126
-
127
-                  {(() => {
128
-                    const myself = props.curWs.memberList.find(m => m.id === props.user.user_id)
129
-                    if (myself === undefined) return
130
-
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
-                  })()}
145
-                </div>
146
-
147
-                <div className='dashboard__userstatut__notification'>
148
-                  <div className='dashboard__userstatut__notification__text'>
149
-                    {props.t("You have subscribed to this workspace's notifications")} (nyi)
150
-                  </div>
151
-
152
-                  {state.displayNotifBtn
153
-                    ? (
154
-                      <div className='dashboard__userstatut__notification__subscribe dropdown'>
155
-                        <button
156
-                          className='dashboard__userstatut__notification__subscribe__btn btn btn-outline-primary dropdown-toggle'
157
-                          type='button'
158
-                          id='dropdownMenuButton'
159
-                          data-toggle='dropdown'
160
-                          aria-haspopup='true'
161
-                          aria-expanded='false'
162
-                        >
163
-                          {props.t('subscriber')}
164
-                        </button>
165
-
166
-                        <div className='dashboard__userstatut__notification__subscribe__submenu dropdown-menu'>
167
-                          <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item'>
168
-                            {props.t('subscriber')}
169
-                          </div>
170
-                          <div className='dashboard__userstatut__notification__subscribe__submenu__item dropdown-item dropdown-item'>
171
-                            {props.t('unsubscribed')}
172
-                          </div>
173
-                        </div>
174
-                      </div>
175
-                    )
176
-                    : (
177
-                      <div
178
-                        className='dashboard__userstatut__notification__btn btn btn-outline-primary'
179
-                        onClick={this.handleToggleNotifBtn}
180
-                      >
181
-                        {props.t('Change your status')}
182
-                      </div>
183
-                    )
184
-                  }
185
-                </div>
186
-              </div>
209
+              <UserStatus
210
+                user={props.user}
211
+                curWs={props.curWs}
212
+                displayNotifBtn={state.displayNotifBtn}
213
+                onClickToggleNotifBtn={this.handleToggleNotifBtn}
214
+                t={props.t}
215
+              />
187
             </div>
216
             </div>
188
 
217
 
189
             <div className='dashboard__calltoaction justify-content-xl-center'>
218
             <div className='dashboard__calltoaction justify-content-xl-center'>
202
             <div className='dashboard__workspaceInfo'>
231
             <div className='dashboard__workspaceInfo'>
203
               <RecentActivity
232
               <RecentActivity
204
                 customClass='dashboard__activity'
233
                 customClass='dashboard__activity'
205
-                recentActivityFilteredForUser={props.curWs.recentActivityList.filter(content => !props.curWs.contentReadStatusList.includes(content.id))}
234
+                recentActivityFilteredForUser={props.curWs.recentActivityForUserList}
206
                 contentTypeList={props.contentType}
235
                 contentTypeList={props.contentType}
207
-                onClickSeeMore={() => {}}
236
+                onClickRecentContent={this.handleClickRecentContent}
237
+                onClickEverythingAsRead={this.handleClickMarkRecentActivityAsRead}
238
+                onClickSeeMore={this.handleClickSeeMore}
208
                 t={props.t}
239
                 t={props.t}
209
               />
240
               />
210
 
241
 
212
                 customClass='dashboard__memberlist'
243
                 customClass='dashboard__memberlist'
213
                 memberList={props.curWs.memberList}
244
                 memberList={props.curWs.memberList}
214
                 roleList={ROLE}
245
                 roleList={ROLE}
246
+                searchedKnownMemberList={state.searchedKnownMemberList}
247
+                nameOrEmail={state.newMember.nameOrEmail}
248
+                onChangeNameOrEmail={this.handleChangeNewMemberNameOrEmail}
249
+                onClickKnownMember={this.handleClickKnownMember}
250
+                // createAccount={state.newMember.createAccount}
251
+                // onChangeCreateAccount={this.handleChangeNewMemberCreateAccount}
252
+                role={state.newMember.role}
253
+                onChangeRole={this.handleChangeNewMemberRole}
254
+                onClickValidateNewMember={this.handleClickValidateNewMember}
215
                 t={props.t}
255
                 t={props.t}
216
               />
256
               />
217
             </div>
257
             </div>
218
 
258
 
219
-            <div className='dashboard__moreinfo'>
220
-              <div className='dashboard__moreinfo__webdav genericBtnInfoDashboard'>
221
-                <div
222
-                  className='dashboard__moreinfo__webdav__btn genericBtnInfoDashboard__btn'
223
-                  onClick={this.handleToggleWebdavBtn}
224
-                >
225
-                  <div className='dashboard__moreinfo__webdav__btn__icon genericBtnInfoDashboard__btn__icon'>
226
-                    <i className='fa fa-windows' />
227
-                  </div>
228
-
229
-                  <div className='dashboard__moreinfo__webdav__btn__text genericBtnInfoDashboard__btn__text'>
230
-                    {this.props.t('Implement Tracim in your explorer')}
231
-                  </div>
232
-                </div>
233
-                {this.state.displayWebdavBtn === true &&
234
-                <div>
235
-                  <div className='dashboard__moreinfo__webdav__information genericBtnInfoDashboard__info'>
236
-                    <div className='dashboard__moreinfo__webdav__information__text genericBtnInfoDashboard__info__text'>
237
-                      {this.props.t('Find all your documents deposited online directly on your computer via the workstation, without going through the software.')}'
238
-                    </div>
239
-
240
-                    <div className='dashboard__moreinfo__webdav__information__link genericBtnInfoDashboard__info__link'>
241
-                      http://algoo.trac.im/webdav/
242
-                    </div>
243
-                  </div>
244
-                </div>
245
-                }
246
-              </div>
247
-              <div className='dashboard__moreinfo__calendar genericBtnInfoDashboard'>
248
-                <div className='dashboard__moreinfo__calendar__wrapperBtn'>
249
-                  <div
250
-                    className='dashboard__moreinfo__calendar__btn genericBtnInfoDashboard__btn'
251
-                    onClick={this.handleToggleCalendarBtn}
252
-                  >
253
-                    <div className='dashboard__moreinfo__calendar__btn__icon genericBtnInfoDashboard__btn__icon'>
254
-                      <i className='fa fa-calendar' />
255
-                    </div>
256
-
257
-                    <div className='dashboard__moreinfo__calendar__btn__text genericBtnInfoDashboard__btn__text'>
258
-                      {this.props.t('Workspace Calendar')}
259
-                    </div>
260
-                  </div>
261
-                </div>
262
-                <div className='dashboard__moreinfo__calendar__wrapperText'>
263
-                  {this.state.displayCalendarBtn === true &&
264
-                  <div>
265
-                    <div className='dashboard__moreinfo__calendar__information genericBtnInfoDashboard__info'>
266
-                      <div className='dashboard__moreinfo__calendar__information__text genericBtnInfoDashboard__info__text'>
267
-                        {this.props.t('Each workspace has its own calendar.')}
268
-                      </div>
269
-
270
-                      <div className='dashboard__moreinfo__calendar__information__link genericBtnInfoDashboard__info__link'>
271
-                        http://algoo.trac.im/calendar/
272
-                      </div>
273
-                    </div>
274
-                  </div>
275
-                  }
276
-                </div>
277
-              </div>
278
-            </div>
259
+            <MoreInfo
260
+              onClickToggleWebdav={this.handleToggleWebdavBtn}
261
+              displayWebdavBtn={state.displayWebdavBtn}
262
+              onClickToggleCalendar={this.handleToggleCalendarBtn}
263
+              displayCalendarBtn={state.displayCalendarBtn}
264
+              t={props.t}
265
+            />
279
           </PageContent>
266
           </PageContent>
280
         </PageWrapper>
267
         </PageWrapper>
281
       </div>
268
       </div>

+ 5 - 3
frontend/src/container/Header.jsx View File

65
         <nav className='navbar navbar-expand-md navbar-light bg-light'>
65
         <nav className='navbar navbar-expand-md navbar-light bg-light'>
66
           <Logo logoSrc={logoHeader} onClickImg={this.handleClickLogo} />
66
           <Logo logoSrc={logoHeader} onClickImg={this.handleClickLogo} />
67
 
67
 
68
-          <div className='header__breadcrumb d-none d-lg-block ml-4'>
69
-            Dev Tracim - liste des contenus
70
-          </div>
68
+          { /*
69
+            <div className='header__breadcrumb d-none d-lg-block ml-4'>
70
+              Dev Tracim - liste des contenus
71
+            </div>
72
+          */ }
71
 
73
 
72
           <NavbarToggler />
74
           <NavbarToggler />
73
 
75
 

+ 25 - 24
frontend/src/container/Login.jsx View File

102
                         onChange={this.handleChangePassword}
102
                         onChange={this.handleChangePassword}
103
                       />
103
                       />
104
 
104
 
105
-                      <div className='row mt-4 mb-4'>
106
-                        <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6'>
107
-                          {/*
108
-                          <InputCheckbox
109
-                            parentClassName='connection__form__rememberme'
110
-                            customClass=''
111
-                            label='Se souvenir de moi'
112
-                            checked={this.state.inputRememberMe}
113
-                            onChange={this.handleChangeRememberMe}
114
-                          />
115
-                          */}
116
-                        </div>
117
-
118
-                        <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6 text-sm-right'>
105
+                      <div className='row align-items-center mt-4 mb-4'>
106
+
107
+                        {/*
108
+                          <div className='col-12 col-sm-6 col-md-6 col-lg-6 col-xl-6'>
109
+                            <InputCheckbox
110
+                              parentClassName='connection__form__rememberme'
111
+                              customClass=''
112
+                              label='Se souvenir de moi'
113
+                              checked={this.state.inputRememberMe}
114
+                              onChange={this.handleChangeRememberMe}
115
+                            />
116
+                          </div>
117
+                        */}
118
+
119
+                        <div className='col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6'>
119
                           <LoginBtnForgotPw
120
                           <LoginBtnForgotPw
120
                             customClass='connection__form__pwforgot'
121
                             customClass='connection__form__pwforgot'
121
                             label={this.props.t('Forgotten password ?')}
122
                             label={this.props.t('Forgotten password ?')}
122
                           />
123
                           />
123
                         </div>
124
                         </div>
125
+                        <Button
126
+                          htmlType='button'
127
+                          bootstrapType='primary'
128
+                          customClass='connection__form__btnsubmit ml-auto'
129
+                          label={this.props.t('Connection')}
130
+                          onClick={this.handleClickSubmit}
131
+                        />
124
                       </div>
132
                       </div>
125
-
126
-                      <Button
127
-                        htmlType='button'
128
-                        bootstrapType='primary'
129
-                        customClass='connection__form__btnsubmit ml-auto'
130
-                        label={this.props.t('Connection')}
131
-                        onClick={this.handleClickSubmit}
132
-                      />
133
                     </div>
133
                     </div>
134
+
134
                   </CardBody>
135
                   </CardBody>
135
                 </Card>
136
                 </Card>
136
 
137
 
140
           </div>
141
           </div>
141
 
142
 
142
           <footer className='loginpage__footer'>
143
           <footer className='loginpage__footer'>
143
-            <div className='loginpage__footer__text whiteFontColor'>
144
-              copyright © 2013 - 2018 tracim project.
144
+            <div className='loginpage__footer__text d-flex align-items-center flex wrap'>
145
+            copyright © 2013 - 2018 <a href='http://www.tracim.fr/' target='_blank' className='ml-3'>tracim.fr</a>
145
             </div>
146
             </div>
146
           </footer>
147
           </footer>
147
 
148
 

+ 37 - 39
frontend/src/container/Sidebar.jsx View File

106
 
106
 
107
     return (
107
     return (
108
       <div className={classnames('sidebar primaryColorBgDarken', {'sidebarclose': sidebarClose})}>
108
       <div className={classnames('sidebar primaryColorBgDarken', {'sidebarclose': sidebarClose})}>
109
-        <div className='sidebarSticky'>
110
-          <div className='sidebar__expand primaryColorBg whiteColorBorder' onClick={this.handleClickToggleSidebar}>
111
-            <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
112
-          </div>
113
 
109
 
114
-          <div className='sidebar__wrapper'>
115
-
116
-            <nav className='sidebar__navigation'>
117
-              <ul className='sidebar__navigation__workspace'>
118
-                { workspaceList.map(ws =>
119
-                  <WorkspaceListItem
120
-                    idWs={ws.id}
121
-                    label={ws.label}
122
-                    allowedApp={ws.sidebarEntry}
123
-                    lang={activeLang}
124
-                    activeFilterList={ws.id === workspaceIdInUrl ? [qs.parse(this.props.location.search).type] : []}
125
-                    isOpenInSidebar={ws.isOpenInSidebar}
126
-                    onClickTitle={() => this.handleClickWorkspace(ws.id, !ws.isOpenInSidebar)}
127
-                    onClickAllContent={this.handleClickAllContent}
128
-                    // onClickContentFilter={this.handleClickContentFilter}
129
-                    key={ws.id}
130
-                  />
131
-                )}
132
-              </ul>
133
-            </nav>
134
-
135
-            <div className='sidebar__btnnewworkspace'>
136
-              <button
137
-                className='sidebar__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'
138
-                onClick={this.handleClickNewWorkspace}
139
-              >
140
-                {t('Create a workspace')}
141
-              </button>
142
-            </div>
110
+        <div className='sidebar__expand primaryColorBg' onClick={this.handleClickToggleSidebar}>
111
+          <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
112
+        </div>
143
 
113
 
114
+        <div className='sidebar__content'>
115
+          <nav className={classnames('sidebar__content__navigation', {'sidebarclose': sidebarClose})}>
116
+            <ul className='sidebar__content__navigation__workspace'>
117
+              { workspaceList.map(ws =>
118
+                <WorkspaceListItem
119
+                  idWs={ws.id}
120
+                  label={ws.label}
121
+                  allowedApp={ws.sidebarEntry}
122
+                  lang={activeLang}
123
+                  activeFilterList={ws.id === workspaceIdInUrl ? [qs.parse(this.props.location.search).type] : []}
124
+                  isOpenInSidebar={ws.isOpenInSidebar}
125
+                  onClickTitle={() => this.handleClickWorkspace(ws.id, !ws.isOpenInSidebar)}
126
+                  onClickAllContent={this.handleClickAllContent}
127
+                  // onClickContentFilter={this.handleClickContentFilter}
128
+                  key={ws.id}
129
+                />
130
+              )}
131
+            </ul>
132
+          </nav>
133
+
134
+          <div className='sidebar__content__btnnewworkspace'>
135
+            <button
136
+              className='sidebar__content__btnnewworkspace__btn btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover mb-5'
137
+              onClick={this.handleClickNewWorkspace}
138
+            >
139
+              {t('Create a workspace')}
140
+            </button>
144
           </div>
141
           </div>
145
 
142
 
146
-          <div className='sidebar__footer mb-2'>
147
-            <div className='sidebar__footer__text whiteFontColor d-flex align-items-end justify-content-center'>
148
-              Copyright - 2013 - 2018
149
-              <div className='sidebar__footer__text__link'>
150
-                <a href='http://www.tracim.fr/' target='_blank' className='ml-3'>tracim.fr</a>
151
-              </div>
143
+        </div>
144
+
145
+        <div className='sidebar__footer mb-2'>
146
+          <div className='sidebar__footer__text whiteFontColor d-flex align-items-end justify-content-center'>
147
+            Copyright - 2013 - 2018
148
+            <div className='sidebar__footer__text__link'>
149
+              <a href='http://www.tracim.fr/' target='_blank' className='ml-3'>tracim.fr</a>
152
             </div>
150
             </div>
153
           </div>
151
           </div>
154
         </div>
152
         </div>

+ 38 - 25
frontend/src/container/Tracim.jsx View File

70
   render () {
70
   render () {
71
     const { props } = this
71
     const { props } = this
72
 
72
 
73
+    if (props.user.logged === null) return null // @TODO show loader
74
+
75
+    if (props.user.logged === false && props.location.pathname !== '/login') {
76
+      return <Redirect to={{pathname: '/login', state: {from: props.location}}} />
77
+    }
78
+
73
     return (
79
     return (
74
       <div className='tracim'>
80
       <div className='tracim'>
75
         <Header />
81
         <Header />
89
             }
95
             }
90
           }} />
96
           }} />
91
 
97
 
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
98
+          <Route path='/workspaces/:idws?' render={() => // Workspace Router
99
+            <div className='sidebarpagecontainer'>
100
+              <Sidebar />
100
 
101
 
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} />
102
+              <Route exact path={PAGE.WORKSPACE.ROOT} render={() => props.workspaceList.length === 0 // handle '/' and redirect to first workspace
103
+                ? null // @FIXME this needs to be handled in case of new user that has no workspace
104
+                : <Redirect to={{pathname: `/workspaces/${props.workspaceList[0].id}/contents`, state: {from: props.location}}} />
105
+              } />
111
 
106
 
112
-                  <Route path={PAGE.ACCOUNT} component={Account} />
113
-                  <Route path={PAGE.ADMIN.ROOT} component={AppFullscreenManager} />
114
-                </div>
107
+              <Route exact path={`${PAGE.WORKSPACE.ROOT}/:idws`} render={props2 => // handle '/workspaces/:id' and add '/contents'
108
+                <Redirect to={{pathname: `/workspaces/${props2.match.params.idws}/contents`, state: {from: props.location}}} />
115
               } />
109
               } />
116
-            )
117
-            : props.user.logged === false && props.location.pathname !== '/login' &&
118
-              <Redirect to={{pathname: '/login', state: {from: props.location}}} />
119
-          }
110
+
111
+              <Route path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />
112
+              <Route path={PAGE.WORKSPACE.CALENDAR(':idws')} component={() => <div><br /><br /><br /><br />NYI</div>} />
113
+              <Route path={PAGE.WORKSPACE.CONTENT(':idws', ':type', ':idcts')} component={WorkspaceContent} />
114
+              <Route exact path={PAGE.WORKSPACE.CONTENT_LIST(':idws')} component={WorkspaceContent} />
115
+            </div>
116
+          } />
117
+
118
+          <Route path={PAGE.ACCOUNT} render={() =>
119
+            <div className='sidebarpagecontainer'>
120
+              <Sidebar />
121
+              <Account />
122
+            </div>
123
+          } />
124
+
125
+          <Route path={PAGE.ADMIN.ROOT} render={() =>
126
+            <div className='sidebarpagecontainer'>
127
+              <Sidebar />
128
+              <AppFullscreenManager />
129
+            </div>
130
+          } />
131
+
132
+          <Route path='/admin_temp/workspace' component={AdminWorkspacePage} />
120
 
133
 
121
           <Route path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
134
           <Route path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
122
 
135
 

+ 5 - 2
frontend/src/css/AdminWorkspacePage.styl View File

1
+.table th
2
+  vertical-align middle
3
+
1
 .adminWorkspacePage
4
 .adminWorkspacePage
2
   &__createworkspace
5
   &__createworkspace
3
     &__btncreate
6
     &__btncreate
4
       margin 25px 15px
7
       margin 25px 15px
5
   &__description
8
   &__description
6
-    margin 25px 15px
9
+    margin 25px 0
7
     font-size 20px
10
     font-size 20px
8
   &__delimiter
11
   &__delimiter
9
-    margin 25px auto 65px auto
12
+    margin 65px auto
10
   &__workspaceTable
13
   &__workspaceTable
11
     margin 25px 15px
14
     margin 25px 15px

+ 6 - 163
frontend/src/css/Dashboard.styl View File

1
+@import "../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
+
1
 flexwrap()
3
 flexwrap()
2
   display flex
4
   display flex
3
   flex-wrap wrap
5
   flex-wrap wrap
4
 
6
 
5
-btnNotification()
6
-  margin 20px 0
7
-  border 1px solid thirdColor
8
-  padding 10px 15px
9
-
10
-hoverfocus()
11
-  background-color thirdColor
12
-  color white
13
-
14
-bgandcolor()
15
-  background-color transparent
16
-  color thirdColor
17
-
18
-label()
19
-  font-weight 500
20
-  font-size 18px
21
-  color thirdColor
22
-
23
-coloricon()
24
-  .fa-gavel
25
-    color responsable
26
-  .fa-graduation-cap
27
-    color gestionnaire
28
-  .fa-pencil
29
-    color contributeur
30
-  .fa-eye
31
-    color lecteur
32
-
33
 .dashboard
7
 .dashboard
34
   width 100%
8
   width 100%
35
   &__header
9
   &__header
55
     &__detail
29
     &__detail
56
       margin-bottom 20px
30
       margin-bottom 20px
57
       font-size 18px
31
       font-size 18px
58
-  &__userstatut
59
-    width 35%
60
-    &__role
61
-      margin 20px 0
62
-      font-size 18px
63
-      &__msg
64
-        margin-right 15px
65
-      &__definition
66
-        display flex
67
-        &__icon
68
-          margin-right 15px
69
-          color gestionnaire
70
-    &__notification
71
-      font-size 18px
72
-      &__btn
73
-        btnNotification()
74
-        cursor pointer
75
-      &__subscribe
76
-        &__btn
77
-          btnNotification()
78
-        &__submenu
79
-          padding 0
80
-          &__item
81
-            padding 10px
82
   &__calltoaction
32
   &__calltoaction
83
     flexwrap()
33
     flexwrap()
84
     margin 100px 0
34
     margin 100px 0
93
           font-size 18px
43
           font-size 18px
94
   &__workspaceInfo
44
   &__workspaceInfo
95
     flexwrap()
45
     flexwrap()
96
-  &__activity
97
-    margin 0 35px 50px 0
98
-    width 60%
99
-    &__wrapper
100
-      border 1px solid grey
101
-      height 480px
102
-      overflow-y scroll
103
-    &__header
104
-      display flex
105
-      justify-content space-between
106
-      align-items center
107
-      margin-bottom 20px
108
-      height 44px
109
-      &__allread
110
-        padding 10px 25px
111
-        font-size 18px
112
-        cursor pointer
113
-    &__workspace
114
-      display flex
115
-      align-items center
116
-      border-bottom 1px solid grey
117
-      padding 15px
118
-      cursor pointer
119
-      &:hover
120
-        background-color fourthColor
121
-      &:nth-child(even)
122
-        background-color grey-hover
123
-        &:hover
124
-          background-color fourthColor
125
-      &__icon
126
-        margin 0 25px
127
-        font-size 25px
128
-      &__name
129
-        font-size 18px
130
-        font-weight 500
131
-        span
132
-          font-weight 400
133
-    &__more
134
-      &__btn
135
-        margin 15px
136
-        padding 10px 25px
137
-        cursor pointer
138
-  &__moreinfo
139
-    display flex
140
-    justify-content space-between
141
-    flexwrap wrap
142
-    &__webdav
143
-      margin 0 15px 40px 0
144
-      &__btn
145
-        width 300px
146
-      &__information
147
-        width 300px
148
-    &__calendar
149
-      margin-bottom 100px
150
-      &__wrapperBtn
151
-        margin-right 290px
152
-      &__btn
153
-        width 300px
154
-      &__information
155
-        width 300px
156
 
46
 
157
 /**** MEDIAQUERIES *****/
47
 /**** MEDIAQUERIES *****/
158
 
48
 
169
       width auto
59
       width auto
170
     &__calltoaction
60
     &__calltoaction
171
       justify-content center
61
       justify-content center
172
-    &__activity
173
-      width 100%
174
-    &__memberlist
175
-      width 50%
176
 
62
 
177
 /**** MEDIA 992px & 1199px ****/
63
 /**** MEDIA 992px & 1199px ****/
178
 
64
 
181
   .dashboard
67
   .dashboard
182
     margin-left 15px
68
     margin-left 15px
183
 
69
 
184
-/**** MEDIA 768px & 991px ****/
185
-
186
-@media (min-width: min-md) and (max-width: max-md)
187
-
188
-  .dashboard
189
-    &__activity
190
-      margin 25px 15px 25px 0
191
-
192
 /**** MEDIA 576px & 767px ****/
70
 /**** MEDIA 576px & 767px ****/
193
 
71
 
194
 @media (min-width: min-sm) and (max-width: max-sm)
72
 @media (min-width: min-sm) and (max-width: max-sm)
196
   .dashboard
74
   .dashboard
197
     &__activity
75
     &__activity
198
       margin 25px 15px 25px 0
76
       margin 25px 15px 25px 0
199
-    &__memberlist
200
-      margin 50px 0
201
-      width 90%
202
-    &__moreinfo__webdav__information
203
-      width 500px
204
 
77
 
205
 /**** MEDIA 575px ****/
78
 /**** MEDIA 575px ****/
206
 
79
 
207
 @media (max-width: max-xs)
80
 @media (max-width: max-xs)
208
 
81
 
209
-  position()
210
-    margin-left 10px
211
-    width auto
212
-
213
   .dashboard
82
   .dashboard
214
     margin-left 0
83
     margin-left 0
215
     &__title
84
     &__title
216
       margin-left 10px
85
       margin-left 10px
217
     &__workspace
86
     &__workspace
218
-      position()
87
+      margin-left 10px
88
+      width auto
219
     &__userstatut
89
     &__userstatut
220
-     position()
90
+      margin-left 10px
91
+      width auto
221
     &__calltoaction
92
     &__calltoaction
222
       justify-content center
93
       justify-content center
223
       &__button
94
       &__button
224
         margin 10px
95
         margin 10px
225
-    &__activity
226
-      margin 25px 0
227
-      width 100%
228
-      &__header
229
-        display block
230
-        height auto
231
-        margin 0 15px 20px 15px
232
-        &__title
233
-          margin-bottom 20px
234
-    &__memberlist
235
-      margin 50px 0
236
-      width 100%
237
-      &__title
238
-        margin-left 10px
239
-      &__wrapper
240
-        height auto
241
-      &__list
242
-        height auto
243
-        overflow-Y visible
244
-        &__item:nth-last-child(1)
245
-          border-bottom 1px solid grey
246
-      &__btnadd
247
-        border-top 0
248
-    &__moreinfo
249
-      &__webdav
250
-        margin-left 10px
251
-        &__information
252
-          width 350px

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

25
       margin-top 15px
25
       margin-top 15px
26
       list-style none
26
       list-style none
27
       &__itemsearch
27
       &__itemsearch
28
+        display none
28
         margin-right 8%
29
         margin-right 8%
29
         width 55%
30
         width 55%
30
         &__search
31
         &__search

+ 8 - 2
frontend/src/css/Login.styl View File

43
         padding-left 45px
43
         padding-left 45px
44
     &__btnsubmit
44
     &__btnsubmit
45
       display block
45
       display block
46
+      margin-right 15px
46
       border none
47
       border none
47
       width 150px
48
       width 150px
48
       background-color green
49
       background-color green
52
       &:focus
53
       &:focus
53
         box-shadow shadow-all-side-green
54
         box-shadow shadow-all-side-green
54
     &__pwforgot
55
     &__pwforgot
55
-      margin-top 3px
56
       cursor pointer
56
       cursor pointer
57
       font-size 13px
57
       font-size 13px
58
       &:hover::after
58
       &:hover::after
59
         position absolute
59
         position absolute
60
         top 20px
60
         top 20px
61
-        right 15px
61
+        left 15px
62
         border-bottom 1px solid darkGrey
62
         border-bottom 1px solid darkGrey
63
         padding-bottom 2px
63
         padding-bottom 2px
64
         content ' '
64
         content ' '
70
     &__text
70
     &__text
71
       width 310px
71
       width 310px
72
       font-size 17px
72
       font-size 17px
73
+      color off-white
74
+      & > a
75
+        color off-white
76
+        font-size 19px
77
+        &:hover
78
+          text-decoration underline
73
 
79
 
74
 @media (min-width: min-lg) and (max-width: max-lg)
80
 @media (min-width: min-lg) and (max-width: max-lg)
75
   .loginpage
81
   .loginpage

+ 95 - 74
frontend/src/css/Sidebar.styl View File

1
-sidebar-width = 280px
1
+sidebar-width = 300px
2
 sidebar-animate-speed = 0.5s
2
 sidebar-animate-speed = 0.5s
3
 
3
 
4
 .sidebarSticky
4
 .sidebarSticky
15
   background-color rgba(253, 253, 253, 0.3)
15
   background-color rgba(253, 253, 253, 0.3)
16
 
16
 
17
 .sidebar
17
 .sidebar
18
+  display flex
19
+  flex-direction column
20
+  justify-content space-between
18
   position relative
21
   position relative
19
   transition all sidebar-animate-speed
22
   transition all sidebar-animate-speed
20
   width sidebar-width
23
   width sidebar-width
26
     width 0
29
     width 0
27
   &__expand
30
   &__expand
28
     position absolute
31
     position absolute
29
-    top 0
30
-    right -43px
32
+    right -42px
31
     display flex
33
     display flex
32
     justify-content center
34
     justify-content center
33
     align-items center
35
     align-items center
38
     cursor pointer
40
     cursor pointer
39
     color white
41
     color white
40
     transition all sidebar-animate-speed
42
     transition all sidebar-animate-speed
41
-  &__btnnewworkspace
42
-    margin 50px 0
43
-    overflow hidden
44
-    &__btn
45
-      display block
46
-      margin 0 auto
47
-      padding 15px 30px
48
-  &__navigation
49
-    padding 0
50
-    overflow hidden
51
-    &__workspace
52
-      padding-left 0
53
-      list-style none
54
-      &__item
55
-        &__wrapper
56
-          display flex
57
-          align-items center
58
-          border-bottom 1px solid
59
-          width 100%
60
-          height 100%
61
-          cursor pointer
62
-        &__number
63
-          display flex
64
-          leftside()
65
-          padding 12px
66
-          width 50px
67
-          letter-spacing 2px
68
-        &__name
69
-          padding 10px
70
-          font-size 20px
71
-          color off-white
72
-          white-space nowrap
73
-          overflow hidden
74
-          text-overflow ellipsis
75
-        &__icon
76
-          margin 0 10px 0 auto
77
-          color white
78
-        &__submenu
79
-          margin 0
80
-          padding 0
81
-          width 100%
82
-          overflow hidden
83
-          & > li
84
-            display block
85
-          &__dropdown
43
+  &__content
44
+    height 100%
45
+    &__btnnewworkspace
46
+      margin 50px 0
47
+      overflow hidden
48
+      &__btn
49
+        display block
50
+        margin 0 auto
51
+        padding 15px 30px
52
+    &__navigation
53
+      padding 0
54
+      width sidebar-width
55
+      transition all sidebar-animate-speed
56
+      overflow hidden
57
+      &.sidebarclose
58
+        width 0
59
+      &__workspace
60
+        padding-left 0
61
+        list-style none
62
+        &__item
63
+          &__wrapper
86
             display flex
64
             display flex
87
             align-items center
65
             align-items center
88
             border-bottom 1px solid
66
             border-bottom 1px solid
67
+            width 100%
68
+            height 100%
89
             cursor pointer
69
             cursor pointer
90
-            &__showdropdown
70
+          &__number
71
+            display flex
72
+            leftside()
73
+            padding 12px
74
+            width 50px
75
+            letter-spacing 2px
76
+          &__name
77
+            padding 10px
78
+            width 224px
79
+            font-size 20px
80
+            color off-white
81
+            white-space nowrap
82
+            overflow hidden
83
+            text-overflow ellipsis
84
+          &__icon
85
+            display flex
86
+            align-items center
87
+            width 26px
88
+            height 50px
89
+            color white
90
+          &__submenu
91
+            margin 0
92
+            padding 0
93
+            width 100%
94
+            overflow hidden
95
+            & > li
96
+              display block
97
+            &__dropdown
91
               display flex
98
               display flex
92
-              justify-content space-between
93
               align-items center
99
               align-items center
94
-              padding 0 10px
95
-              width 100%
96
-            .dropdown__icon
97
-              padding 10px 15px
98
-              min-width 50px
99
-              leftside()
100
-            .dropdown__title
101
-              white-space nowrap
102
-              overflow hidden
103
-              text-overflow ellipsis
104
-              // color off-white
105
-            &.activeFilter
100
+              border-bottom 1px solid
101
+              cursor pointer
102
+              &__showdropdown
103
+                display flex
104
+                justify-content space-between
105
+                align-items center
106
+                padding 0 10px
107
+                width 100%
106
               .dropdown__icon
108
               .dropdown__icon
107
-                background-color rgba(253, 253, 253, 0.8)
109
+                padding 10px 15px
110
+                min-width 50px
111
+                leftside()
112
+              .dropdown__title
113
+                white-space nowrap
114
+                overflow hidden
115
+                text-overflow ellipsis
116
+                // color off-white
117
+              &.activeFilter
118
+                .dropdown__icon
119
+                  background-color rgba(253, 253, 253, 0.8)
108
   &__footer
120
   &__footer
109
     &__text
121
     &__text
122
+      color off-white
110
       font-size 14px
123
       font-size 14px
111
       &__link
124
       &__link
112
         & > a
125
         & > a
116
             text-decoration underline
129
             text-decoration underline
117
             color fourthColor
130
             color fourthColor
118
 
131
 
132
+
133
+/***** MEDIAQUERIES ******/
134
+
119
 /***** MEDIA 992px and 1199px ******/
135
 /***** MEDIA 992px and 1199px ******/
120
 
136
 
121
 @media (min-width: min-lg) and (max-width: max-lg)
137
 @media (min-width: min-lg) and (max-width: max-lg)
122
 
138
 
139
+  .sidebarpagecontainer
140
+    position relative
141
+
123
   .sidebar
142
   .sidebar
124
-    position fixed
143
+    position absolute
125
 
144
 
126
-/***** MEDIA 576px and 991px ******/
145
+/*** MEDIA 768px and 991px ****/
127
 
146
 
128
-@media (min-width: min-sm) and (max-width: max-md)
147
+@media (min-width: min-md) and (max-width: max-md)
129
 
148
 
130
-  .sidebarSticky
131
-    top 69px
149
+  .sidebarpagecontainer
150
+    position relative
132
 
151
 
133
   .sidebar
152
   .sidebar
134
-    position fixed
153
+    position absolute
135
 
154
 
136
 /***** MEDIA 576px and 767px *****/
155
 /***** MEDIA 576px and 767px *****/
137
 
156
 
138
 @media (min-width: min-sm) and (max-width: max-sm)
157
 @media (min-width: min-sm) and (max-width: max-sm)
139
 
158
 
140
   .sidebarpagecontainer
159
   .sidebarpagecontainer
160
+    position relative
141
     padding-top 69px
161
     padding-top 69px
142
 
162
 
143
-/***** MEDIA  *****/
163
+  .sidebar
164
+    position absolute
165
+
166
+/***** MEDIA 575px *****/
144
 
167
 
145
 @media (max-width: 575px)
168
 @media (max-width: 575px)
146
 
169
 
147
   .sidebarpagecontainer
170
   .sidebarpagecontainer
171
+    position relative
148
     padding-top 69px
172
     padding-top 69px
149
 
173
 
150
-  .sidebarSticky
151
-    top 69px
152
-
153
   .sidebar
174
   .sidebar
154
-    position fixed
175
+    position absolute

+ 2 - 0
frontend/src/css/index.styl View File

30
 @import 'HomepageCard'
30
 @import 'HomepageCard'
31
 
31
 
32
 @import 'ExtandedAction'
32
 @import 'ExtandedAction'
33
+
34
+@import 'AdminWorkspacePage'

+ 19 - 1
frontend/src/reducer/currentWorkspace.js View File

2
   SET,
2
   SET,
3
   WORKSPACE_DETAIL,
3
   WORKSPACE_DETAIL,
4
   WORKSPACE_MEMBER_LIST,
4
   WORKSPACE_MEMBER_LIST,
5
-  WORKSPACE_READ_STATUS_LIST,
5
+  WORKSPACE_READ_STATUS_LIST, WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST,
6
   WORKSPACE_RECENT_ACTIVITY_LIST
6
   WORKSPACE_RECENT_ACTIVITY_LIST
7
 } from '../action-creator.sync.js'
7
 } from '../action-creator.sync.js'
8
 import { handleRouteFromApi } from '../helper.js'
8
 import { handleRouteFromApi } from '../helper.js'
15
   sidebarEntryList: [],
15
   sidebarEntryList: [],
16
   memberList: [],
16
   memberList: [],
17
   recentActivityList: [],
17
   recentActivityList: [],
18
+  recentActivityForUserList: [],
18
   contentReadStatusList: []
19
   contentReadStatusList: []
19
 }
20
 }
20
 
21
 
65
         }))
66
         }))
66
       }
67
       }
67
 
68
 
69
+    case `${SET}/${WORKSPACE_RECENT_ACTIVITY_FOR_USER_LIST}`:
70
+      return {
71
+        ...state,
72
+        recentActivityForUserList: action.workspaceRecentActivityForUserList.map(ra => ({
73
+          id: ra.content_id,
74
+          slug: ra.slug,
75
+          label: ra.label,
76
+          type: ra.content_type,
77
+          idParent: ra.parent_id,
78
+          showInUi: ra.show_in_ui,
79
+          isArchived: ra.is_archived,
80
+          isDeleted: ra.is_deleted,
81
+          statusSlug: ra.status,
82
+          subContentTypeSlug: ra.sub_content_types
83
+        }))
84
+      }
85
+
68
     case `${SET}/${WORKSPACE_READ_STATUS_LIST}`:
86
     case `${SET}/${WORKSPACE_READ_STATUS_LIST}`:
69
       return {
87
       return {
70
         ...state,
88
         ...state,

+ 0 - 0
frontend/src/reducer/user.js View File


Some files were not shown because too many files changed in this diff