mirror of https://github.com/pixelfed/pixelfed
commit
5cfa08946e
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Add {
|
||||
|
||||
public static function validate($payload)
|
||||
{
|
||||
$valid = Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'id' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Add'])
|
||||
],
|
||||
'actor' => 'required|url',
|
||||
'object' => 'required',
|
||||
'object.id' => 'required|url',
|
||||
'object.type' => [
|
||||
'required',
|
||||
Rule::in(['Story'])
|
||||
],
|
||||
'object.attributedTo' => 'required|url|same:actor',
|
||||
'object.attachment' => 'required',
|
||||
'object.attachment.type' => [
|
||||
'required',
|
||||
Rule::in(['Image'])
|
||||
],
|
||||
'object.attachment.url' => 'required|url',
|
||||
'object.attachment.mediaType' => [
|
||||
'required',
|
||||
Rule::in(['image/jpeg', 'image/png'])
|
||||
]
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTypeToDirectMessagesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('direct_messages', function (Blueprint $table) {
|
||||
$table->string('type')->default('text')->nullable()->index()->after('from_id');
|
||||
$table->boolean('is_hidden')->default(false)->index()->after('group_message');
|
||||
$table->json('meta')->nullable()->after('is_hidden');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('direct_messages', function (Blueprint $table) {
|
||||
$table->dropColumn('type');
|
||||
$table->dropColumn('is_hidden');
|
||||
$table->dropColumn('meta');
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[16],{19:function(t,e,s){t.exports=s("ETg6")},"7wkd":function(t,e,s){"use strict";s.r(e);var o={data:function(){return{loaded:!1,showLoadMore:!0,profiles:[],page:1}},beforeMount:function(){this.fetchData()},methods:{fetchData:function(){var t=this;axios.get("/api/pixelfed/v2/discover/profiles",{params:{page:this.page}}).then((function(e){if(0==e.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=e.data,t.showLoadMore=8==t.profiles.length,t.loaded=!0}))},prettyCount:function(t){return App.util.format.count(t)},loadMore:function(){this.loaded=!1,this.page++,this.fetchData()},thumbUrl:function(t){return t.media_attachments[0].url}}},a=s("KHd+"),n=Object(a.a)(o,(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[s("div",{staticClass:"col-12"},[s("p",{staticClass:"font-weight-bold text-lighter text-uppercase"},[t._v("Profiles Directory")]),t._v(" "),t.loaded?s("div",{},[s("div",{staticClass:"row"},[t._l(t.profiles,(function(e,o){return s("div",{staticClass:"col-12 col-md-6 p-1"},[s("div",{staticClass:"card card-body border shadow-none py-2"},[s("div",{staticClass:"media"},[s("a",{attrs:{href:e.url}},[s("img",{staticClass:"rounded-circle border mr-3",attrs:{src:e.avatar,alt:"...",width:"40px",height:"40px"}})]),t._v(" "),s("div",{staticClass:"media-body"},[s("p",{staticClass:"mt-0 mb-0 font-weight-bold"},[s("a",{staticClass:"text-dark",attrs:{href:e.url}},[t._v(t._s(e.username))])]),t._v(" "),s("p",{staticClass:"mb-1 small text-lighter d-flex justify-content-between font-weight-bold"},[s("span",[s("span",[t._v(t._s(t.prettyCount(e.statuses_count)))]),t._v(" POSTS\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),s("span",[s("span",[t._v(t._s(t.prettyCount(e.followers_count)))]),t._v(" FOLLOWERS\n\t\t\t\t\t\t\t\t\t")])]),t._v(" "),s("p",{staticClass:"mb-1"},t._l(e.posts,(function(e,o){return s("span",{key:"profile_posts_"+o,staticClass:"shadow-sm"},[s("a",{staticClass:"text-decoration-none mr-1",attrs:{href:e.url}},[s("img",{staticClass:"border rounded",attrs:{src:t.thumbUrl(e),width:"62.3px",height:"62.3px"}})])])})),0)])])])])})),t._v(" "),t.showLoadMore?s("div",{staticClass:"col-12"},[s("p",{staticClass:"text-center mb-0 pt-3"},[s("button",{staticClass:"btn btn-outline-secondary btn-sm px-4 py-1 font-weight-bold",on:{click:function(e){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"row"},[e("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[e("div",{staticClass:"spinner-border",attrs:{role:"status"}},[e("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}],!1,null,"7b3eea1c",null);e.default=n.exports},ETg6:function(t,e,s){Vue.component("profile-directory",s("7wkd").default)},"KHd+":function(t,e,s){"use strict";function o(t,e,s,o,a,n,r,i){var c,d="function"==typeof t?t.options:t;if(e&&(d.render=e,d.staticRenderFns=s,d._compiled=!0),o&&(d.functional=!0),n&&(d._scopeId="data-v-"+n),r?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),a&&a.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=c):a&&(c=i?function(){a.call(this,this.$root.$options.shadowRoot)}:a),c)if(d.functional){d._injectStyles=c;var l=d.render;d.render=function(t,e){return c.call(e),l(t,e)}}else{var u=d.beforeCreate;d.beforeCreate=u?[].concat(u,c):[c]}return{exports:t,options:d}}s.d(e,"a",(function(){return o}))}},[[19,0]]]);
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[17],{19:function(t,e,s){t.exports=s("ETg6")},"7wkd":function(t,e,s){"use strict";s.r(e);var o={data:function(){return{loaded:!1,showLoadMore:!0,profiles:[],page:1}},beforeMount:function(){this.fetchData()},methods:{fetchData:function(){var t=this;axios.get("/api/pixelfed/v2/discover/profiles",{params:{page:this.page}}).then((function(e){if(0==e.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=e.data,t.showLoadMore=8==t.profiles.length,t.loaded=!0}))},prettyCount:function(t){return App.util.format.count(t)},loadMore:function(){this.loaded=!1,this.page++,this.fetchData()},thumbUrl:function(t){return t.media_attachments[0].url}}},a=s("KHd+"),n=Object(a.a)(o,(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[s("div",{staticClass:"col-12"},[s("p",{staticClass:"font-weight-bold text-lighter text-uppercase"},[t._v("Profiles Directory")]),t._v(" "),t.loaded?s("div",{},[s("div",{staticClass:"row"},[t._l(t.profiles,(function(e,o){return s("div",{staticClass:"col-12 col-md-6 p-1"},[s("div",{staticClass:"card card-body border shadow-none py-2"},[s("div",{staticClass:"media"},[s("a",{attrs:{href:e.url}},[s("img",{staticClass:"rounded-circle border mr-3",attrs:{src:e.avatar,alt:"...",width:"40px",height:"40px"}})]),t._v(" "),s("div",{staticClass:"media-body"},[s("p",{staticClass:"mt-0 mb-0 font-weight-bold"},[s("a",{staticClass:"text-dark",attrs:{href:e.url}},[t._v(t._s(e.username))])]),t._v(" "),s("p",{staticClass:"mb-1 small text-lighter d-flex justify-content-between font-weight-bold"},[s("span",[s("span",[t._v(t._s(t.prettyCount(e.statuses_count)))]),t._v(" POSTS\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),s("span",[s("span",[t._v(t._s(t.prettyCount(e.followers_count)))]),t._v(" FOLLOWERS\n\t\t\t\t\t\t\t\t\t")])]),t._v(" "),s("p",{staticClass:"mb-1"},t._l(e.posts,(function(e,o){return s("span",{key:"profile_posts_"+o,staticClass:"shadow-sm"},[s("a",{staticClass:"text-decoration-none mr-1",attrs:{href:e.url}},[s("img",{staticClass:"border rounded",attrs:{src:t.thumbUrl(e),width:"62.3px",height:"62.3px"}})])])})),0)])])])])})),t._v(" "),t.showLoadMore?s("div",{staticClass:"col-12"},[s("p",{staticClass:"text-center mb-0 pt-3"},[s("button",{staticClass:"btn btn-outline-secondary btn-sm px-4 py-1 font-weight-bold",on:{click:function(e){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"row"},[e("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[e("div",{staticClass:"spinner-border",attrs:{role:"status"}},[e("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}],!1,null,"7b3eea1c",null);e.default=n.exports},ETg6:function(t,e,s){Vue.component("profile-directory",s("7wkd").default)},"KHd+":function(t,e,s){"use strict";function o(t,e,s,o,a,n,r,i){var c,d="function"==typeof t?t.options:t;if(e&&(d.render=e,d.staticRenderFns=s,d._compiled=!0),o&&(d.functional=!0),n&&(d._scopeId="data-v-"+n),r?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),a&&a.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=c):a&&(c=i?function(){a.call(this,this.$root.$options.shadowRoot)}:a),c)if(d.functional){d._injectStyles=c;var l=d.render;d.render=function(t,e){return c.call(e),l(t,e)}}else{var u=d.beforeCreate;d.beforeCreate=u?[].concat(u,c):[c]}return{exports:t,options:d}}s.d(e,"a",(function(){return o}))}},[[19,0]]]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[23],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],(function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)})),ace.require(["ace/theme/monokai"],(function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)}))}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[24],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],(function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)})),ace.require(["ace/theme/monokai"],(function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)}))}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loaded && page == 'browse'" class="container messages-page p-0 p-md-2 mt-n4" style="min-height: 50vh;">
|
||||
<div class="col-12 col-md-8 offset-md-2 p-0 px-md-2">
|
||||
<div class="card shadow-none border mt-4">
|
||||
<div class="card-header bg-white py-4">
|
||||
<span class="h4 font-weight-bold mb-0">Direct Messages</span>
|
||||
<span class="float-right">
|
||||
<a class="btn btn-outline-primary font-weight-bold py-0 rounded-pill" href="#" @click.prevent="goto('add')">New Message</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-header bg-white">
|
||||
<ul class="nav nav-pills nav-fill">
|
||||
<li class="nav-item">
|
||||
<a :class="[tab == 'inbox' ? 'nav-link px-4 font-weight-bold rounded-pill active' : 'nav-link px-4 font-weight-bold rounded-pill']" @click.prevent="switchTab('inbox')" href="#">Inbox</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a :class="[tab == 'sent' ? 'nav-link px-4 font-weight-bold rounded-pill active' : 'nav-link px-4 font-weight-bold rounded-pill']" @click.prevent="switchTab('sent')" href="#">Sent</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a :class="[tab == 'filtered' ? 'nav-link px-4 font-weight-bold rounded-pill active' : 'nav-link px-4 font-weight-bold rounded-pill']" @click.prevent="switchTab('filtered')" href="#">Filtered</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul v-if="tab == 'inbox'" class="list-group list-group-flush">
|
||||
<div v-if="!messages.inbox.length" class="list-group-item d-flex justify-content-center align-items-center" style="min-height: 40vh;">
|
||||
<p class="lead mb-0">No messages found :(</p>
|
||||
</div>
|
||||
<div v-else v-for="(thread, index) in messages.inbox">
|
||||
<a class="list-group-item text-dark text-decoration-none border-left-0 border-right-0 border-top-0" :href="'/account/direct/t/'+thread.id">
|
||||
<div class="media d-flex align-items-center">
|
||||
<img class="mr-3 rounded-circle img-thumbnail" :src="thread.avatar" alt="Generic placeholder image" width="32px">
|
||||
<div class="media-body">
|
||||
<p class="mb-0">
|
||||
<span class="font-weight-bold text-truncate">
|
||||
{{thread.name}}
|
||||
</span>
|
||||
<span class="pl-1 text-muted small text-truncate" style="font-weight: 500;">
|
||||
{{thread.isLocal ? '@' + thread.username : thread.username}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-muted mb-0" style="font-size:13px;font-weight: 500;">
|
||||
<span>
|
||||
<i class="far fa-comment text-primary"></i>
|
||||
</span>
|
||||
<span class="pl-1 pr-3">
|
||||
Received
|
||||
</span>
|
||||
<span>
|
||||
{{thread.timeAgo}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<span class="float-right">
|
||||
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</ul>
|
||||
<ul v-if="tab == 'sent'" class="list-group list-group-flush">
|
||||
<div v-if="!messages.sent.length" class="list-group-item d-flex justify-content-center align-items-center" style="min-height: 40vh;">
|
||||
<p class="lead mb-0">No messages found :(</p>
|
||||
</div>
|
||||
<div v-else v-for="(thread, index) in messages.sent">
|
||||
<a class="list-group-item text-dark text-decoration-none border-left-0 border-right-0 border-top-0" href="#" @click.prevent="loadMessage(thread.id)">
|
||||
<div class="media d-flex align-items-center">
|
||||
<img class="mr-3 rounded-circle img-thumbnail" :src="thread.avatar" alt="Generic placeholder image" width="32px">
|
||||
<div class="media-body">
|
||||
<p class="mb-0">
|
||||
<span class="font-weight-bold text-truncate">
|
||||
{{thread.name}}
|
||||
</span>
|
||||
<span class="pl-1 text-muted small text-truncate" style="font-weight: 500;">
|
||||
{{thread.isLocal ? '@' + thread.username : thread.username}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-muted mb-0" style="font-size:13px;font-weight: 500;">
|
||||
<span>
|
||||
<i class="far fa-paper-plane text-primary"></i>
|
||||
</span>
|
||||
<span class="pl-1 pr-3">
|
||||
Delivered
|
||||
</span>
|
||||
<span>
|
||||
{{thread.timeAgo}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<span class="float-right">
|
||||
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</ul>
|
||||
<ul v-if="tab == 'filtered'" class="list-group list-group-flush">
|
||||
<div v-if="!messages.filtered.length" class="list-group-item d-flex justify-content-center align-items-center" style="min-height: 40vh;">
|
||||
<p class="lead mb-0">No messages found :(</p>
|
||||
</div>
|
||||
<div v-else v-for="(thread, index) in messages.filtered">
|
||||
<a class="list-group-item text-dark text-decoration-none border-left-0 border-right-0 border-top-0" href="#" @click.prevent="loadMessage(thread.id)">
|
||||
<div class="media d-flex align-items-center">
|
||||
<img class="mr-3 rounded-circle img-thumbnail" :src="thread.avatar" alt="Generic placeholder image" width="32px">
|
||||
<div class="media-body">
|
||||
<p class="mb-0">
|
||||
<span class="font-weight-bold text-truncate">
|
||||
{{thread.name}}
|
||||
</span>
|
||||
<span class="pl-1 text-muted small text-truncate" style="font-weight: 500;">
|
||||
{{thread.isLocal ? '@' + thread.username : thread.username}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-muted mb-0" style="font-size:13px;font-weight: 500;">
|
||||
<span>
|
||||
<i class="fas fa-shield-alt" style="color:#fd9426"></i>
|
||||
</span>
|
||||
<span class="pl-1 pr-3">
|
||||
Filtered
|
||||
</span>
|
||||
<span>
|
||||
{{thread.timeAgo}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<span class="float-right">
|
||||
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="tab == 'inbox'" class="mt-3 text-center">
|
||||
<button class="btn btn-outline-primary rounded-pill btn-sm" :disabled="inboxPage == 1" @click="messagePagination('inbox', 'prev')">Prev</button>
|
||||
<button class="btn btn-outline-primary rounded-pill btn-sm" :disabled="messages.inbox.length != 8" @click="messagePagination('inbox', 'next')">Next</button>
|
||||
</div>
|
||||
<div v-if="tab == 'sent'" class="mt-3 text-center">
|
||||
<button class="btn btn-outline-primary rounded-pill btn-sm" :disabled="sentPage == 1" @click="messagePagination('sent', 'prev')">Prev</button>
|
||||
<button class="btn btn-outline-primary rounded-pill btn-sm" :disabled="messages.sent.length != 8" @click="messagePagination('sent', 'next')">Next</button>
|
||||
</div>
|
||||
<div v-if="tab == 'filtered'" class="mt-3 text-center">
|
||||
<button class="btn btn-outline-primary rounded-pill btn-sm" :disabled="filteredPage == 1" @click="messagePagination('filtered', 'prev')">Prev</button>
|
||||
<button class="btn btn-outline-primary rounded-pill btn-sm" :disabled="messages.filtered.length != 8" @click="messagePagination('filtered', 'next')">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loaded && page == 'add'" class="container messages-page p-0 p-md-2 mt-n4" style="min-height: 60vh;">
|
||||
<div class="col-12 col-md-8 offset-md-2 p-0 px-md-2">
|
||||
<div class="card shadow-none border mt-4">
|
||||
<div class="card-header bg-white py-4 d-flex justify-content-between">
|
||||
<span class="cursor-pointer px-3" @click="goto('browse')"><i class="fas fa-chevron-left"></i></span>
|
||||
<span class="h4 font-weight-bold mb-0">New Direct Message</span>
|
||||
<span><i class="fas fa-chevron-right text-white"></i></span>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 60vh;">
|
||||
<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"
|
||||
ref="autocomplete"
|
||||
>
|
||||
</autocomplete>
|
||||
<div style="width:300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style type="text/css" scoped>
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
import Autocomplete from '@trevoreyre/autocomplete-vue'
|
||||
import '@trevoreyre/autocomplete-vue/dist/style.css'
|
||||
export default {
|
||||
components: {
|
||||
Autocomplete
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config: window.App.config,
|
||||
loaded: false,
|
||||
profile: {},
|
||||
page: 'browse',
|
||||
pages: ['browse', 'add', 'read'],
|
||||
tab: 'inbox',
|
||||
tabs: ['inbox', 'sent', 'filtered'],
|
||||
inboxPage: 1,
|
||||
sentPage: 1,
|
||||
filteredPage: 1,
|
||||
threads: [],
|
||||
thread: false,
|
||||
threadIndex: false,
|
||||
|
||||
replyText: '',
|
||||
composeUsername: '',
|
||||
|
||||
ctxContext: null,
|
||||
ctxIndex: null,
|
||||
|
||||
uploading: false,
|
||||
uploadProgress: null,
|
||||
|
||||
messages: {
|
||||
inbox: [],
|
||||
sent: [],
|
||||
filtered: []
|
||||
},
|
||||
|
||||
newType: 'select',
|
||||
composeLoading: false,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchProfile();
|
||||
let self = this;
|
||||
axios.get('/api/direct/browse', {
|
||||
params: {
|
||||
a: 'inbox'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
this.threads = res.data
|
||||
this.messages.inbox = res.data;
|
||||
});
|
||||
},
|
||||
|
||||
updated() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchProfile() {
|
||||
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
||||
this.profile = res.data;
|
||||
window._sharedData.curUser = res.data;
|
||||
});
|
||||
},
|
||||
|
||||
goto(l = 'browse') {
|
||||
this.page = l;
|
||||
},
|
||||
|
||||
loadMessage(id) {
|
||||
let url = '/account/direct/t/' + id;
|
||||
window.location.href = url;
|
||||
return;
|
||||
},
|
||||
|
||||
truncate(t) {
|
||||
return _.truncate(t);
|
||||
},
|
||||
|
||||
switchTab(tab) {
|
||||
let self = this;
|
||||
switch(tab) {
|
||||
case 'inbox':
|
||||
if(this.messages.inbox.length == 0) {
|
||||
// fetch
|
||||
}
|
||||
break;
|
||||
case 'sent':
|
||||
if(this.messages.sent.length == 0) {
|
||||
axios.get('/api/direct/browse', {
|
||||
params: {
|
||||
a: 'sent'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
self.threads = res.data
|
||||
self.messages.sent = res.data;
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'filtered':
|
||||
if(this.messages.filtered.length == 0) {
|
||||
axios.get('/api/direct/browse', {
|
||||
params: {
|
||||
a: 'filtered'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
self.threads = res.data
|
||||
self.messages.filtered = res.data;
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.tab = tab;
|
||||
},
|
||||
|
||||
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 = '/account/direct/t/' + result.id;
|
||||
return;
|
||||
},
|
||||
|
||||
messagePagination(tab, dir) {
|
||||
if(tab == 'inbox') {
|
||||
this.inboxPage = dir == 'prev' ? this.inboxPage - 1 : this.inboxPage + 1;
|
||||
axios.get('/api/direct/browse', {
|
||||
params: {
|
||||
a: 'inbox',
|
||||
page: this.inboxPage
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
this.threads = res.data
|
||||
this.messages.inbox = res.data;
|
||||
});
|
||||
}
|
||||
if(tab == 'sent') {
|
||||
this.sentPage = dir == 'prev' ? this.sentPage - 1 : this.sentPage + 1;
|
||||
axios.get('/api/direct/browse', {
|
||||
params: {
|
||||
a: 'sent',
|
||||
page: this.sentPage
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
this.threads = res.data
|
||||
this.messages.sent = res.data;
|
||||
});
|
||||
}
|
||||
if(tab == 'filtered') {
|
||||
this.filteredPage = dir == 'prev' ? this.filteredPage - 1 : this.filteredPage + 1;
|
||||
axios.get('/api/direct/browse', {
|
||||
params: {
|
||||
a: 'filtered',
|
||||
page: this.filteredPage
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
this.threads = res.data
|
||||
this.messages.filtered = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,682 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loaded && page == 'read'" class="container messages-page p-0 p-md-2 mt-n4" style="min-height: 60vh;">
|
||||
<div class="col-12 col-md-8 offset-md-2 p-0 px-md-2">
|
||||
<div class="card shadow-none border mt-4">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<a href="/account/direct" class="text-muted">
|
||||
<i class="fas fa-chevron-left fa-lg"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<div class="media">
|
||||
<img class="mr-3 rounded-circle img-thumbnail" :src="thread.avatar" alt="Generic placeholder image" width="40px">
|
||||
<div class="media-body">
|
||||
<p class="mb-0">
|
||||
<span class="font-weight-bold">{{thread.name}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<a v-if="!thread.isLocal" :href="'/'+thread.username" class="text-decoration-none text-muted">{{thread.username}}</a>
|
||||
<a v-else :href="'/'+thread.username" class="text-decoration-none text-muted">@{{thread.username}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span><a href="#" class="text-muted" @click.prevent="showOptions()"><i class="fas fa-cog fa-lg"></i></a></span>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush dm-wrapper" style="height:60vh;overflow-y: scroll;">
|
||||
<li class="list-group-item border-0">
|
||||
<p class="text-center small text-muted">
|
||||
Conversation with <span class="font-weight-bold">{{thread.username}}</span>
|
||||
</p>
|
||||
<hr>
|
||||
</li>
|
||||
<li v-if="showLoadMore && thread.messages && thread.messages.length > 5" class="list-group-item border-0 mt-n4">
|
||||
<p class="text-center small text-muted">
|
||||
<button v-if="!loadingMessages" class="btn btn-primary font-weight-bold rounded-pill btn-sm px-3" @click="loadOlderMessages()">Load Older Messages</button>
|
||||
<button v-else class="btn btn-primary font-weight-bold rounded-pill btn-sm px-3" disabled>Loading...</button>
|
||||
</p>
|
||||
</li>
|
||||
<li v-for="(convo, index) in thread.messages" class="list-group-item border-0 chat-msg cursor-pointer" @click="openCtxMenu(convo, index)">
|
||||
<div v-if="!convo.isAuthor" class="media d-inline-flex mb-0">
|
||||
<img v-if="!hideAvatars" class="mr-3 mt-2 rounded-circle img-thumbnail" :src="thread.avatar" alt="avatar" width="32px">
|
||||
<div class="media-body">
|
||||
<p v-if="convo.type == 'photo'" class="pill-to p-0 shadow">
|
||||
<img :src="convo.media" width="140px" style="border-radius:20px;">
|
||||
</p>
|
||||
<div v-else-if="convo.type == 'link'" class="media d-inline-flex mb-0 cursor-pointer">
|
||||
<div class="media-body">
|
||||
<div class="card mb-2 rounded border shadow" style="width:240px;" :title="convo.text">
|
||||
<div class="card-body p-0">
|
||||
<div class="media d-flex align-items-center">
|
||||
<div v-if="convo.meta.local" class="bg-primary mr-3 border-right p-3">
|
||||
<i class="fas fa-link text-white fa-2x"></i>
|
||||
</div>
|
||||
<div v-else class="bg-light mr-3 border-right p-3">
|
||||
<i class="fas fa-link text-lighter fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body text-muted small text-truncate pr-2 font-weight-bold">
|
||||
{{convo.meta.local ? convo.text.substr(8) : convo.meta.domain}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else-if="convo.type == 'video'" class="pill-to p-0 shadow">
|
||||
<!-- <video :src="convo.media" width="140px" style="border-radius:20px;"></video> -->
|
||||
<span class="d-block bg-primary d-flex align-items-center justify-content-center" style="width:200px;height: 110px;border-radius: 20px;">
|
||||
<div class="text-center">
|
||||
<p class="mb-1">
|
||||
<i class="fas fa-play fa-2x text-white"></i>
|
||||
</p>
|
||||
<p class="mb-0 small font-weight-bold text-white">
|
||||
Play
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
<p v-else-if="convo.type == 'emoji'" class="p-0 emoji-msg">
|
||||
{{convo.text}}
|
||||
</p>
|
||||
<p v-else :class="[largerText ? 'pill-to shadow larger-text text-break':'pill-to shadow text-break']">
|
||||
{{convo.text}}
|
||||
</p>
|
||||
<p v-if="!hideTimestamps" class="small text-muted font-weight-bold ml-2 d-flex align-items-center justify-content-start" data-timestamp="timestamp"> <span v-if="convo.hidden" class="mr-2 small" title="Filtered Message" data-toggle="tooltip" data-placement="bottom"><i class="fas fa-lock"></i></span> {{convo.timeAgo}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="media d-inline-flex float-right mb-0">
|
||||
<div class="media-body">
|
||||
<p v-if="convo.type == 'photo'" class="pill-from p-0 shadow">
|
||||
<img :src="convo.media" width="140px" style="border-radius:20px;">
|
||||
</p>
|
||||
<div v-else-if="convo.type == 'link'" class="media d-inline-flex float-right mb-0 cursor-pointer">
|
||||
<div class="media-body">
|
||||
<div class="card mb-2 rounded border shadow" style="width:240px;" :title="convo.text">
|
||||
<div class="card-body p-0">
|
||||
<div class="media d-flex align-items-center">
|
||||
<div v-if="convo.meta.local" class="bg-primary mr-3 border-right p-3">
|
||||
<i class="fas fa-link text-white fa-2x"></i>
|
||||
</div>
|
||||
<div v-else class="bg-light mr-3 border-right p-3">
|
||||
<i class="fas fa-link text-lighter fa-2x"></i>
|
||||
</div>
|
||||
<div class="media-body text-muted small text-truncate pr-2 font-weight-bold">
|
||||
{{convo.meta.local ? convo.text.substr(8) : convo.meta.domain}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else-if="convo.type == 'video'" class="pill-from p-0 shadow">
|
||||
<!-- <video :src="convo.media" width="140px" style="border-radius:20px;"></video> -->
|
||||
<span class="rounded-pill bg-primary d-flex align-items-center justify-content-center" style="width:200px;height: 110px">
|
||||
<div class="text-center">
|
||||
<p class="mb-1">
|
||||
<i class="fas fa-play fa-2x text-white"></i>
|
||||
</p>
|
||||
<p class="mb-0 small font-weight-bold">
|
||||
Play
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</p>
|
||||
<p v-else-if="convo.type == 'emoji'" class="p-0 emoji-msg">
|
||||
{{convo.text}}
|
||||
</p>
|
||||
<p v-else :class="[largerText ? 'pill-from shadow larger-text text-break':'pill-from shadow text-break']">
|
||||
{{convo.text}}
|
||||
</p>
|
||||
<p v-if="!hideTimestamps" class="small text-muted font-weight-bold text-right mr-2"> <span v-if="convo.hidden" class="mr-2 small" title="Filtered Message" data-toggle="tooltip" data-placement="bottom"><i class="fas fa-lock"></i></span> {{convo.timeAgo}}
|
||||
</p>
|
||||
</div>
|
||||
<img v-if="!hideAvatars" class="ml-3 mt-2 rounded-circle img-thumbnail" :src="profile.avatar" alt="avatar" width="32px">
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="card-footer bg-white p-0">
|
||||
<form class="border-0 rounded-0 align-middle" method="post" action="#">
|
||||
<textarea class="form-control border-0 rounded-0 no-focus" name="comment" placeholder="Reply ..." autocomplete="off" autocorrect="off" style="height:86px;line-height: 18px;max-height:80px;resize: none; padding-right:115.22px;" v-model="replyText" :disabled="blocked"></textarea>
|
||||
<input type="button" value="Send" :class="[replyText.length ? 'd-inline-block btn btn-sm btn-primary rounded-pill font-weight-bold reply-btn text-decoration-none text-uppercase' : 'd-inline-block btn btn-sm btn-primary rounded-pill font-weight-bold reply-btn text-decoration-none text-uppercase disabled']" :disabled="replyText.length == 0" @click.prevent="sendMessage"/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer p-0">
|
||||
<p class="d-flex justify-content-between align-items-center mb-0 px-3 py-1 small">
|
||||
<!-- <span class="font-weight-bold" style="color: #D69E2E">
|
||||
<i class="fas fa-circle mr-1"></i>
|
||||
Typing ...
|
||||
</span> -->
|
||||
<span>
|
||||
<!-- <span class="btn btn-primary btn-sm font-weight-bold py-0 px-3 rounded-pill" @click="uploadMedia">
|
||||
<i class="fas fa-share mr-1"></i>
|
||||
Share
|
||||
</span> -->
|
||||
<span class="btn btn-primary btn-sm font-weight-bold py-0 px-3 rounded-pill" @click="uploadMedia">
|
||||
<i class="fas fa-upload mr-1"></i>
|
||||
Add Photo/Video
|
||||
</span>
|
||||
</span>
|
||||
<input type="file" id="uploadMedia" class="d-none" name="uploadMedia" accept="image/jpeg,image/png,image/gif,video/mp4" >
|
||||
<span class="text-muted font-weight-bold">{{replyText.length}}/600</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loaded && page == 'options'" class="container messages-page p-0 p-md-2 mt-n4" style="min-height: 60vh;">
|
||||
<div class="col-12 col-md-8 offset-md-2 p-0 px-md-2">
|
||||
<div class="card shadow-none border mt-4">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<a href="#" class="text-muted" @click.prevent="page='read'">
|
||||
<i class="fas fa-chevron-left fa-lg"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<p class="mb-0 lead font-weight-bold py-2">Message Settings</p>
|
||||
</span>
|
||||
<span class="text-lighter" data-toggle="tooltip" data-placement="bottom" title="Have a nice day!"><i class="far fa-smile fa-lg"></i></span>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush dm-wrapper" style="height: 698px;">
|
||||
<div class="list-group-item media border-bottom">
|
||||
<div class="d-inline-block custom-control custom-switch ml-3">
|
||||
<input type="checkbox" class="custom-control-input" id="customSwitch0" v-model="hideAvatars">
|
||||
<label class="custom-control-label" for="customSwitch0"></label>
|
||||
</div>
|
||||
<div class="d-inline-block ml-3 font-weight-bold">
|
||||
Hide Avatars
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item media border-bottom">
|
||||
<div class="d-inline-block custom-control custom-switch ml-3">
|
||||
<input type="checkbox" class="custom-control-input" id="customSwitch1" v-model="hideTimestamps">
|
||||
<label class="custom-control-label" for="customSwitch1"></label>
|
||||
</div>
|
||||
<div class="d-inline-block ml-3 font-weight-bold">
|
||||
Hide Timestamps
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item media border-bottom">
|
||||
<div class="d-inline-block custom-control custom-switch ml-3">
|
||||
<input type="checkbox" class="custom-control-input" id="customSwitch2" v-model="largerText">
|
||||
<label class="custom-control-label" for="customSwitch2"></label>
|
||||
</div>
|
||||
<div class="d-inline-block ml-3 font-weight-bold">
|
||||
Larger Text
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="list-group-item media border-bottom">
|
||||
<div class="d-inline-block custom-control custom-switch ml-3">
|
||||
<input type="checkbox" class="custom-control-input" id="customSwitch3" v-model="autoRefresh">
|
||||
<label class="custom-control-label" for="customSwitch3"></label>
|
||||
</div>
|
||||
<div class="d-inline-block ml-3 font-weight-bold">
|
||||
Auto Refresh
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="list-group-item media border-bottom d-flex align-items-center">
|
||||
<div class="d-inline-block custom-control custom-switch ml-3">
|
||||
<input type="checkbox" class="custom-control-input" id="customSwitch4" v-model="mutedNotifications">
|
||||
<label class="custom-control-label" for="customSwitch4"></label>
|
||||
</div>
|
||||
<div class="d-inline-block ml-3 font-weight-bold">
|
||||
Mute Notifications
|
||||
<p class="small mb-0">You will not receive any direct message notifications from <strong>{{thread.username}}</strong>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<b-modal ref="ctxModal"
|
||||
id="ctx-modal"
|
||||
hide-header
|
||||
hide-footer
|
||||
centered
|
||||
rounded
|
||||
size="sm"
|
||||
body-class="list-group-flush p-0 rounded">
|
||||
<div class="list-group text-center">
|
||||
<div v-if="ctxContext && ctxContext.type == 'photo'" class="list-group-item rounded cursor-pointer font-weight-bold text-dark" @click="viewOriginal()">View Original</div>
|
||||
<div v-if="ctxContext && ctxContext.type == 'video'" class="list-group-item rounded cursor-pointer font-weight-bold text-dark" @click="viewOriginal()">Play</div>
|
||||
<div v-if="ctxContext && ctxContext.type == 'link'" class="list-group-item rounded cursor-pointer" @click="clickLink()">
|
||||
<p class="mb-0" style="font-size:12px;">
|
||||
Navigate to
|
||||
</p>
|
||||
<p class="mb-0 font-weight-bold text-dark">
|
||||
{{this.ctxContext.meta.domain}}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="ctxContext && (ctxContext.type == 'text' || ctxContext.type == 'emoji' || ctxContext.type == 'link')" class="list-group-item rounded cursor-pointer text-dark" @click="copyText()">Copy</div>
|
||||
<div v-if="ctxContext && !ctxContext.isAuthor" class="list-group-item rounded cursor-pointer text-muted" @click="reportMessage()">Report</div>
|
||||
<div v-if="ctxContext && ctxContext.isAuthor" class="list-group-item rounded cursor-pointer text-muted" @click="deleteMessage()">Delete</div>
|
||||
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style type="text/css" scoped>
|
||||
.reply-btn {
|
||||
position: absolute;
|
||||
bottom: 54px;
|
||||
right: 20px;
|
||||
width: 90px;
|
||||
text-align: center;
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
.media-body .bg-primary {
|
||||
background: linear-gradient(135deg, #2EA2F4 0%, #0B93F6 100%) !important;
|
||||
}
|
||||
.pill-to {
|
||||
background:#EDF2F7;
|
||||
font-weight: 500;
|
||||
border-radius: 20px !important;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-right: 3rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.pill-from {
|
||||
color: white !important;
|
||||
text-align: right !important;
|
||||
/*background: #53d769;*/
|
||||
background: linear-gradient(135deg, #2EA2F4 0%, #0B93F6 100%) !important;
|
||||
font-weight: 500;
|
||||
border-radius: 20px !important;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-left: 3rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.chat-msg:hover {
|
||||
background: #f7fbfd;
|
||||
}
|
||||
.no-focus:focus {
|
||||
outline: none !important;
|
||||
outline-width: 0 !important;
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
}
|
||||
.emoji-msg {
|
||||
font-size: 4rem !important;
|
||||
line-height: 30px !important;
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
.larger-text {
|
||||
font-size: 22px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
props: ['accountId'],
|
||||
data() {
|
||||
return {
|
||||
config: window.App.config,
|
||||
hideAvatars: true,
|
||||
hideTimestamps: false,
|
||||
largerText: false,
|
||||
autoRefresh: false,
|
||||
mutedNotifications: false,
|
||||
blocked: false,
|
||||
loaded: false,
|
||||
profile: {},
|
||||
page: 'read',
|
||||
pages: ['browse', 'add', 'read'],
|
||||
threads: [],
|
||||
thread: false,
|
||||
threadIndex: false,
|
||||
|
||||
replyText: '',
|
||||
composeUsername: '',
|
||||
|
||||
ctxContext: null,
|
||||
ctxIndex: null,
|
||||
|
||||
uploading: false,
|
||||
uploadProgress: null,
|
||||
|
||||
min_id: null,
|
||||
max_id: null,
|
||||
loadingMessages: false,
|
||||
showLoadMore: true,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchProfile();
|
||||
let self = this;
|
||||
axios.get('/api/direct/thread', {
|
||||
params: {
|
||||
pid: self.accountId
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
self.loaded = true;
|
||||
let d = res.data;
|
||||
d.messages.reverse();
|
||||
this.thread = d;
|
||||
this.threads = [d];
|
||||
this.threadIndex = 0;
|
||||
let mids = d.messages.map(m => m.id);
|
||||
this.max_id = Math.max(...mids);
|
||||
this.min_id = Math.min(...mids);
|
||||
this.mutedNotifications = d.muted;
|
||||
this.markAsRead();
|
||||
//this.messagePoll();
|
||||
setTimeout(function() {
|
||||
let objDiv = document.querySelector('.dm-wrapper');
|
||||
objDiv.scrollTop = objDiv.scrollHeight;
|
||||
}, 300);
|
||||
});
|
||||
|
||||
let options = localStorage.getItem('px_dm_options');
|
||||
if(options) {
|
||||
options = JSON.parse(options);
|
||||
this.hideAvatars = options.hideAvatars;
|
||||
this.hideTimestamps = options.hideTimestamps;
|
||||
this.largerText = options.largerText;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
mutedNotifications: function(v) {
|
||||
if(v) {
|
||||
axios.post('/api/direct/mute', {
|
||||
id: this.accountId
|
||||
}).then(res => {
|
||||
|
||||
});
|
||||
} else {
|
||||
axios.post('/api/direct/unmute', {
|
||||
id: this.accountId
|
||||
}).then(res => {
|
||||
|
||||
});
|
||||
}
|
||||
this.mutedNotifications = v;
|
||||
},
|
||||
|
||||
hideAvatars: function(v) {
|
||||
this.hideAvatars = v;
|
||||
this.updateOptions();
|
||||
},
|
||||
|
||||
hideTimestamps: function(v) {
|
||||
this.hideTimestamps = v;
|
||||
this.updateOptions();
|
||||
},
|
||||
|
||||
largerText: function(v) {
|
||||
this.largerText = v;
|
||||
this.updateOptions();
|
||||
},
|
||||
},
|
||||
|
||||
updated() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchProfile() {
|
||||
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
||||
this.profile = res.data;
|
||||
window._sharedData.curUser = res.data;
|
||||
});
|
||||
},
|
||||
|
||||
sendMessage() {
|
||||
let self = this;
|
||||
let rt = this.replyText;
|
||||
axios.post('/api/direct/create', {
|
||||
'to_id': this.threads[this.threadIndex].id,
|
||||
'message': rt,
|
||||
'type': self.isEmoji(rt) && rt.length < 10 ? 'emoji' : 'text'
|
||||
}).then(res => {
|
||||
let msg = res.data;
|
||||
self.threads[self.threadIndex].messages.push(msg);
|
||||
let mids = self.threads[self.threadIndex].messages.map(m => m.id);
|
||||
this.max_id = Math.max(...mids)
|
||||
this.min_id = Math.min(...mids)
|
||||
setTimeout(function() {
|
||||
var objDiv = document.querySelector('.dm-wrapper');
|
||||
objDiv.scrollTop = objDiv.scrollHeight;
|
||||
}, 300);
|
||||
}).catch(err => {
|
||||
if(err.response.status == 403) {
|
||||
self.blocked = true;
|
||||
swal('Profile Unavailable', 'You cannot message this profile at this time.', 'error');
|
||||
}
|
||||
})
|
||||
this.replyText = '';
|
||||
},
|
||||
|
||||
openCtxMenu(r, i) {
|
||||
this.ctxIndex = i;
|
||||
this.ctxContext = r;
|
||||
this.$refs.ctxModal.show();
|
||||
},
|
||||
|
||||
closeCtxMenu() {
|
||||
this.$refs.ctxModal.hide();
|
||||
},
|
||||
|
||||
truncate(t) {
|
||||
return _.truncate(t);
|
||||
},
|
||||
|
||||
deleteMessage() {
|
||||
let self = this;
|
||||
let c = window.confirm('Are you sure you want to delete this message?');
|
||||
if(c) {
|
||||
axios.delete('/api/direct/message', {
|
||||
params: {
|
||||
id: self.ctxContext.reportId
|
||||
}
|
||||
}).then(res => {
|
||||
self.threads[self.threadIndex].messages.splice(self.ctxIndex,1);
|
||||
self.closeCtxMenu();
|
||||
});
|
||||
} else {
|
||||
self.closeCtxMenu();
|
||||
}
|
||||
},
|
||||
|
||||
reportMessage() {
|
||||
this.closeCtxMenu();
|
||||
let url = '/i/report?type=post&id=' + this.ctxContext.reportId;
|
||||
window.location.href = url;
|
||||
return;
|
||||
},
|
||||
|
||||
uploadMedia(event) {
|
||||
let self = this;
|
||||
$(document).on('change', '#uploadMedia', function(e) {
|
||||
self.handleUpload();
|
||||
});
|
||||
let el = $(event.target);
|
||||
el.attr('disabled', '');
|
||||
$('#uploadMedia').click();
|
||||
el.blur();
|
||||
el.removeAttr('disabled');
|
||||
},
|
||||
|
||||
handleUpload() {
|
||||
let self = this;
|
||||
self.uploading = true;
|
||||
let io = document.querySelector('#uploadMedia');
|
||||
if(!io.files.length) {
|
||||
this.uploading = false;
|
||||
}
|
||||
Array.prototype.forEach.call(io.files, function(io, i) {
|
||||
let type = io.type;
|
||||
let acceptedMimes = self.config.uploader.media_types.split(',');
|
||||
let validated = $.inArray(type, acceptedMimes);
|
||||
if(validated == -1) {
|
||||
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error');
|
||||
self.uploading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let form = new FormData();
|
||||
form.append('file', io);
|
||||
form.append('to_id', self.threads[self.threadIndex].id);
|
||||
|
||||
let xhrConfig = {
|
||||
onUploadProgress: function(e) {
|
||||
let progress = Math.round( (e.loaded * 100) / e.total );
|
||||
self.uploadProgress = progress;
|
||||
}
|
||||
};
|
||||
|
||||
axios.post('/api/direct/media', form, xhrConfig)
|
||||
.then(function(e) {
|
||||
self.uploadProgress = 100;
|
||||
self.uploading = false;
|
||||
let msg = {
|
||||
id: e.data.id,
|
||||
type: e.data.type,
|
||||
reportId: e.data.reportId,
|
||||
isAuthor: true,
|
||||
text: null,
|
||||
media: e.data.url,
|
||||
timeAgo: '1s',
|
||||
seen: null
|
||||
};
|
||||
self.threads[self.threadIndex].messages.push(msg);
|
||||
setTimeout(function() {
|
||||
var objDiv = document.querySelector('.dm-wrapper');
|
||||
objDiv.scrollTop = objDiv.scrollHeight;
|
||||
}, 300);
|
||||
|
||||
}).catch(function(e) {
|
||||
switch(e.response.status) {
|
||||
case 451:
|
||||
self.uploading = false;
|
||||
io.value = null;
|
||||
swal('Banned Content', 'This content has been banned and cannot be uploaded.', 'error');
|
||||
break;
|
||||
|
||||
default:
|
||||
self.uploading = false;
|
||||
io.value = null;
|
||||
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||
break;
|
||||
}
|
||||
});
|
||||
io.value = null;
|
||||
self.uploadProgress = 0;
|
||||
});
|
||||
},
|
||||
|
||||
viewOriginal() {
|
||||
let url = this.ctxContext.media;
|
||||
window.location.href = url;
|
||||
return;
|
||||
},
|
||||
|
||||
isEmoji(text) {
|
||||
const onlyEmojis = text.replace(new RegExp('[\u0000-\u1eeff]', 'g'), '')
|
||||
const visibleChars = text.replace(new RegExp('[\n\r\s]+|( )+', 'g'), '')
|
||||
return onlyEmojis.length === visibleChars.length
|
||||
},
|
||||
|
||||
copyText() {
|
||||
window.App.util.clipboard(this.ctxContext.text);
|
||||
this.closeCtxMenu();
|
||||
return;
|
||||
},
|
||||
|
||||
clickLink() {
|
||||
let url = this.ctxContext.text;
|
||||
if(this.ctxContext.meta.local != true) {
|
||||
url = '/i/redirect?url=' + encodeURI(this.ctxContext.text);
|
||||
}
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
markAsRead() {
|
||||
return;
|
||||
axios.post('/api/direct/read', {
|
||||
pid: this.accountId,
|
||||
sid: this.max_id
|
||||
}).then(res => {
|
||||
}).catch(err => {
|
||||
});
|
||||
},
|
||||
|
||||
loadOlderMessages() {
|
||||
let self = this;
|
||||
this.loadingMessages = true;
|
||||
|
||||
axios.get('/api/direct/thread', {
|
||||
params: {
|
||||
pid: this.accountId,
|
||||
max_id: this.min_id,
|
||||
}
|
||||
}).then(res => {
|
||||
let d = res.data;
|
||||
if(!d.messages.length) {
|
||||
this.showLoadMore = false;
|
||||
this.loadingMessages = false;
|
||||
return;
|
||||
}
|
||||
let cids = this.thread.messages.map(m => m.id);
|
||||
let m = d.messages.filter(m => {
|
||||
return cids.indexOf(m.id) == -1;
|
||||
}).reverse();
|
||||
let mids = m.map(m => m.id);
|
||||
let min_id = Math.min(...mids);
|
||||
if(min_id == this.min_id) {
|
||||
this.showLoadMore = false;
|
||||
this.loadingMessages = false;
|
||||
return;
|
||||
}
|
||||
this.min_id = min_id;
|
||||
this.thread.messages.unshift(...m);
|
||||
setTimeout(function() {
|
||||
self.loadingMessages = false;
|
||||
}, 500);
|
||||
}).catch(err => {
|
||||
this.loadingMessages = false;
|
||||
})
|
||||
},
|
||||
|
||||
messagePoll() {
|
||||
let self = this;
|
||||
setInterval(function() {
|
||||
axios.get('/api/direct/thread', {
|
||||
params: {
|
||||
pid: self.accountId,
|
||||
min_id: self.thread.messages[self.thread.messages.length - 1].id
|
||||
}
|
||||
}).then(res => {
|
||||
});
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
showOptions() {
|
||||
this.page = 'options';
|
||||
},
|
||||
|
||||
updateOptions() {
|
||||
let options = {
|
||||
v: 1,
|
||||
hideAvatars: this.hideAvatars,
|
||||
hideTimestamps: this.hideTimestamps,
|
||||
largerText: this.largerText
|
||||
}
|
||||
window.localStorage.setItem('px_dm_options', JSON.stringify(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,4 +1,9 @@
|
||||
Vue.component(
|
||||
'direct-component',
|
||||
require('./components/Direct.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'direct-message',
|
||||
require('./components/DirectMessage.vue').default
|
||||
);
|
||||
@ -0,0 +1,12 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
<direct-message account-id="{{$id}}"></direct-message>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/direct.js') }}"></script>
|
||||
<script type="text/javascript">App.boot();</script>
|
||||
@endpush
|
||||
Loading…
Reference in New Issue