From b6fc2b7d105ff2663aebb55b2643142897840ed0 Mon Sep 17 00:00:00 2001 From: "J.-S. Caux" <J.S.Caux@uva.nl> Date: Sat, 8 Feb 2020 14:25:07 +0100 Subject: [PATCH] Add TagListEditable --- apimail/api/serializers.py | 2 +- apimail/api/views.py | 23 +++ .../assets/vue/components/MessagesTable.vue | 62 +++++-- .../assets/vue/components/TagListEditable.vue | 154 ++++++++++++++++++ apimail/urls.py | 10 ++ 5 files changed, 235 insertions(+), 16 deletions(-) create mode 100644 apimail/static/apimail/assets/vue/components/TagListEditable.vue diff --git a/apimail/api/serializers.py b/apimail/api/serializers.py index 7961710d1..691898784 100644 --- a/apimail/api/serializers.py +++ b/apimail/api/serializers.py @@ -81,7 +81,7 @@ class EventSerializer(serializers.ModelSerializer): class UserTagSerializer(serializers.ModelSerializer): class Meta: model = UserTag - fields = ['pk', 'label', 'unicode_symbol', 'variant'] + fields = ['pk', 'user', 'label', 'unicode_symbol', 'variant'] def get_queryset(self): user = self.request.user diff --git a/apimail/api/views.py b/apimail/api/views.py index 04855345d..0f6c4f144 100644 --- a/apimail/api/views.py +++ b/apimail/api/views.py @@ -229,6 +229,29 @@ class StoredMessageUpdateReadAPIView(UpdateAPIView): return Response() +class UserTagCreateAPIView(CreateAPIView): + permission_classes = (IsAuthenticated,) + queryset = UserTag.objects.all() + serializer_class = UserTagSerializer + + def create(self, request, *args, **kwargs): + data = request.data + data['user'] = request.user.id + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + + +class UserTagDestroyAPIView(DestroyAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = UserTagSerializer + + def get_queryset(self): + return UserTag.objects.filter(user=self.request.user) + + class UserTagListAPIView(ListAPIView): permission_classes = (IsAuthenticated,) serializer_class = UserTagSerializer diff --git a/apimail/static/apimail/assets/vue/components/MessagesTable.vue b/apimail/static/apimail/assets/vue/components/MessagesTable.vue index e8937341c..242d85312 100644 --- a/apimail/static/apimail/assets/vue/components/MessagesTable.vue +++ b/apimail/static/apimail/assets/vue/components/MessagesTable.vue @@ -17,6 +17,19 @@ </template> </b-modal> + <b-modal + id="modal-manage-tags" + title="Manage your Tags" + hide-header-close + > + <tag-list-editable :tags="tags" @fetchtags="fetchTags"></tag-list-editable> + <template v-slot:modal-footer="{ close, }"> + <b-button size="sm" variant="danger" @click="close()"> + Done + </b-button> + </template> + </b-modal> + <div v-if="draftMessages.length > 0" class="m-2 mb-4"> <h2>Message drafts to complete</h2> @@ -130,7 +143,7 @@ </b-form-radio-group> </b-form-group> </b-col> - <b-col class="col-lg-6"> + <b-col class="col-lg-5"> <b-form-group label="Tag:" label-cols-sm="3" @@ -147,6 +160,14 @@ </b-form-radio-group> </b-form-group> </b-col> + <b-col class="col-lg-1"> + <b-button + size="sm" + @click="showManageTagsModal" + > + <small>Manage your tags</small> + </b-button> + </b-col> </b-row> <hr> <b-row class="mb-0"> @@ -273,6 +294,8 @@ import MessageContent from './MessageContent.vue' import MessageComposer from './MessageComposer.vue' +import TagListEditable from './TagListEditable.vue' + var csrftoken = Cookies.get('csrftoken'); export default { @@ -280,6 +303,7 @@ export default { components: { MessageContent, MessageComposer, + TagListEditable, }, data() { return { @@ -317,6 +341,7 @@ export default { { text: 'read', value: true }, { text: 'all', value: null }, ], + tags: null, tagRequired: 'any', } }, @@ -339,25 +364,30 @@ export default { .then(data => this.draftMessages = data.results) .catch(error => console.error(error)) }, + showManageTagsModal () { + this.$bvModal.show('modal-manage-tags') + }, showReworkDraftModal (draftmsg) { this.draftMessageSelected = draftmsg this.$bvModal.show('modal-resumedraft') }, deleteDraft (uuid) { - fetch('/mail/api/composed_message/' + uuid + '/delete', - { - method: 'DELETE', - headers: { - "X-CSRFToken": csrftoken, + if (confirm("Are you sure you want to delete this draft?")) { + fetch('/mail/api/composed_message/' + uuid + '/delete', + { + method: 'DELETE', + headers: { + "X-CSRFToken": csrftoken, + } } - } - ) - .then(response => { - if (response.ok) { - this.fetchDrafts() - } - }) - .catch(error => console.error(error)) + ) + .then(response => { + if (response.ok) { + this.fetchDrafts() + } + }) + .catch(error => console.error(error)) + } }, isSelected: function (selection) { return selection === this.accountSelected @@ -417,7 +447,9 @@ export default { this.fetchTags() this.fetchDrafts() this.$root.$on('bv::modal::hide', (bvEvent, modalId) => { - this.fetchDrafts() + if (bvEvent.componentId === 'modal-resumedraft') { + this.fetchDrafts() + } }) }, watch: { diff --git a/apimail/static/apimail/assets/vue/components/TagListEditable.vue b/apimail/static/apimail/assets/vue/components/TagListEditable.vue new file mode 100644 index 000000000..8149bfba2 --- /dev/null +++ b/apimail/static/apimail/assets/vue/components/TagListEditable.vue @@ -0,0 +1,154 @@ +<template> +<div> + <h3>Your current tags:</h3> + <table class="table"> + <tr v-for="tag in tags" class="mb-4"> + <td> + <b-button + size="sm" + class="p-1" + :variant="tag.variant" + > + {{ tag.unicode_symbol }} + </b-button> + </td> + <td>{{ tag.label }}</td> + <td> + <b-button class="float-right bg-danger text-white px-1 py-0" @click.stop="deleteTag(tag.pk)"> + <small>Delete</small> + </b-button> + </td> + </tr> + </table> + <h3>Create a new tag:</h3> + <b-form + > + <b-form-group + id="label" + label="Label:" + label-for="input-label" + > + <b-form-input + id="input-label" + v-model="newTagForm.label" + required + placeholder="Enter a label for your new Tag" + > + </b-form-input> + </b-form-group> + <b-form-group + id="unicode_symbol" + label="Unicode symbol:" + label-for="input-unicode-symbol" + > + <b-form-input + id="input-unicode-symbol" + v-model="newTagForm.unicode_symbol" + required + placeholder="Enter a single (arbitrary) unicode character" + > + </b-form-input> + </b-form-group> + <b-form-group + id="variant" + label="Variant:" + label-for="input-variant" + > + <b-form-select + id="input-variant" + :options="variantOptions" + v-model="newTagForm.variant" + > + </b-form-select> + </b-form-group> + </b-form> + <b-button + variant="success" + class="text-white" + @click.stop.prevent="createNewTag" + > + Create new Tag + </b-button> +</div> +</template> + +<script> +import Cookies from 'js-cookie' + +var csrftoken = Cookies.get('csrftoken'); + +export default { + props: { + tags: { + type: Array, + required: false, + }, + }, + data() { + return { + newTagForm: { + label: null, + unicode_symbol: null, + variant: null + }, + variantOptions: [ + { text: 'primary', value: 'primary' }, + { text: 'secondary', value: 'secondary' }, + { text: 'success', value: 'success' }, + { text: 'warning', value: 'warning' }, + { text: 'danger', value: 'danger' }, + { text: 'info', value: 'info' }, + { text: 'light', value: 'light' }, + { text: 'dark', value: 'dark' }, + ] + } + }, + methods: { + createNewTag () { + fetch('/mail/api/user_tag/create', + { + method: 'POST', + headers: { + "X-CSRFToken": csrftoken, + "Content-Type": "application/json; charset=utf-8" + }, + body: JSON.stringify({ + 'label': this.newTagForm.label, + 'unicode_symbol': this.newTagForm.unicode_symbol, + 'variant': this.newTagForm.variant, + }) + }) + .then(response => { + if (response.ok) { + this.newTagForm.label = null, + this.newTagForm.unicode_symbol = null, + this.newTagForm.variant = null + this.$emit('fetchtags') + } + else { + console.log(response.data) + } + }) + .catch(error => console.error(error)) + }, + deleteTag (pk) { + if (confirm("Do you really want to delete this tag? " + + "It will be immediately removed from all messages.")) { + fetch('/mail/api/user_tag/' + pk + '/delete', + { + method: 'DELETE', + headers: { + "X-CSRFToken": csrftoken, + } + }) + .then(response => { + if (response.ok) { + this.$emit('fetchtags') + } + }) + .catch(error => console.error(error)) + } + } + } +} +</script> diff --git a/apimail/urls.py b/apimail/urls.py index c87ba7294..eb15453af 100644 --- a/apimail/urls.py +++ b/apimail/urls.py @@ -82,6 +82,16 @@ urlpatterns = [ apiviews.StoredMessageUpdateTagAPIView.as_view(), name='api_stored_message_tag' ), + path( # /mail/api/user_tag/create + 'user_tag/create', + apiviews.UserTagCreateAPIView.as_view(), + name='user_tag_create' + ), + path( # /mail/api/user_tag/<pk>/delete + 'user_tag/<int:pk>/delete', + apiviews.UserTagDestroyAPIView.as_view(), + name='user_tag_delete' + ), path( # /mail/api/user_tags 'user_tags', apiviews.UserTagListAPIView.as_view(), -- GitLab