123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- <template>
- <div class="dms-page-component">
- <div v-if="isLoaded" class="container-fluid mt-3">
- <div class="row">
- <div class="col-md-3 d-md-block">
- <sidebar :user="profile" />
- </div>
- <div class="col-md-5 offset-md-1 mb-5 order-2 order-md-1">
- <h1 class="font-weight-bold mb-4">Direct Messages</h1>
- <div v-if="threadsLoaded">
- <div v-for="(thread, idx) in threads" class="card shadow-sm mb-1" style="border-radius:15px;">
- <div class="card-body p-3">
- <div class="media">
- <img :src="thread.accounts[0].avatar" width="45" height="45" class="shadow-sm mr-3" style="border-radius: 15px;" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
- <div class="media-body">
- <!-- <p class="lead mb-n2">{{ thread.accounts[0].display_name }}</p> -->
- <div class="d-flex justify-content-between align-items-start mb-1">
- <p class="dm-display-name font-weight-bold mb-0">@{{ thread.accounts[0].acct }}</p>
- <p class="font-weight-bold small text-muted mb-0">{{ timeago(thread.last_status.created_at) }} ago</p>
- </div>
- <p class="dm-thread-summary text-muted mr-4" v-html="threadSummary(thread.last_status)"></p>
- </div>
- <router-link class="btn btn-link stretched-link align-self-center mr-n3" :to="`/i/web/direct/thread/${thread.accounts[0].id}`">
- <i class="fal fa-chevron-right fa-lg text-lighter"></i>
- </router-link>
- </div>
- </div>
- </div>
- <div v-if="!threads || !threads.length" class="row justify-content-center">
- <div class="col-12 text-center">
- <img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
- <p class="lead text-muted font-weight-bold">Your inbox is empty</p>
- </div>
- </div>
- <div v-if="canLoadMore">
- <intersect @enter="enterIntersect">
- <dm-placeholder />
- </intersect>
- </div>
- </div>
- <div v-else>
- <dm-placeholder />
- </div>
- </div>
- <div class="col-md-3 d-md-block order-1 order-md-2 mb-4">
- <button class="btn btn-dark shadow-sm font-weight-bold btn-block" @click="openCompose"><i class="far fa-envelope mr-1"></i> Compose</button>
- <hr>
- <div class="d-flex d-md-block">
- <button
- v-for="(tab, index) in tabs"
- class="btn shadow-sm font-weight-bold btn-block text-capitalize mt-0 mt-md-2 mx-1 mx-md-0"
- :class="[ index === tabIndex ? 'btn-primary' : 'btn-light' ]"
- @click="toggleTab(index)"
- >
- {{ $t('directMessages.' + tab) }}
- </button>
- </div>
- </div>
- </div>
- <drawer />
- </div>
- <div v-else class="d-flex justify-content-center align-items-center" style="height:calc(100vh - 58px);">
- <b-spinner />
- </div>
- <b-modal
- ref="compose"
- hide-header
- hide-footer
- centered
- rounded
- size="md"
- >
- <div class="card shadow-none mt-4">
- <div class="card-body d-flex align-items-center justify-content-between flex-column" style="min-height: 50vh;">
- <h3 class="font-weight-bold">New Direct Message</h3>
- <div>
- <p class="mb-0 font-weight-bold">Select Recipient</p>
- <autocomplete
- :search="composeSearch"
- :disabled="composeLoading"
- placeholder="@dansup"
- aria-label="Search usernames"
- :get-result-value="getTagResultValue"
- @submit="onTagSubmitLocation"
- :debounce-time="500"
- ref="autocomplete"
- >
- </autocomplete>
- <p class="small text-muted">Search by username, or webfinger (@dansup@pixelfed.social)</p>
- <div style="width:300px;"></div>
- </div>
- <div>
- <button class="btn btn-outline-dark rounded-pill font-weight-bold px-5 py-1" @click="closeCompose">Cancel</button>
- </div>
- </div>
- </div>
- </b-modal>
- </div>
- </template>
- <script type="text/javascript">
- import Drawer from './partials/drawer.vue';
- import Sidebar from './partials/sidebar.vue';
- import Placeholder from './partials/placeholders/DirectMessagePlaceholder.vue';
- import Intersect from 'vue-intersect'
- import Autocomplete from '@trevoreyre/autocomplete-vue'
- import '@trevoreyre/autocomplete-vue/dist/style.css';
- export default {
- components: {
- "drawer": Drawer,
- "sidebar": Sidebar,
- "intersect": Intersect,
- "dm-placeholder": Placeholder,
- "autocomplete": Autocomplete
- },
- data() {
- return {
- isLoaded: false,
- profile: undefined,
- canLoadMore: true,
- threadsLoaded: false,
- composeLoading: false,
- threads: [],
- tabIndex: 0,
- tabs: [
- 'inbox',
- 'sent',
- 'requests'
- ],
- page: 1,
- ids: [],
- isIntersecting: false
- }
- },
- mounted() {
- this.profile = window._sharedData.user;
- this.isLoaded = true;
- this.fetchThreads();
- },
- methods: {
- fetchThreads() {
- axios.get('/api/v1/conversations', {
- params: {
- scope: this.tabs[this.tabIndex]
- }
- })
- .then(res => {
- let data = res.data.filter(m => {
- return m && m.hasOwnProperty('last_status') && m.last_status;
- })
- let ids = data.map(dm => dm.accounts[0].id);
- this.ids = ids;
- this.threads = data;
- this.threadsLoaded = true;
- this.page++;
- });
- },
- timeago(ts) {
- return App.util.format.timeAgo(ts);
- },
- enterIntersect() {
- if(this.isIntersecting) {
- return;
- }
- this.isIntersecting = true;
- axios.get('/api/v1/conversations', {
- params: {
- scope: this.tabs[this.tabIndex],
- page: this.page
- }
- })
- .then(res => {
- let data = res.data.filter(m => {
- return m && m.hasOwnProperty('last_status') && m.last_status;
- })
- data.forEach(dm => {
- if(this.ids.indexOf(dm.accounts[0].id) == -1) {
- this.ids.push(dm.accounts[0].id);
- this.threads.push(dm);
- }
- })
- // this.threads.push(...res.data);
- if(!res.data.length || res.data.length < 5) {
- this.canLoadMore = false;
- this.isIntersecting = false;
- return;
- }
- this.page++;
- this.isIntersecting = false;
- });
- },
- toggleTab(index) {
- event.currentTarget.blur();
- this.threadsLoaded = false;
- this.page = 1;
- this.tabIndex = index;
- this.fetchThreads();
- },
- threadSummary(status, len = 50) {
- if(status.pf_type == 'photo') {
- let sender = this.profile.id == status.account.id;
- let icon = '<div class="' + (sender ? 'text-muted' : 'text-primary') + ' border px-2 py-1 mt-1 rounded" style="font-size:11px;width: fit-content"><i class="far fa-image mr-1"></i> <span>';
- icon += sender ? 'Sent a photo' : 'Received a photo';
- return icon + '</span></div>';
- }
- if(status.pf_type == 'video') {
- let sender = this.profile.id == status.account.id;
- let icon = '<div class="' + (sender ? 'text-muted' : 'text-primary') + ' border px-2 py-1 mt-1 rounded" style="font-size:11px;width: fit-content"><i class="far fa-video mr-1"></i> <span>';
- icon += sender ? 'Sent a video' : 'Received a video';
- return icon + '</span></div>';
- }
- let res = '';
- if(this.profile.id == status.account.id) {
- res += '<i class="far fa-reply-all fa-flip-both"></i> ';
- }
- let content = status.content;
- let text = content.replace(/(<([^>]+)>)/gi, "");
- if(text.length > len) {
- return res + text.slice(0, len) + '...';
- }
- return res + text;
- },
- openCompose() {
- this.$refs.compose.show();
- },
- composeSearch(input) {
- if (input.length < 1) { return []; };
- let self = this;
- let results = [];
- return axios.post('/api/direct/lookup', {
- q: input
- }).then(res => {
- return res.data;
- });
- },
- getTagResultValue(result) {
- // return '@' + result.name;
- return result.local ? '@' + result.name : result.name;
- },
- onTagSubmitLocation(result) {
- //this.$refs.autocomplete.value = '';
- this.composeLoading = true;
- window.location.href = '/i/web/direct/thread/' + result.id;
- return;
- },
- closeCompose() {
- this.$refs.compose.hide();
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .dms-page-component {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- .dm {
- &-thread-summary {
- margin-bottom: 0;
- font-size: 12px;
- line-height: 12px;
- }
- &-display-name {
- font-size: 16px;
- }
- }
- }
- </style>
|