Skip to content
Snippets Groups Projects
MessagesList.vue 11.1 KiB
Newer Older
<template>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
    <div>
        <table class="default" id="messages">
            <caption>{{ messageListName }}</caption>
            <colgroup>
                <col class="hidden-small-down">
                <col>
                <col class="hidden-small-down">
                <col style="width: 20ex">
                <col class="hidden-small-down">
            </colgroup>
            <thead>
            <tr>
                <th class="hidden-small-down">
                    <input type="checkbox" @click="setCheckboxesByProxy"
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                           :disabled="allMessages.length === 0"
                           :checked="selected.length === allMessages.length"
                           :indeterminate.prop="selected.length > 0 && selected.length < allMessages.length">
                </th>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                <th>{{ $gettext('Betreff') }}</th>
                <th class="hidden-small-down">
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                    <template v-if="type === 'inbox'">{{ $gettext('Absender') }}</template>
                    <template v-else>{{ $gettext('Empfänger') }}</template>
                </th>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                <th>{{ $gettext('Zeit') }}</th>
                <th class="hidden-small-down">
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                    {{ $gettext('Schlagworte') }}
                </th>
            </tr>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            </thead>
            <transition-group name="fade" tag="tbody" aria-relevant="additions" aria-live="polite">
                <tr v-for="message in allMessages" :key="`message-${message.id}`"
                    :id="`message_${message.id}`"
                    :class="isMessageRead(message) ? 'read' : 'unread'"
                    draggable="true"
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                    @dragstart="dragStart(message, $event)" @dragend="dragStop()"
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                >
                    <td class="hidden-small-down">
                        <input type="checkbox" :checked="messageIsSelected(message)" :value="message.id" @click="selectedMessage(message)">
                    </td>
                    <td class="title">
                        <a :href="getMessageURL(message)" data-dialog>
                            {{ message.attributes.subject }}
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                            <div class="message-indicators">
                            <span v-if="message.relationships.attachments.data.length > 0">
                                <studip-icon shape="staple" role="info" :size="20" :title="$gettext('Mit Anhang')"/>
                            </span>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                                <span v-if="message.attributes['is-answered']">
                                <studip-icon shape="outbox" role="info" :size="20" :title="$gettext('Beantwortet')"/>
                            </span>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                            </div>
                        </a>
                    </td>
                    <td v-if="type === 'inbox'" class="hidden-small-down">
                        <a v-if="message.relationships.sender" :href="getProfileURL(userById(message.relationships.sender.data.id))">
                            {{ userById(message.relationships.sender.data.id).attributes['formatted-name'] }}
                        </a>
                        <template v-else>{{ $gettext('Systemnachricht') }}</template>
                    </td>
                    <td v-else class="hidden-small-down">
                        <a v-for="recipient in message.relationships.recipients.data" :href="getProfileURL(userById(recipient.id))">
                            {{ userById(recipient.id).attributes['formatted-name'] }}
                        </a>
                    </td>
                    <td>
                        {{ showDate(message.attributes.mkdate) }}
                    </td>
                    <td class="tag-container hidden-small-down">
                        <a v-for="tag in message.attributes.tags" :key="`message-${message.attributes.id}-${tag}`"
                           class="message-tag"
                           :href="`?tag=${tag}`"
                           :title="$gettext('Alle Nachrichten zu diesem Schlagwort')">
                            {{ tag }}
                        </a>
                    </td>
                </tr>
            </transition-group>
        </table>

        <mounting-portal mount-to="#sidebar" append>
            <sidebar-widget :title="$gettext('Schlagwörter')">
                <template #content>
                    <ul class="widget-list widget-links sidebar-views">
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                        <li :class="{active: !selectedTag}">
                            <a href="#" @click.prevent="selectedTag = null">
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                                {{ $gettext('Alle Nachrichten') }}
                            </a>
                        </li>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                        <li v-for="tag in tags" :key="`tag-${tag}`"
                            :class="{active: selectedTag === tag, dropzone: isDragging, draggedover: draggedOverTag === tag}"
                            @drop="drop(tag, $event)"
                            @dragover.prevent="draggedOver(tag)" @dragenter.prevent
                            @dragleave="draggedOver(null)"
                        >
                            <a href="#" @click.prevent="selectedTag = tag">
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                                {{ tag }}
                            </a>
                        </li>
                    </ul>
                </template>
            </sidebar-widget>
        </mounting-portal>
    </div>
</template>
<script>
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
import SidebarWidget from "./SidebarWidget.vue";
import {LOAD_TAGS} from "../store/MessagesStore";
import {mapActions, mapGetters} from "vuex";

