From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- .../static/src/components/activity/activity.js | 199 + .../static/src/components/activity/activity.scss | 186 + .../static/src/components/activity/activity.xml | 153 + .../src/components/activity/activity_tests.js | 1157 +++++ .../src/components/activity_box/activity_box.js | 64 + .../src/components/activity_box/activity_box.scss | 45 + .../src/components/activity_box/activity_box.xml | 45 + .../activity_mark_done_popover.js | 122 + .../activity_mark_done_popover.scss | 20 + .../activity_mark_done_popover.xml | 23 + .../activity_mark_done_popover_tests.js | 297 ++ .../static/src/components/attachment/attachment.js | 204 + .../src/components/attachment/attachment.scss | 204 + .../src/components/attachment/attachment.xml | 115 + .../src/components/attachment/attachment_tests.js | 762 ++++ .../components/attachment_box/attachment_box.js | 124 + .../components/attachment_box/attachment_box.scss | 46 + .../components/attachment_box/attachment_box.xml | 50 + .../attachment_box/attachment_box_tests.js | 337 ++ .../attachment_delete_confirm_dialog.js | 92 + .../attachment_delete_confirm_dialog.xml | 12 + .../components/attachment_list/attachment_list.js | 119 + .../attachment_list/attachment_list.scss | 29 + .../components/attachment_list/attachment_list.xml | 39 + .../attachment_viewer/attachment_viewer.js | 598 +++ .../attachment_viewer/attachment_viewer.scss | 198 + .../attachment_viewer/attachment_viewer.xml | 93 + .../autocomplete_input/autocomplete_input.js | 174 + .../autocomplete_input/autocomplete_input.xml | 8 + .../src/components/chat_window/chat_window.js | 363 ++ .../src/components/chat_window/chat_window.scss | 93 + .../src/components/chat_window/chat_window.xml | 54 + .../chat_window_header/chat_window_header.js | 118 + .../chat_window_header/chat_window_header.scss | 95 + .../chat_window_header/chat_window_header.xml | 56 + .../chat_window_hidden_menu.js | 141 + .../chat_window_hidden_menu.scss | 90 + .../chat_window_hidden_menu.xml | 31 + .../chat_window_manager/chat_window_manager.js | 51 + .../chat_window_manager/chat_window_manager.scss | 16 + .../chat_window_manager/chat_window_manager.xml | 23 + .../chat_window_manager_tests.js | 2423 +++++++++++ .../mail/static/src/components/chatter/chatter.js | 150 + .../static/src/components/chatter/chatter.scss | 42 + .../mail/static/src/components/chatter/chatter.xml | 56 + .../chatter/chatter_suggested_recipient_tests.js | 420 ++ .../static/src/components/chatter/chatter_tests.js | 469 +++ .../chatter_container/chatter_container.js | 139 + .../chatter_container/chatter_container.scss | 25 + .../chatter_container/chatter_container.xml | 15 + .../components/chatter_topbar/chatter_topbar.js | 137 + .../components/chatter_topbar/chatter_topbar.scss | 106 + .../components/chatter_topbar/chatter_topbar.xml | 74 + .../chatter_topbar/chatter_topbar_tests.js | 730 ++++ .../static/src/components/composer/composer.js | 444 ++ .../static/src/components/composer/composer.scss | 273 ++ .../static/src/components/composer/composer.xml | 179 + .../src/components/composer/composer_tests.js | 2153 ++++++++++ .../composer_suggested_recipient.js | 158 + .../composer_suggested_recipient.scss | 5 + .../composer_suggested_recipient.xml | 41 + .../composer_suggested_recipient_list.js | 77 + .../composer_suggested_recipient_list.scss | 3 + .../composer_suggested_recipient_list.xml | 26 + .../composer_suggestion/composer_suggestion.js | 143 + .../composer_suggestion/composer_suggestion.scss | 43 + .../composer_suggestion/composer_suggestion.xml | 33 + .../composer_suggestion_canned_response_tests.js | 154 + .../composer_suggestion_channel_tests.js | 144 + .../composer_suggestion_command_tests.js | 151 + .../composer_suggestion_partner_tests.js | 160 + .../composer_suggestion_list.js | 73 + .../composer_suggestion_list.scss | 27 + .../composer_suggestion_list.xml | 32 + .../composer_text_input/composer_text_input.js | 419 ++ .../composer_text_input/composer_text_input.scss | 40 + .../composer_text_input/composer_text_input.xml | 24 + addons/mail/static/src/components/dialog/dialog.js | 119 + .../mail/static/src/components/dialog/dialog.scss | 23 + .../mail/static/src/components/dialog/dialog.xml | 22 + .../components/dialog_manager/dialog_manager.js | 69 + .../components/dialog_manager/dialog_manager.xml | 17 + .../dialog_manager/dialog_manager_tests.js | 82 + .../mail/static/src/components/discuss/discuss.js | 313 ++ .../static/src/components/discuss/discuss.scss | 114 + .../mail/static/src/components/discuss/discuss.xml | 106 + .../discuss/tests/discuss_domain_tests.js | 408 ++ .../discuss/tests/discuss_inbox_tests.js | 725 ++++ .../discuss/tests/discuss_moderation_tests.js | 1180 ++++++ .../discuss/tests/discuss_pinned_tests.js | 238 ++ .../discuss/tests/discuss_sidebar_tests.js | 163 + .../src/components/discuss/tests/discuss_tests.js | 4447 ++++++++++++++++++++ .../discuss_mobile_mailbox_selection.js | 95 + .../discuss_mobile_mailbox_selection.scss | 26 + .../discuss_mobile_mailbox_selection.xml | 20 + .../discuss_mobile_mailbox_selection_tests.js | 130 + .../components/discuss_sidebar/discuss_sidebar.js | 308 ++ .../discuss_sidebar/discuss_sidebar.scss | 110 + .../components/discuss_sidebar/discuss_sidebar.xml | 81 + .../discuss_sidebar_item/discuss_sidebar_item.js | 220 + .../discuss_sidebar_item/discuss_sidebar_item.scss | 109 + .../discuss_sidebar_item/discuss_sidebar_item.xml | 63 + .../static/src/components/drop_zone/drop_zone.js | 139 + .../static/src/components/drop_zone/drop_zone.scss | 29 + .../static/src/components/drop_zone/drop_zone.xml | 12 + .../src/components/editable_text/editable_text.js | 91 + .../src/components/editable_text/editable_text.xml | 8 + .../components/emojis_popover/emojis_popover.js | 78 + .../components/emojis_popover/emojis_popover.scss | 22 + .../components/emojis_popover/emojis_popover.xml | 14 + .../src/components/file_uploader/file_uploader.js | 241 ++ .../components/file_uploader/file_uploader.scss | 3 + .../src/components/file_uploader/file_uploader.xml | 10 + .../file_uploader/file_uploader_tests.js | 94 + .../src/components/follow_button/follow_button.js | 93 + .../components/follow_button/follow_button.scss | 27 + .../src/components/follow_button/follow_button.xml | 24 + .../follow_button/follow_button_tests.js | 278 ++ .../static/src/components/follower/follower.js | 80 + .../static/src/components/follower/follower.scss | 55 + .../static/src/components/follower/follower.xml | 23 + .../src/components/follower/follower_tests.js | 380 ++ .../follower_list_menu/follower_list_menu.js | 154 + .../follower_list_menu/follower_list_menu.scss | 17 + .../follower_list_menu/follower_list_menu.xml | 38 + .../follower_list_menu/follower_list_menu_tests.js | 424 ++ .../follower_subtype/follower_subtype.js | 71 + .../follower_subtype/follower_subtype.scss | 27 + .../follower_subtype/follower_subtype.xml | 13 + .../follower_subtype/follower_subtype_tests.js | 233 + .../follower_subtype_list/follower_subtype_list.js | 89 + .../follower_subtype_list.scss | 8 + .../follower_subtype_list.xml | 38 + .../src/components/mail_template/mail_template.js | 81 + .../components/mail_template/mail_template.scss | 27 + .../src/components/mail_template/mail_template.xml | 29 + .../mail/static/src/components/message/message.js | 680 +++ .../static/src/components/message/message.scss | 381 ++ .../mail/static/src/components/message/message.xml | 210 + .../static/src/components/message/message_tests.js | 1580 +++++++ .../message_author_prefix/message_author_prefix.js | 67 + .../message_author_prefix.scss | 11 + .../message_author_prefix.xml | 17 + .../src/components/message_list/message_list.js | 600 +++ .../src/components/message_list/message_list.scss | 135 + .../src/components/message_list/message_list.xml | 103 + .../message_seen_indicator.js | 136 + .../message_seen_indicator.scss | 39 + .../message_seen_indicator.xml | 16 + .../message_seen_indicator_tests.js | 294 ++ .../components/messaging_menu/messaging_menu.js | 234 + .../components/messaging_menu/messaging_menu.scss | 143 + .../components/messaging_menu/messaging_menu.xml | 83 + .../messaging_menu/messaging_menu_tests.js | 1039 +++++ .../mobile_messaging_navbar.js | 61 + .../mobile_messaging_navbar.scss | 43 + .../mobile_messaging_navbar.xml | 17 + .../moderation_ban_dialog/moderation_ban_dialog.js | 94 + .../moderation_ban_dialog.xml | 23 + .../moderation_discard_dialog.js | 109 + .../moderation_discard_dialog.xml | 13 + .../moderation_reject_dialog.js | 104 + .../moderation_reject_dialog.xml | 13 + .../notification_alert/notification_alert.js | 54 + .../notification_alert/notification_alert.xml | 14 + .../notification_group/notification_group.js | 93 + .../notification_group/notification_group.scss | 93 + .../notification_group/notification_group.xml | 39 + .../notification_list/notification_list.js | 226 + .../notification_list/notification_list.scss | 37 + .../notification_list/notification_list.xml | 47 + .../notification_list/notification_list_item.scss | 179 + .../notification_list_notification_group_tests.js | 546 +++ .../notification_list/notification_list_tests.js | 162 + .../notification_popover/notification_popover.js | 95 + .../notification_popover/notification_popover.scss | 7 + .../notification_popover/notification_popover.xml | 17 + .../notification_request/notification_request.js | 94 + .../notification_request/notification_request.scss | 77 + .../notification_request/notification_request.xml | 31 + .../partner_im_status_icon.js | 74 + .../partner_im_status_icon.scss | 59 + .../partner_im_status_icon.xml | 38 + .../partner_im_status_icon_tests.js | 145 + .../src/components/thread_icon/thread_icon.js | 64 + .../src/components/thread_icon/thread_icon.scss | 26 + .../src/components/thread_icon/thread_icon.xml | 58 + .../components/thread_icon/thread_icon_tests.js | 118 + .../thread_needaction_preview.js | 151 + .../thread_needaction_preview.scss | 108 + .../thread_needaction_preview.xml | 58 + .../thread_needaction_preview_tests.js | 457 ++ .../components/thread_preview/thread_preview.js | 130 + .../components/thread_preview/thread_preview.scss | 117 + .../components/thread_preview/thread_preview.xml | 63 + .../thread_preview/thread_preview_tests.js | 114 + .../thread_textual_typing_status.js | 52 + .../thread_textual_typing_status.scss | 12 + .../thread_textual_typing_status.xml | 14 + .../thread_textual_typing_status_tests.js | 367 ++ .../thread_typing_icon/thread_typing_icon.js | 41 + .../thread_typing_icon/thread_typing_icon.scss | 108 + .../thread_typing_icon/thread_typing_icon.xml | 29 + .../src/components/thread_view/thread_view.js | 222 + .../src/components/thread_view/thread_view.scss | 38 + .../src/components/thread_view/thread_view.xml | 50 + .../components/thread_view/thread_view_tests.js | 1809 ++++++++ 207 files changed, 40950 insertions(+) create mode 100644 addons/mail/static/src/components/activity/activity.js create mode 100644 addons/mail/static/src/components/activity/activity.scss create mode 100644 addons/mail/static/src/components/activity/activity.xml create mode 100644 addons/mail/static/src/components/activity/activity_tests.js create mode 100644 addons/mail/static/src/components/activity_box/activity_box.js create mode 100644 addons/mail/static/src/components/activity_box/activity_box.scss create mode 100644 addons/mail/static/src/components/activity_box/activity_box.xml create mode 100644 addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js create mode 100644 addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss create mode 100644 addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml create mode 100644 addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js create mode 100644 addons/mail/static/src/components/attachment/attachment.js create mode 100644 addons/mail/static/src/components/attachment/attachment.scss create mode 100644 addons/mail/static/src/components/attachment/attachment.xml create mode 100644 addons/mail/static/src/components/attachment/attachment_tests.js create mode 100644 addons/mail/static/src/components/attachment_box/attachment_box.js create mode 100644 addons/mail/static/src/components/attachment_box/attachment_box.scss create mode 100644 addons/mail/static/src/components/attachment_box/attachment_box.xml create mode 100644 addons/mail/static/src/components/attachment_box/attachment_box_tests.js create mode 100644 addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.js create mode 100644 addons/mail/static/src/components/attachment_delete_confirm_dialog/attachment_delete_confirm_dialog.xml create mode 100644 addons/mail/static/src/components/attachment_list/attachment_list.js create mode 100644 addons/mail/static/src/components/attachment_list/attachment_list.scss create mode 100644 addons/mail/static/src/components/attachment_list/attachment_list.xml create mode 100644 addons/mail/static/src/components/attachment_viewer/attachment_viewer.js create mode 100644 addons/mail/static/src/components/attachment_viewer/attachment_viewer.scss create mode 100644 addons/mail/static/src/components/attachment_viewer/attachment_viewer.xml create mode 100644 addons/mail/static/src/components/autocomplete_input/autocomplete_input.js create mode 100644 addons/mail/static/src/components/autocomplete_input/autocomplete_input.xml create mode 100644 addons/mail/static/src/components/chat_window/chat_window.js create mode 100644 addons/mail/static/src/components/chat_window/chat_window.scss create mode 100644 addons/mail/static/src/components/chat_window/chat_window.xml create mode 100644 addons/mail/static/src/components/chat_window_header/chat_window_header.js create mode 100644 addons/mail/static/src/components/chat_window_header/chat_window_header.scss create mode 100644 addons/mail/static/src/components/chat_window_header/chat_window_header.xml create mode 100644 addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js create mode 100644 addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.scss create mode 100644 addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.xml create mode 100644 addons/mail/static/src/components/chat_window_manager/chat_window_manager.js create mode 100644 addons/mail/static/src/components/chat_window_manager/chat_window_manager.scss create mode 100644 addons/mail/static/src/components/chat_window_manager/chat_window_manager.xml create mode 100644 addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js create mode 100644 addons/mail/static/src/components/chatter/chatter.js create mode 100644 addons/mail/static/src/components/chatter/chatter.scss create mode 100644 addons/mail/static/src/components/chatter/chatter.xml create mode 100644 addons/mail/static/src/components/chatter/chatter_suggested_recipient_tests.js create mode 100644 addons/mail/static/src/components/chatter/chatter_tests.js create mode 100644 addons/mail/static/src/components/chatter_container/chatter_container.js create mode 100644 addons/mail/static/src/components/chatter_container/chatter_container.scss create mode 100644 addons/mail/static/src/components/chatter_container/chatter_container.xml create mode 100644 addons/mail/static/src/components/chatter_topbar/chatter_topbar.js create mode 100644 addons/mail/static/src/components/chatter_topbar/chatter_topbar.scss create mode 100644 addons/mail/static/src/components/chatter_topbar/chatter_topbar.xml create mode 100644 addons/mail/static/src/components/chatter_topbar/chatter_topbar_tests.js create mode 100644 addons/mail/static/src/components/composer/composer.js create mode 100644 addons/mail/static/src/components/composer/composer.scss create mode 100644 addons/mail/static/src/components/composer/composer.xml create mode 100644 addons/mail/static/src/components/composer/composer_tests.js create mode 100644 addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js create mode 100644 addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.scss create mode 100644 addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml create mode 100644 addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js create mode 100644 addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.scss create mode 100644 addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.xml create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion.js create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion.scss create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion.xml create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion_canned_response_tests.js create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion_channel_tests.js create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion_command_tests.js create mode 100644 addons/mail/static/src/components/composer_suggestion/composer_suggestion_partner_tests.js create mode 100644 addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.js create mode 100644 addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.scss create mode 100644 addons/mail/static/src/components/composer_suggestion_list/composer_suggestion_list.xml create mode 100644 addons/mail/static/src/components/composer_text_input/composer_text_input.js create mode 100644 addons/mail/static/src/components/composer_text_input/composer_text_input.scss create mode 100644 addons/mail/static/src/components/composer_text_input/composer_text_input.xml create mode 100644 addons/mail/static/src/components/dialog/dialog.js create mode 100644 addons/mail/static/src/components/dialog/dialog.scss create mode 100644 addons/mail/static/src/components/dialog/dialog.xml create mode 100644 addons/mail/static/src/components/dialog_manager/dialog_manager.js create mode 100644 addons/mail/static/src/components/dialog_manager/dialog_manager.xml create mode 100644 addons/mail/static/src/components/dialog_manager/dialog_manager_tests.js create mode 100644 addons/mail/static/src/components/discuss/discuss.js create mode 100644 addons/mail/static/src/components/discuss/discuss.scss create mode 100644 addons/mail/static/src/components/discuss/discuss.xml create mode 100644 addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js create mode 100644 addons/mail/static/src/components/discuss/tests/discuss_inbox_tests.js create mode 100644 addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js create mode 100644 addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js create mode 100644 addons/mail/static/src/components/discuss/tests/discuss_sidebar_tests.js create mode 100644 addons/mail/static/src/components/discuss/tests/discuss_tests.js create mode 100644 addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js create mode 100644 addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.scss create mode 100644 addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.xml create mode 100644 addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection_tests.js create mode 100644 addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js create mode 100644 addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.scss create mode 100644 addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml create mode 100644 addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js create mode 100644 addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.scss create mode 100644 addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.xml create mode 100644 addons/mail/static/src/components/drop_zone/drop_zone.js create mode 100644 addons/mail/static/src/components/drop_zone/drop_zone.scss create mode 100644 addons/mail/static/src/components/drop_zone/drop_zone.xml create mode 100644 addons/mail/static/src/components/editable_text/editable_text.js create mode 100644 addons/mail/static/src/components/editable_text/editable_text.xml create mode 100644 addons/mail/static/src/components/emojis_popover/emojis_popover.js create mode 100644 addons/mail/static/src/components/emojis_popover/emojis_popover.scss create mode 100644 addons/mail/static/src/components/emojis_popover/emojis_popover.xml create mode 100644 addons/mail/static/src/components/file_uploader/file_uploader.js create mode 100644 addons/mail/static/src/components/file_uploader/file_uploader.scss create mode 100644 addons/mail/static/src/components/file_uploader/file_uploader.xml create mode 100644 addons/mail/static/src/components/file_uploader/file_uploader_tests.js create mode 100644 addons/mail/static/src/components/follow_button/follow_button.js create mode 100644 addons/mail/static/src/components/follow_button/follow_button.scss create mode 100644 addons/mail/static/src/components/follow_button/follow_button.xml create mode 100644 addons/mail/static/src/components/follow_button/follow_button_tests.js create mode 100644 addons/mail/static/src/components/follower/follower.js create mode 100644 addons/mail/static/src/components/follower/follower.scss create mode 100644 addons/mail/static/src/components/follower/follower.xml create mode 100644 addons/mail/static/src/components/follower/follower_tests.js create mode 100644 addons/mail/static/src/components/follower_list_menu/follower_list_menu.js create mode 100644 addons/mail/static/src/components/follower_list_menu/follower_list_menu.scss create mode 100644 addons/mail/static/src/components/follower_list_menu/follower_list_menu.xml create mode 100644 addons/mail/static/src/components/follower_list_menu/follower_list_menu_tests.js create mode 100644 addons/mail/static/src/components/follower_subtype/follower_subtype.js create mode 100644 addons/mail/static/src/components/follower_subtype/follower_subtype.scss create mode 100644 addons/mail/static/src/components/follower_subtype/follower_subtype.xml create mode 100644 addons/mail/static/src/components/follower_subtype/follower_subtype_tests.js create mode 100644 addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.js create mode 100644 addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.scss create mode 100644 addons/mail/static/src/components/follower_subtype_list/follower_subtype_list.xml create mode 100644 addons/mail/static/src/components/mail_template/mail_template.js create mode 100644 addons/mail/static/src/components/mail_template/mail_template.scss create mode 100644 addons/mail/static/src/components/mail_template/mail_template.xml create mode 100644 addons/mail/static/src/components/message/message.js create mode 100644 addons/mail/static/src/components/message/message.scss create mode 100644 addons/mail/static/src/components/message/message.xml create mode 100644 addons/mail/static/src/components/message/message_tests.js create mode 100644 addons/mail/static/src/components/message_author_prefix/message_author_prefix.js create mode 100644 addons/mail/static/src/components/message_author_prefix/message_author_prefix.scss create mode 100644 addons/mail/static/src/components/message_author_prefix/message_author_prefix.xml create mode 100644 addons/mail/static/src/components/message_list/message_list.js create mode 100644 addons/mail/static/src/components/message_list/message_list.scss create mode 100644 addons/mail/static/src/components/message_list/message_list.xml create mode 100644 addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.js create mode 100644 addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.scss create mode 100644 addons/mail/static/src/components/message_seen_indicator/message_seen_indicator.xml create mode 100644 addons/mail/static/src/components/message_seen_indicator/message_seen_indicator_tests.js create mode 100644 addons/mail/static/src/components/messaging_menu/messaging_menu.js create mode 100644 addons/mail/static/src/components/messaging_menu/messaging_menu.scss create mode 100644 addons/mail/static/src/components/messaging_menu/messaging_menu.xml create mode 100644 addons/mail/static/src/components/messaging_menu/messaging_menu_tests.js create mode 100644 addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js create mode 100644 addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.scss create mode 100644 addons/mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.xml create mode 100644 addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js create mode 100644 addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.xml create mode 100644 addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js create mode 100644 addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.xml create mode 100644 addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js create mode 100644 addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.xml create mode 100644 addons/mail/static/src/components/notification_alert/notification_alert.js create mode 100644 addons/mail/static/src/components/notification_alert/notification_alert.xml create mode 100644 addons/mail/static/src/components/notification_group/notification_group.js create mode 100644 addons/mail/static/src/components/notification_group/notification_group.scss create mode 100644 addons/mail/static/src/components/notification_group/notification_group.xml create mode 100644 addons/mail/static/src/components/notification_list/notification_list.js create mode 100644 addons/mail/static/src/components/notification_list/notification_list.scss create mode 100644 addons/mail/static/src/components/notification_list/notification_list.xml create mode 100644 addons/mail/static/src/components/notification_list/notification_list_item.scss create mode 100644 addons/mail/static/src/components/notification_list/notification_list_notification_group_tests.js create mode 100644 addons/mail/static/src/components/notification_list/notification_list_tests.js create mode 100644 addons/mail/static/src/components/notification_popover/notification_popover.js create mode 100644 addons/mail/static/src/components/notification_popover/notification_popover.scss create mode 100644 addons/mail/static/src/components/notification_popover/notification_popover.xml create mode 100644 addons/mail/static/src/components/notification_request/notification_request.js create mode 100644 addons/mail/static/src/components/notification_request/notification_request.scss create mode 100644 addons/mail/static/src/components/notification_request/notification_request.xml create mode 100644 addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js create mode 100644 addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.scss create mode 100644 addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon.xml create mode 100644 addons/mail/static/src/components/partner_im_status_icon/partner_im_status_icon_tests.js create mode 100644 addons/mail/static/src/components/thread_icon/thread_icon.js create mode 100644 addons/mail/static/src/components/thread_icon/thread_icon.scss create mode 100644 addons/mail/static/src/components/thread_icon/thread_icon.xml create mode 100644 addons/mail/static/src/components/thread_icon/thread_icon_tests.js create mode 100644 addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js create mode 100644 addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.scss create mode 100644 addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.xml create mode 100644 addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview_tests.js create mode 100644 addons/mail/static/src/components/thread_preview/thread_preview.js create mode 100644 addons/mail/static/src/components/thread_preview/thread_preview.scss create mode 100644 addons/mail/static/src/components/thread_preview/thread_preview.xml create mode 100644 addons/mail/static/src/components/thread_preview/thread_preview_tests.js create mode 100644 addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js create mode 100644 addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.scss create mode 100644 addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.xml create mode 100644 addons/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js create mode 100644 addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.js create mode 100644 addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.scss create mode 100644 addons/mail/static/src/components/thread_typing_icon/thread_typing_icon.xml create mode 100644 addons/mail/static/src/components/thread_view/thread_view.js create mode 100644 addons/mail/static/src/components/thread_view/thread_view.scss create mode 100644 addons/mail/static/src/components/thread_view/thread_view.xml create mode 100644 addons/mail/static/src/components/thread_view/thread_view_tests.js (limited to 'addons/mail/static/src/components') diff --git a/addons/mail/static/src/components/activity/activity.js b/addons/mail/static/src/components/activity/activity.js new file mode 100644 index 00000000..1ee7ecf3 --- /dev/null +++ b/addons/mail/static/src/components/activity/activity.js @@ -0,0 +1,199 @@ +odoo.define('mail/static/src/components/activity/activity.js', function (require) { +'use strict'; + +const components = { + ActivityMarkDonePopover: require('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js'), + FileUploader: require('mail/static/src/components/file_uploader/file_uploader.js'), + MailTemplate: require('mail/static/src/components/mail_template/mail_template.js'), +}; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { + auto_str_to_date, + getLangDateFormat, + getLangDatetimeFormat, +} = require('web.time'); + +const { Component, useState } = owl; +const { useRef } = owl.hooks; + +class Activity extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + this.state = useState({ + areDetailsVisible: false, + }); + useStore(props => { + const activity = this.env.models['mail.activity'].get(props.activityLocalId); + return { + activity: activity ? activity.__state : undefined, + assigneeNameOrDisplayName: ( + activity && + activity.assignee && + activity.assignee.nameOrDisplayName + ), + }; + }); + /** + * Reference of the file uploader. + * Useful to programmatically prompts the browser file uploader. + */ + this._fileUploaderRef = useRef('fileUploader'); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {mail.activity} + */ + get activity() { + return this.env.models['mail.activity'].get(this.props.activityLocalId); + } + + /** + * @returns {string} + */ + get assignedUserText() { + return _.str.sprintf(this.env._t("for %s"), this.activity.assignee.nameOrDisplayName); + } + + /** + * @returns {string} + */ + get delayLabel() { + const today = moment().startOf('day'); + const momentDeadlineDate = moment(auto_str_to_date(this.activity.dateDeadline)); + // true means no rounding + const diff = momentDeadlineDate.diff(today, 'days', true); + if (diff === 0) { + return this.env._t("Today:"); + } else if (diff === -1) { + return this.env._t("Yesterday:"); + } else if (diff < 0) { + return _.str.sprintf(this.env._t("%d days overdue:"), Math.abs(diff)); + } else if (diff === 1) { + return this.env._t("Tomorrow:"); + } else { + return _.str.sprintf(this.env._t("Due in %d days:"), Math.abs(diff)); + } + } + + /** + * @returns {string} + */ + get formattedCreateDatetime() { + const momentCreateDate = moment(auto_str_to_date(this.activity.dateCreate)); + const datetimeFormat = getLangDatetimeFormat(); + return momentCreateDate.format(datetimeFormat); + } + + /** + * @returns {string} + */ + get formattedDeadlineDate() { + const momentDeadlineDate = moment(auto_str_to_date(this.activity.dateDeadline)); + const datetimeFormat = getLangDateFormat(); + return momentDeadlineDate.format(datetimeFormat); + } + + /** + * @returns {string} + */ + get MARK_DONE() { + return this.env._t("Mark Done"); + } + + /** + * @returns {string} + */ + get summary() { + return _.str.sprintf(this.env._t("“%s”"), this.activity.summary); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {CustomEvent} ev + * @param {Object} ev.detail + * @param {mail.attachment} ev.detail.attachment + */ + _onAttachmentCreated(ev) { + this.activity.markAsDone({ attachments: [ev.detail.attachment] }); + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClick(ev) { + if ( + ev.target.tagName === 'A' && + ev.target.dataset.oeId && + ev.target.dataset.oeModel + ) { + this.env.messaging.openProfile({ + id: Number(ev.target.dataset.oeId), + model: ev.target.dataset.oeModel, + }); + // avoid following dummy href + ev.preventDefault(); + } + } + + /** + * @private + * @param {MouseEvent} ev + */ + async _onClickCancel(ev) { + ev.preventDefault(); + await this.activity.deleteServerRecord(); + this.trigger('reload', { keepChanges: true }); + } + + /** + * @private + */ + _onClickDetailsButton() { + this.state.areDetailsVisible = !this.state.areDetailsVisible; + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickEdit(ev) { + this.activity.edit(); + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickUploadDocument(ev) { + this._fileUploaderRef.comp.openBrowserFileUploader(); + } + +} + +Object.assign(Activity, { + components, + props: { + activityLocalId: String, + }, + template: 'mail.Activity', +}); + +return Activity; + +}); diff --git a/addons/mail/static/src/components/activity/activity.scss b/addons/mail/static/src/components/activity/activity.scss new file mode 100644 index 00000000..64a0ceac --- /dev/null +++ b/addons/mail/static/src/components/activity/activity.scss @@ -0,0 +1,186 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_Activity { + display: flex; + flex: 0 0 auto; + padding: map-get($spacers, 2); +} + +.o_Activity_detailsUserAvatar { + margin-inline-end: map-get($spacers, 2); + object-fit: cover; + height: 18px; + width: 18px; +} + +.o_Activity_dueDateText, .o_Activity_summary { + margin-inline-end: map-get($spacers, 2); +} + +.o_Activity_iconContainer { + @include o-position-absolute($top: auto, $left: auto, $bottom: -5px, $right: -5px); + display: flex; + align-items: center; + justify-content: center; + width: 25px; + height: 25px; + border-width: 2px; +} + +.o_Activity_info { + display: flex; + align-items: baseline; +} + +.o_Activity_note p { + margin-bottom: 0; +} + +.o_Activity_sidebar { + display: flex; + flex: 0 0 36px; + margin-right: map-get($spacers, 3); + justify-content: center; +} + +.o_Activity_toolButton { + padding-top: map-get($spacers, 0); +} + +.o_Activity_tools { + display: flex; +} + +.o_Activity_user { + height: 36px; + margin-left: map-get($spacers, 2); + margin-right: map-get($spacers, 2); + position: relative; + width: 36px; +} + +.o_Activity_userAvatar { + height: map-get($sizes, 100); + width: map-get($sizes, 100); +} + +// From python template +.o_mail_note_title { + margin-top: map-get($spacers, 2); +} + +.o_mail_note_title + div p { + margin-bottom: 0; +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +$o-mail-activity-default-color: gray('300') !default; +$o-mail-activity-overdue-color: darken(theme-color('danger'), 10%) !default; +$o-mail-activity-planned-color: darken(theme-color('success'), 10%) !default; +$o-mail-activity-today-color: darken(theme-color('warning'), 10%) !default; + + +.o_Activity_deadlineDateText { + &.o-default { + color: $o-mail-activity-default-color; + } + + &.o-overdue { + color: $o-mail-activity-overdue-color; + } + + &.o-planned { + color: $o-mail-activity-planned-color; + } + + &.o-today { + color: $o-mail-activity-today-color; + } +} + +.o_Activity_details { + color: gray('500'); +} + +.o_Activity_detailsCreatorAvatar { + margin-inline-start: map-get($spacers, 2); +} + +.o_Activity_detailsUserAvatar { + border-radius: 50%; +} + +.o_Activity_dueDateText { + font-weight: bolder; + + &.o-default { + color: $o-mail-activity-default-color; + } + + &.o-overdue { + color: $o-mail-activity-overdue-color; + } + + &.o-planned { + color: $o-mail-activity-planned-color; + } + + &.o-today { + color: $o-mail-activity-today-color; + } +} + +/* Needed specifity to counter default bootstrap style */ +a:not([href]):not([tabindex]).o_Activity_detailsButton { + background: transparent; + opacity: 0.5; + color: gray('500'); + + &:hover { + opacity: 1; + color: gray('600'); + } +} + +.o_Activity_detailsCreator { + font-weight: bold; +} + +.o_Activity_iconContainer { + color: white; + border-color: white; + border-radius: 100%; + border-style: solid; +} + +.o_Activity_sidebar { + font-size: smaller; +} + +.o_Activity_summary { + font-weight: bolder; + color: gray('900'); +} + +.o_Activity_toolButton { + opacity: 0.5; + color: gray('500'); + + &:hover { + opacity: 1; + color: gray('600'); + } +} + +.o_Activity_userAvatar { + border-radius: 50%; +} + +.o_Activity_userName { + color: gray('500'); +} diff --git a/addons/mail/static/src/components/activity/activity.xml b/addons/mail/static/src/components/activity/activity.xml new file mode 100644 index 00000000..e5e9832c --- /dev/null +++ b/addons/mail/static/src/components/activity/activity.xml @@ -0,0 +1,153 @@ + + + + +
+ +
+
+ + + +
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ + + +
+ + +
+
+ +
Activity type
+
+ +
+
+ +
Created
+
+ + + + + +
+
+ +
Assigned to
+
+ + +
+
+
Due on
+
+ + + +
+
+
+
+ + +
+ +
+
+ + +
+ + + +
+
+ + +
+ + + + + + + + + + + + + + +
+
+
+
+
+
+ +
diff --git a/addons/mail/static/src/components/activity/activity_tests.js b/addons/mail/static/src/components/activity/activity_tests.js new file mode 100644 index 00000000..1c260f07 --- /dev/null +++ b/addons/mail/static/src/components/activity/activity_tests.js @@ -0,0 +1,1157 @@ +odoo.define('mail/static/src/components/activity/activity_tests.js', function (require) { +'use strict'; + +const components = { + Activity: require('mail/static/src/components/activity/activity.js'), +}; + +const { + afterEach, + afterNextRender, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const Bus = require('web.Bus'); +const { date_to_str } = require('web.time'); + +const { Component, tags: { xml } } = owl; + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('activity', {}, function () { +QUnit.module('activity_tests.js', { + beforeEach() { + beforeEach(this); + + this.createActivityComponent = async function (activity) { + await createRootComponent(this, components.Activity, { + props: { activityLocalId: activity.localId }, + target: this.widget.el, + }); + }; + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + data: this.data, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('activity simplest layout', async function (assert) { + assert.expect(12); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_sidebar').length, + 1, + "should have activity sidebar" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_core').length, + 1, + "should have activity core" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_user').length, + 1, + "should have activity user" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_info').length, + 1, + "should have activity info" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_note').length, + 0, + "should not have activity note" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_details').length, + 0, + "should not have activity details" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_mailTemplates').length, + 0, + "should not have activity mail templates" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_editButton').length, + 0, + "should not have activity Edit button" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_cancelButton').length, + 0, + "should not have activity Cancel button" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_markDoneButton').length, + 0, + "should not have activity Mark as Done button" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_uploadButton').length, + 0, + "should not have activity Upload button" + ); +}); + +QUnit.test('activity with note layout', async function (assert) { + assert.expect(3); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + id: 12, + note: 'There is no good or bad note', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_note').length, + 1, + "should have activity note" + ); + assert.strictEqual( + document.querySelector('.o_Activity_note').textContent, + "There is no good or bad note", + "activity note should be 'There is no good or bad note'" + ); +}); + +QUnit.test('activity info layout when planned after tomorrow', async function (assert) { + assert.expect(4); + + await this.start(); + const today = new Date(); + const fiveDaysFromNow = new Date(); + fiveDaysFromNow.setDate(today.getDate() + 5); + const activity = this.env.models['mail.activity'].create({ + dateDeadline: date_to_str(fiveDaysFromNow), + id: 12, + state: 'planned', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_dueDateText').length, + 1, + "should have activity delay" + ); + assert.ok( + document.querySelector('.o_Activity_dueDateText').classList.contains('o-planned'), + "activity delay should have the right color modifier class (planned)" + ); + assert.strictEqual( + document.querySelector('.o_Activity_dueDateText').textContent, + "Due in 5 days:", + "activity delay should have 'Due in 5 days:' as label" + ); +}); + +QUnit.test('activity info layout when planned tomorrow', async function (assert) { + assert.expect(4); + + await this.start(); + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(today.getDate() + 1); + const activity = this.env.models['mail.activity'].create({ + dateDeadline: date_to_str(tomorrow), + id: 12, + state: 'planned', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_dueDateText').length, + 1, + "should have activity delay" + ); + assert.ok( + document.querySelector('.o_Activity_dueDateText').classList.contains('o-planned'), + "activity delay should have the right color modifier class (planned)" + ); + assert.strictEqual( + document.querySelector('.o_Activity_dueDateText').textContent, + 'Tomorrow:', + "activity delay should have 'Tomorrow:' as label" + ); +}); + +QUnit.test('activity info layout when planned today', async function (assert) { + assert.expect(4); + + await this.start(); + const today = new Date(); + const activity = this.env.models['mail.activity'].create({ + dateDeadline: date_to_str(today), + id: 12, + state: 'today', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_dueDateText').length, + 1, + "should have activity delay" + ); + assert.ok( + document.querySelector('.o_Activity_dueDateText').classList.contains('o-today'), + "activity delay should have the right color modifier class (today)" + ); + assert.strictEqual( + document.querySelector('.o_Activity_dueDateText').textContent, + "Today:", + "activity delay should have 'Today:' as label" + ); +}); + +QUnit.test('activity info layout when planned yesterday', async function (assert) { + assert.expect(4); + + await this.start(); + const today = new Date(); + const yesterday = new Date(); + yesterday.setDate(today.getDate() - 1); + const activity = this.env.models['mail.activity'].create({ + dateDeadline: date_to_str(yesterday), + id: 12, + state: 'overdue', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_dueDateText').length, + 1, + "should have activity delay" + ); + assert.ok( + document.querySelector('.o_Activity_dueDateText').classList.contains('o-overdue'), + "activity delay should have the right color modifier class (overdue)" + ); + assert.strictEqual( + document.querySelector('.o_Activity_dueDateText').textContent, + "Yesterday:", + "activity delay should have 'Yesterday:' as label" + ); +}); + +QUnit.test('activity info layout when planned before yesterday', async function (assert) { + assert.expect(4); + + await this.start(); + const today = new Date(); + const fiveDaysBeforeNow = new Date(); + fiveDaysBeforeNow.setDate(today.getDate() - 5); + const activity = this.env.models['mail.activity'].create({ + dateDeadline: date_to_str(fiveDaysBeforeNow), + id: 12, + state: 'overdue', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_dueDateText').length, + 1, + "should have activity delay" + ); + assert.ok( + document.querySelector('.o_Activity_dueDateText').classList.contains('o-overdue'), + "activity delay should have the right color modifier class (overdue)" + ); + assert.strictEqual( + document.querySelector('.o_Activity_dueDateText').textContent, + "5 days overdue:", + "activity delay should have '5 days overdue:' as label" + ); +}); + +QUnit.test('activity with a summary layout', async function (assert) { + assert.expect(4); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + id: 12, + summary: 'test summary', + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_summary').length, + 1, + "should have activity summary" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_type').length, + 0, + "should not have the activity type as summary" + ); + assert.strictEqual( + document.querySelector('.o_Activity_summary').textContent.trim(), + "“test summary”", + "should have the specific activity summary in activity summary" + ); +}); + +QUnit.test('activity without summary layout', async function (assert) { + assert.expect(5); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + type: [['insert', { id: 1, displayName: "Fake type" }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_type').length, + 1, + "activity details should have an activity type section" + ); + assert.strictEqual( + document.querySelector('.o_Activity_type').textContent.trim(), + "Fake type", + "activity details should have the activity type display name in type section" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_summary.o_Activity_type').length, + 1, + "should have activity type as summary" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_summary:not(.o_Activity_type)').length, + 0, + "should not have a specific summary" + ); +}); + +QUnit.test('activity details toggle', async function (assert) { + assert.expect(5); + + await this.start(); + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(today.getDate() + 1); + const activity = this.env.models['mail.activity'].create({ + creator: [['insert', { id: 1, display_name: "Admin" }]], + dateCreate: date_to_str(today), + dateDeadline: date_to_str(tomorrow), + id: 12, + state: 'planned', + thread: [['insert', { id: 42, model: 'res.partner' }]], + type: [['insert', { id: 1, displayName: "Fake type" }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_details').length, + 0, + "activity details should not be visible by default" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_detailsButton').length, + 1, + "activity should have a details button" + ); + + await afterNextRender(() => + document.querySelector('.o_Activity_detailsButton').click() + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_details').length, + 1, + "activity details should be visible after clicking on details button" + ); + + await afterNextRender(() => + document.querySelector('.o_Activity_detailsButton').click() + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_details').length, + 0, + "activity details should no longer be visible after clicking again on details button" + ); +}); + +QUnit.test('activity details layout', async function (assert) { + assert.expect(11); + + await this.start(); + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(today.getDate() + 1); + const activity = this.env.models['mail.activity'].create({ + assignee: [['insert', { id: 10, display_name: "Pauvre pomme" }]], + creator: [['insert', { id: 1, display_name: "Admin" }]], + dateCreate: date_to_str(today), + dateDeadline: date_to_str(tomorrow), + id: 12, + state: 'planned', + thread: [['insert', { id: 42, model: 'res.partner' }]], + type: [['insert', { id: 1, displayName: "Fake type" }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_userAvatar').length, + 1, + "should have activity user avatar" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_detailsButton').length, + 1, + "activity should have a details button" + ); + + await afterNextRender(() => + document.querySelector('.o_Activity_detailsButton').click() + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_details').length, + 1, + "activity details should be visible after clicking on details button" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_details .o_Activity_type').length, + 1, + "activity details should have type" + ); + assert.strictEqual( + document.querySelector('.o_Activity_details .o_Activity_type').textContent, + "Fake type", + "activity details type should be 'Fake type'" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_detailsCreation').length, + 1, + "activity details should have creation date " + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_detailsCreator').length, + 1, + "activity details should have creator" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_detailsAssignation').length, + 1, + "activity details should have assignation information" + ); + assert.strictEqual( + document.querySelector('.o_Activity_detailsAssignation').textContent.indexOf('Pauvre pomme'), + 0, + "activity details assignation information should contain creator display name" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_detailsAssignationUserAvatar').length, + 1, + "activity details should have user avatar" + ); +}); + +QUnit.test('activity with mail template layout', async function (assert) { + assert.expect(8); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + id: 12, + mailTemplates: [['insert', { id: 1, name: "Dummy mail template" }]], + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_sidebar').length, + 1, + "should have activity sidebar" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_mailTemplates').length, + 1, + "should have activity mail templates" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_mailTemplate').length, + 1, + "should have activity mail template" + ); + assert.strictEqual( + document.querySelectorAll('.o_MailTemplate_name').length, + 1, + "should have activity mail template name" + ); + assert.strictEqual( + document.querySelector('.o_MailTemplate_name').textContent, + "Dummy mail template", + "should have activity mail template name" + ); + assert.strictEqual( + document.querySelectorAll('.o_MailTemplate_preview').length, + 1, + "should have activity mail template name preview button" + ); + assert.strictEqual( + document.querySelectorAll('.o_MailTemplate_send').length, + 1, + "should have activity mail template name send button" + ); +}); + +QUnit.test('activity with mail template: preview mail', async function (assert) { + assert.expect(10); + + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action.context.default_res_id, + 42, + 'Action should have the activity res id as default res id in context' + ); + assert.strictEqual( + payload.action.context.default_model, + 'res.partner', + 'Action should have the activity res model as default model in context' + ); + assert.ok( + payload.action.context.default_use_template, + 'Action should have true as default use_template in context' + ); + assert.strictEqual( + payload.action.context.default_template_id, + 1, + 'Action should have the selected mail template id as default template id in context' + ); + assert.strictEqual( + payload.action.type, + "ir.actions.act_window", + 'Action should be of type "ir.actions.act_window"' + ); + assert.strictEqual( + payload.action.res_model, + "mail.compose.message", + 'Action should have "mail.compose.message" as res_model' + ); + }); + + await this.start({ env: { bus } }); + const activity = this.env.models['mail.activity'].create({ + id: 12, + mailTemplates: [['insert', { + id: 1, + name: "Dummy mail template", + }]], + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_MailTemplate_preview').length, + 1, + "should have activity mail template name preview button" + ); + + document.querySelector('.o_MailTemplate_preview').click(); + assert.verifySteps( + ['do_action'], + "should have called 'compose email' action correctly" + ); +}); + +QUnit.test('activity with mail template: send mail', async function (assert) { + assert.expect(7); + + await this.start({ + async mockRPC(route, args) { + if (args.method === 'activity_send_mail') { + assert.step('activity_send_mail'); + assert.strictEqual(args.args[0].length, 1); + assert.strictEqual(args.args[0][0], 42); + assert.strictEqual(args.args[1], 1); + return; + } else { + return this._super(...arguments); + } + }, + }); + const activity = this.env.models['mail.activity'].create({ + id: 12, + mailTemplates: [['insert', { + id: 1, + name: "Dummy mail template", + }]], + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_MailTemplate_send').length, + 1, + "should have activity mail template name send button" + ); + + document.querySelector('.o_MailTemplate_send').click(); + assert.verifySteps( + ['activity_send_mail'], + "should have called activity_send_mail rpc" + ); +}); + +QUnit.test('activity upload document is available', async function (assert) { + assert.expect(3); + + await this.start(); + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(today.getDate() + 1); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_uploadButton').length, + 1, + "should have activity upload button" + ); + assert.strictEqual( + document.querySelectorAll('.o_FileUploader').length, + 1, + "should have a file uploader" + ); +}); + +QUnit.test('activity click on mark as done', async function (assert) { + assert.expect(4); + + await this.start(); + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(today.getDate() + 1); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_markDoneButton').length, + 1, + "should have activity Mark as Done button" + ); + + await afterNextRender(() => { + document.querySelector('.o_Activity_markDoneButton').click(); + }); + assert.strictEqual( + document.querySelectorAll('.o_ActivityMarkDonePopover').length, + 1, + "should have opened the mark done popover" + ); + + await afterNextRender(() => { + document.querySelector('.o_Activity_markDoneButton').click(); + }); + assert.strictEqual( + document.querySelectorAll('.o_ActivityMarkDonePopover').length, + 0, + "should have closed the mark done popover" + ); +}); + +QUnit.test('activity mark as done popover should focus feedback input on open [REQUIRE FOCUS]', async function (assert) { + assert.expect(3); + + await this.start(); + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(today.getDate() + 1); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + + assert.containsOnce( + document.body, + '.o_Activity', + "should have activity component" + ); + assert.containsOnce( + document.body, + '.o_Activity_markDoneButton', + "should have activity Mark as Done button" + ); + + await afterNextRender(() => { + document.querySelector('.o_Activity_markDoneButton').click(); + }); + assert.strictEqual( + document.querySelector('.o_ActivityMarkDonePopover_feedback'), + document.activeElement, + "the popover textarea should have the focus" + ); +}); + +QUnit.test('activity click on edit', async function (assert) { + assert.expect(9); + + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action.context.default_res_id, + 42, + 'Action should have the activity res id as default res id in context' + ); + assert.strictEqual( + payload.action.context.default_res_model, + 'res.partner', + 'Action should have the activity res model as default res model in context' + ); + assert.strictEqual( + payload.action.type, + "ir.actions.act_window", + 'Action should be of type "ir.actions.act_window"' + ); + assert.strictEqual( + payload.action.res_model, + "mail.activity", + 'Action should have "mail.activity" as res_model' + ); + assert.strictEqual( + payload.action.res_id, + 12, + 'Action should have activity id as res_id' + ); + }); + + await this.start({ env: { bus } }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + id: 12, + mailTemplates: [['insert', { id: 1, name: "Dummy mail template" }]], + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_editButton').length, + 1, + "should have activity edit button" + ); + + document.querySelector('.o_Activity_editButton').click(); + assert.verifySteps( + ['do_action'], + "should have called 'schedule activity' action correctly" + ); +}); + +QUnit.test('activity edition', async function (assert) { + assert.expect(14); + + this.data['mail.activity'].records.push({ + can_write: true, + icon: 'fa-times', + id: 12, + res_id: 42, + res_model: 'res.partner', + }); + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action.context.default_res_id, + 42, + 'Action should have the activity res id as default res id in context' + ); + assert.strictEqual( + payload.action.context.default_res_model, + 'res.partner', + 'Action should have the activity res model as default res model in context' + ); + assert.strictEqual( + payload.action.type, + 'ir.actions.act_window', + 'Action should be of type "ir.actions.act_window"' + ); + assert.strictEqual( + payload.action.res_model, + 'mail.activity', + 'Action should have "mail.activity" as res_model' + ); + assert.strictEqual( + payload.action.res_id, + 12, + 'Action should have activity id as res_id' + ); + this.data['mail.activity'].records[0].icon = 'fa-check'; + payload.options.on_close(); + }); + + await this.start({ env: { bus } }); + const activity = this.env.models['mail.activity'].insert( + this.env.models['mail.activity'].convertData( + this.data['mail.activity'].records[0] + ) + ); + await this.createActivityComponent(activity); + + assert.containsOnce( + document.body, + '.o_Activity', + "should have activity component" + ); + assert.containsOnce( + document.body, + '.o_Activity_editButton', + "should have activity edit button" + ); + assert.containsOnce( + document.body, + '.o_Activity_icon', + "should have activity icon" + ); + assert.containsOnce( + document.body, + '.o_Activity_icon.fa-times', + "should have initial activity icon" + ); + assert.containsNone( + document.body, + '.o_Activity_icon.fa-check', + "should not have new activity icon when not edited yet" + ); + + await afterNextRender(() => { + document.querySelector('.o_Activity_editButton').click(); + }); + assert.verifySteps( + ['do_action'], + "should have called 'schedule activity' action correctly" + ); + assert.containsNone( + document.body, + '.o_Activity_icon.fa-times', + "should no more have initial activity icon once edited" + ); + assert.containsOnce( + document.body, + '.o_Activity_icon.fa-check', + "should now have new activity icon once edited" + ); +}); + +QUnit.test('activity click on cancel', async function (assert) { + assert.expect(7); + + await this.start({ + async mockRPC(route, args) { + if (route === '/web/dataset/call_kw/mail.activity/unlink') { + assert.step('unlink'); + assert.strictEqual(args.args[0].length, 1); + assert.strictEqual(args.args[0][0], 12); + return; + } else { + return this._super(...arguments); + } + }, + }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + id: 12, + mailTemplates: [['insert', { + id: 1, + name: "Dummy mail template", + }]], + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + + // Create a parent component to surround the Activity component in order to be able + // to check that activity component has been destroyed + class ParentComponent extends Component { + constructor(...args) { + super(... args); + useStore(props => { + const activity = this.env.models['mail.activity'].get(props.activityLocalId); + return { + activity: activity ? activity.__state : undefined, + }; + }); + } + + /** + * @returns {mail.activity} + */ + get activity() { + return this.env.models['mail.activity'].get(this.props.activityLocalId); + } + } + ParentComponent.env = this.env; + Object.assign(ParentComponent, { + components, + props: { activityLocalId: String }, + template: xml` +
+

parent

+ + + +
+ `, + }); + await createRootComponent(this, ParentComponent, { + props: { activityLocalId: activity.localId }, + target: this.widget.el, + }); + + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 1, + "should have activity component" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity_cancelButton').length, + 1, + "should have activity cancel button" + ); + + await afterNextRender(() => + document.querySelector('.o_Activity_cancelButton').click() + ); + assert.verifySteps( + ['unlink'], + "should have called unlink rpc after clicking on cancel" + ); + assert.strictEqual( + document.querySelectorAll('.o_Activity').length, + 0, + "should no longer display activity after clicking on cancel" + ); +}); + +QUnit.test('activity mark done popover close on ESCAPE', async function (assert) { + // This test is not in activity_mark_done_popover_tests.js as it requires the activity mark done + // component to have a parent in order to allow testing interactions the popover. + assert.expect(2); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + + await this.createActivityComponent(activity); + await afterNextRender(() => { + document.querySelector('.o_Activity_markDoneButton').click(); + }); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover', + "Popover component should be present" + ); + + await afterNextRender(() => { + const ev = new window.KeyboardEvent('keydown', { bubbles: true, key: "Escape" }); + document.querySelector(`.o_ActivityMarkDonePopover`).dispatchEvent(ev); + }); + assert.containsNone( + document.body, + '.o_ActivityMarkDonePopover', + "ESCAPE pressed should have closed the mark done popover" + ); +}); + +QUnit.test('activity mark done popover click on discard', async function (assert) { + // This test is not in activity_mark_done_popover_tests.js as it requires the activity mark done + // component to have a parent in order to allow testing interactions the popover. + assert.expect(3); + + await this.start(); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + await afterNextRender(() => { + document.querySelector('.o_Activity_markDoneButton').click(); + }); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover', + "Popover component should be present" + ); + assert.containsOnce( + document.body, + '.o_ActivityMarkDonePopover_discardButton', + "Popover component should contain the discard button" + ); + await afterNextRender(() => + document.querySelector('.o_ActivityMarkDonePopover_discardButton').click() + ); + assert.containsNone( + document.body, + '.o_ActivityMarkDonePopover', + "Discard button clicked should have closed the mark done popover" + ); +}); + +QUnit.test('data-oe-id & data-oe-model link redirection on click', async function (assert) { + assert.expect(7); + + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.strictEqual( + payload.action.type, + 'ir.actions.act_window', + "action should open view" + ); + assert.strictEqual( + payload.action.res_model, + 'some.model', + "action should open view on 'some.model' model" + ); + assert.strictEqual( + payload.action.res_id, + 250, + "action should open view on 250" + ); + assert.step('do-action:openFormView_some.model_250'); + }); + await this.start({ env: { bus } }); + const activity = this.env.models['mail.activity'].create({ + canWrite: true, + category: 'not_upload_file', + id: 12, + note: `

some.model_250

`, + thread: [['insert', { id: 42, model: 'res.partner' }]], + }); + await this.createActivityComponent(activity); + assert.containsOnce( + document.body, + '.o_Activity_note', + "activity should have a note" + ); + assert.containsOnce( + document.querySelector('.o_Activity_note'), + 'a', + "activity note should have a link" + ); + + document.querySelector(`.o_Activity_note a`).click(); + assert.verifySteps( + ['do-action:openFormView_some.model_250'], + "should have open form view on related record after click on link" + ); +}); + +}); +}); +}); + +}); diff --git a/addons/mail/static/src/components/activity_box/activity_box.js b/addons/mail/static/src/components/activity_box/activity_box.js new file mode 100644 index 00000000..ca191694 --- /dev/null +++ b/addons/mail/static/src/components/activity_box/activity_box.js @@ -0,0 +1,64 @@ +odoo.define('mail/static/src/components/activity_box/activity_box.js', function (require) { +'use strict'; + +const components = { + Activity: require('mail/static/src/components/activity/activity.js'), +}; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; + +class ActivityBox extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + useStore(props => { + const chatter = this.env.models['mail.chatter'].get(props.chatterLocalId); + const thread = chatter && chatter.thread; + return { + chatter: chatter ? chatter.__state : undefined, + thread: thread && thread.__state, + }; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {Chatter} + */ + get chatter() { + return this.env.models['mail.chatter'].get(this.props.chatterLocalId); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onClickTitle() { + this.chatter.toggleActivityBoxVisibility(); + } + +} + +Object.assign(ActivityBox, { + components, + props: { + chatterLocalId: String, + }, + template: 'mail.ActivityBox', +}); + +return ActivityBox; + +}); diff --git a/addons/mail/static/src/components/activity_box/activity_box.scss b/addons/mail/static/src/components/activity_box/activity_box.scss new file mode 100644 index 00000000..64e99347 --- /dev/null +++ b/addons/mail/static/src/components/activity_box/activity_box.scss @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_ActivityBox_title { + display: flex; + align-items: center; + flex: 0 0 auto; + margin-top: map-get($spacers, 4); + margin-bottom: map-get($spacers, 4); +} + +.o_ActivityBox_titleBadge { + padding: map-get($spacers, 0) map-get($spacers, 2); +} + +.o_ActivityBox_titleBadges { + margin-inline-end: map-get($spacers, 3); +} + +.o_ActivityBox_titleLine { + flex: 1 1 auto; + width: auto; +} + +.o_ActivityBox_titleText { + margin: map-get($spacers, 0) map-get($spacers, 3); +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +.o_ActivityBox_title { + font-weight: bold; +} + +.o_ActivityBox_titleBadge { + font-size: 11px; +} + +.o_ActivityBox_titleLine { + border-color: gray('400'); + border-style: dashed; +} diff --git a/addons/mail/static/src/components/activity_box/activity_box.xml b/addons/mail/static/src/components/activity_box/activity_box.xml new file mode 100644 index 00000000..900b5634 --- /dev/null +++ b/addons/mail/static/src/components/activity_box/activity_box.xml @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js new file mode 100644 index 00000000..de1ea5ce --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js @@ -0,0 +1,122 @@ +odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js', function (require) { +'use strict'; + +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; +const { useRef } = owl.hooks; + +class ActivityMarkDonePopover extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + useStore(props => { + const activity = this.env.models['mail.activity'].get(props.activityLocalId); + return { + activity: activity ? activity.__state : undefined, + }; + }); + this._feedbackTextareaRef = useRef('feedbackTextarea'); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + mounted() { + this._feedbackTextareaRef.el.focus(); + if (this.activity.feedbackBackup) { + this._feedbackTextareaRef.el.value = this.activity.feedbackBackup; + } + } + + /** + * @returns {mail.activity} + */ + get activity() { + return this.env.models['mail.activity'].get(this.props.activityLocalId); + } + + /** + * @returns {string} + */ + get DONE_AND_SCHEDULE_NEXT() { + return this.env._t("Done & Schedule Next"); + } + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _close() { + this.trigger('o-popover-close'); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onBlur() { + this.activity.update({ + feedbackBackup: this._feedbackTextareaRef.el.value, + }); + } + + /** + * @private + */ + _onClickDiscard() { + this._close(); + } + + /** + * @private + */ + async _onClickDone() { + await this.activity.markAsDone({ + feedback: this._feedbackTextareaRef.el.value, + }); + this.trigger('reload', { keepChanges: true }); + } + + /** + * @private + */ + _onClickDoneAndScheduleNext() { + this.activity.markAsDoneAndScheduleNext({ + feedback: this._feedbackTextareaRef.el.value, + }); + } + + /** + * @private + */ + _onKeydown(ev) { + if (ev.key === 'Escape') { + this._close(); + } + } + +} + +Object.assign(ActivityMarkDonePopover, { + props: { + activityLocalId: String, + }, + template: 'mail.ActivityMarkDonePopover', +}); + +return ActivityMarkDonePopover; + +}); diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss new file mode 100644 index 00000000..3479ffc3 --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_ActivityMarkDonePopover { + min-height: 100px; +} + +.o_ActivityMarkDonePopover_buttons { + margin-top: map-get($spacers, 2); +} + +.o_ActivityMarkDonePopover_doneButton { + margin: map-get($spacers, 0) map-get($spacers, 2); +} + +.o_ActivityMarkDonePopover_feedback { + min-height: 70px; +} + diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml new file mode 100644 index 00000000..357ab59b --- /dev/null +++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml @@ -0,0 +1,23 @@ + + + + +
+ +