export default {
    name: 'messages-list',
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
    components: {SidebarWidget},
    props: {
        type: {
            type: String,
            required: true,
            validator (value) {
                return ['inbox', 'outbox'].includes(value);
            }
        }
    },
    data () {
        return {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            selectedTag: null,
            draggedOverTag: null,
            pageOptions: {
                offset: 0,
                limit: 30,
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            },
            observed: null,
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            observer: null,
            isDragging: false,
        }
    },
    methods: {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        ...mapActions({
            loadTags: LOAD_TAGS,
        }),
        // Unfortunately, this cannot be used since we cannot use the dyanmic prop
        //
        // ...mapActions({
        //     isLoading: `${this.type}/isLoading`,
        //     loadPage: `${this.type}/loadPage`,
        //     userById: `users/byId`,
        // }),
        isLoading() {
            return this.$store.getters[`${this.type}/isLoading`];
        },
        loadPage({options}) {
            return this.$store.dispatch(`${this.type}/loadPage`, {options});
        },
        userById(id) {
            return this.$store.getters[`users/byId`]({ id });
        },
        showDate (date) {
            const jsDate = new Date(date);
            const dateOptions = {
                day: '2-digit',
                month: '2-digit',
                year: 'numeric',
                hour: '2-digit',
                minute: '2-digit'
            };
            return jsDate.toLocaleDateString(String.locale, dateOptions) + ' ' + jsDate.toLocaleTimeString(String.locale, timeOptions);
        },
        setCheckboxesByProxy () {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            if (this.selected.length === this.allMessages.length) {
                this.selected = [];
            } else {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                this.selected = this.allMessages.map(message => message.id);
            }
        },
        messageIsSelected(message) {
            return this.selected.includes(message.id);
        },
        selectedMessage(message) {
            if (this.selected.includes(message.id)) {
                this.selected = this.selected.filter(m => m !== message.id);
            } else {
                this.selected.push(message.id);
            }
        },
        getMessageURL(message) {
            return STUDIP.URLHelper.getURL(`dispatch.php/messages/read/${message.id}`);
        },
        getProfileURL(user) {
            return STUDIP.URLHelper.getURL('dispatch.php/profile', {username: user.attributes.username});
        },
        isMessageRead(message) {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            return message.attributes['is-read'] || message.relationships.sender?.data.id === STUDIP.USER_ID;
        },
        loadMessages() {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            if (this.isLoading()) {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
                return;
            }

            let options = {
                'page[offset]': this.pageOptions.offset,
                'page[limit]': this.pageOptions.limit,
                include: this.type === 'inbox' ? 'sender' : 'recipients',
            }

            const lastMeta = this.$store.getters[`${this.type}/lastMeta`];
            if (lastMeta !== null) {
                if (lastMeta.page.offset + lastMeta.page.limit >= lastMeta.page.total) {
                    return;
                }

                options['page[offset]'] = lastMeta.page.offset + lastMeta.page.limit;
                options['page[limit]'] = lastMeta.page.limit;
            }

Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            this.loadPage({options});
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        },
        observe(element) {
            if (this.observer === null) {
                this.observer = new IntersectionObserver(entries => {
                    if (entries[0].intersectionRatio < 1) {
                        return;
                    }
                    this.loadMessages();
                }, {
                    rootMargin: `0px 0px ${screen.availHeight / 3}px 0px`,
                    threshold: 1.0
                })

            }

            if (this.observed) {
                this.observer.unobserve(this.observed);
            }

            this.observed = element;
            this.observer.observe(this.observed);
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        },
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        dragStart(message, event) {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            this.isDragging = true;
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed

            event.dataTransfer.dropEffect = 'move';
            event.dataTransfer.effectAllowed = 'move';
            event.dataTransfer.setData('messageId', message.id);
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        },
        dragStop(message) {
            this.isDragging = false;
        },
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        drop(tag, event) {
            const messageId = event.dataTransfer.getData('messageId');
            console.log('dropped message on tag', messageId, tag);
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        },
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        draggedOver(tag) {
            this.draggedOverTag = tag;
        }
    },
    computed: {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        ...mapGetters({
            tags: 'tags',
        }),
        // Unfortunately, this cannot be used since we cannot use the dyanmic prop
        //
        // ...mapGetters({
        //     allMessages: `${this.type}/all`
        // }),
        allMessages() {
            return this.$store.getters[`${this.type}/all`];
        },
        messageListName () {
            return this.type === 'inbox' ? this.$gettext('Eingang') : this.$gettext('Gesendet');
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        },
    },
    created () {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        this.loadMessages();
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        this.loadTags();
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
    },
    updated () {
        this.observe(this.$el.querySelector('tbody tr:last-of-type'));
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
<style scoped lang="scss">
.dropzone {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
    background-color: var(--red-20);
}
.dropzone.draggedover {
    background-color: var(--red-80);
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
}

Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
.fade-enter-active,
.fade-leave-active {
    transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
    opacity: 0;
}
</style>