فهرست منبع

Merge pull request #1310 from pixelfed/frontend-ui-refactor

Frontend ui refactor
daniel 6 سال پیش
والد
کامیت
1e86169b79

+ 11 - 15
app/Jobs/CommentPipeline/CommentPipeline.php

@@ -2,16 +2,18 @@
 
 namespace App\Jobs\CommentPipeline;
 
-use App\Notification;
-use App\Status;
-use Cache;
+use App\{
+    Notification,
+    Status
+};
+use App\Services\NotificationService;
+use DB, Cache, Log, Redis;
+
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Queue\SerializesModels;
-use Log;
-use Redis;
 
 class CommentPipeline implements ShouldQueue
 {
@@ -55,7 +57,7 @@ class CommentPipeline implements ShouldQueue
             return true;
         }
 
-        try {
+        DB::transaction(function() use($target, $actor, $comment) {
             $notification = new Notification();
             $notification->profile_id = $target->id;
             $notification->actor_id = $actor->id;
@@ -66,14 +68,8 @@ class CommentPipeline implements ShouldQueue
             $notification->item_type = "App\Status";
             $notification->save();
 
-            Cache::forever('notification.'.$notification->id, $notification);
-
-            $redis = Redis::connection();
-
-            $nkey = config('cache.prefix').':user.'.$target->id.'.notifications';
-            $redis->lpush($nkey, $notification->id);
-        } catch (Exception $e) {
-            Log::error($e);
-        }
+            NotificationService::setNotification($notification);
+            NotificationService::set($notification->profile_id, $notification->id);
+        });
     }
 }

+ 25 - 16
app/Jobs/StatusPipeline/StatusDelete.php

@@ -2,6 +2,7 @@
 
 namespace App\Jobs\StatusPipeline;
 
+use DB;
 use App\{
     Notification,
     Report,
@@ -79,24 +80,32 @@ class StatusDelete implements ShouldQueue
             } catch (Exception $e) {
             }
         }
-        $comments = Status::where('in_reply_to_id', $status->id)->get();
-        foreach ($comments as $comment) {
-            $comment->in_reply_to_id = null;
-            $comment->save();
+        if($status->in_reply_to_id) {
+            DB::transaction(function() use($status) {
+                $parent = Status::findOrFail($status->in_reply_to_id);
+                --$parent->reply_count;
+                $parent->save();
+            });
+        }
+        DB::transaction(function() use($status) {
+            $comments = Status::where('in_reply_to_id', $status->id)->get();
+            foreach ($comments as $comment) {
+                $comment->in_reply_to_id = null;
+                $comment->save();
+                Notification::whereItemType('App\Status')
+                    ->whereItemId($comment->id)
+                    ->delete();
+            }
+            $status->likes()->delete();
             Notification::whereItemType('App\Status')
-                ->whereItemId($comment->id)
+                ->whereItemId($status->id)
                 ->delete();
-        }
-
-        $status->likes()->delete();
-        Notification::whereItemType('App\Status')
-            ->whereItemId($status->id)
-            ->delete();
-        StatusHashtag::whereStatusId($status->id)->delete();
-        Report::whereObjectType('App\Status')
-            ->whereObjectId($status->id)
-            ->delete();
-        $status->delete();
+            StatusHashtag::whereStatusId($status->id)->delete();
+            Report::whereObjectType('App\Status')
+                ->whereObjectId($status->id)
+                ->delete();
+            $status->delete();
+        });
 
         return true;
     }

+ 1 - 1
app/Jobs/StatusPipeline/StatusEntityLexer.php

@@ -112,7 +112,7 @@ class StatusEntityLexer implements ShouldQueue
         $status = $this->status;
 
         foreach ($mentions as $mention) {
-            $mentioned = Profile::whereUsername($mention)->firstOrFail();
+            $mentioned = Profile::whereNull('domain')->whereUsername($mention)->firstOrFail();
 
             if (empty($mentioned) || !isset($mentioned->id)) {
                 continue;

+ 10 - 0
app/Services/NotificationService.php

@@ -79,6 +79,16 @@ class NotificationService {
 			$resource = new Fractal\Resource\Item($n, new NotificationTransformer());
 			return $fractal->createData($resource)->toArray();
 		});
+	}
+
+	public static function setNotification(Notification $notification)
+	{
+		return Cache::remember('service:notification:'.$notification->id, now()->addDays(7), function() use($notification) {
+			$fractal = new Fractal\Manager();
+			$fractal->setSerializer(new ArraySerializer());
+			$resource = new Fractal\Resource\Item($notification, new NotificationTransformer());
+			return $fractal->createData($resource)->toArray();
+		});
 	} 
 
 	public static function warmCache($id, $stop = 100, $force = false)

+ 8 - 2
app/Util/Lexer/Autolink.php

@@ -9,6 +9,8 @@
 
 namespace App\Util\Lexer;
 
+use Illuminate\Support\Str;
+
 /**
  * Twitter Autolink Class.
  *
@@ -413,7 +415,11 @@ class Autolink extends Regex
         $beginIndex = 0;
         foreach ($entities as $entity) {
             if (isset($entity['screen_name'])) {
-                $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex + 1);
+                if(Str::startsWith($entity['screen_name'], '@')) {
+                    $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
+                } else {
+                    $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex + 1);
+                }
             } else {
                 $text .= StringUtils::substr($tweet, $beginIndex, $entity['indices'][0] - $beginIndex);
             }
@@ -704,7 +710,7 @@ class Autolink extends Regex
 
         if (!empty($entity['list_slug'])) {
             // Replace the list and username
-            $linkText = $entity['screen_name'].$entity['list_slug'];
+            $linkText = $entity['screen_name'];
             $class = $this->class_list;
             $url = $this->url_base_list.$linkText;
         } else {

+ 4 - 1
app/Util/Lexer/Extractor.php

@@ -9,6 +9,8 @@
 
 namespace App\Util\Lexer;
 
+use Illuminate\Support\Str;
+
 /**
  * Twitter Extractor Class.
  *
@@ -452,8 +454,9 @@ class Extractor extends Regex
             list($all, $before, $at, $username, $list_slug, $outer) = array_pad($match, 6, ['', 0]);
             $start_position = $at[1] > 0 ? StringUtils::strlen(substr($tweet, 0, $at[1])) : $at[1];
             $end_position = $start_position + StringUtils::strlen($at[0]) + StringUtils::strlen($username[0]);
+            $screenname = trim($all[0]) == '@'.$username[0] ? $username[0] : trim($all[0]);
             $entity = [
-                'screen_name' => $username[0],
+                'screen_name' => $screenname,
                 'list_slug'   => $list_slug[0],
                 'indices'     => [$start_position, $end_position],
             ];

+ 3 - 1
app/Util/Lexer/Regex.php

@@ -161,7 +161,9 @@ abstract class Regex
         //      $after in the following regular expression.  Note that we only use a
         //      look-ahead capture here and don't append $after when we return.
         $tmp['valid_mention_preceding_chars'] = '([^a-zA-Z0-9_!#\$%&*@@\/]|^|(?:^|[^a-z0-9_+~.-])RT:?)';
-        $re['valid_mentions_or_lists'] = '/'.$tmp['valid_mention_preceding_chars'].'(['.$tmp['at_signs'].'])([a-z0-9_]{1,20})(\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))/iu';
+
+        $re['valid_mentions_or_lists'] = '/'.$tmp['valid_mention_preceding_chars'].'(['.$tmp['at_signs'].'])([a-z0-9_]{1,20})((\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i';
+
         $re['valid_reply'] = '/^(?:['.$tmp['spaces'].'])*['.$tmp['at_signs'].']([a-z0-9_]{1,20})(?=(.*|$))/iu';
         $re['end_mention_match'] = '/\A(?:['.$tmp['at_signs'].']|['.$tmp['latin_accents'].']|:\/\/)/iu';
 

BIN
public/js/status.js


BIN
public/mix-manifest.json


+ 42 - 13
resources/assets/js/components/PostComponent.vue

@@ -107,7 +107,7 @@
             <div class="d-flex flex-md-column flex-column-reverse h-100">
               <div class="card-body status-comments pb-5">
                 <div class="status-comment">
-                  <p class="mb-1 read-more" style="overflow: hidden;">
+                  <p :class="[status.content.length > 420 ? 'mb-1 read-more' : 'mb-1']" style="overflow: hidden;">
                     <span class="font-weight-bold pr-1">{{statusUsername}}</span>
                     <span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
                   </p>
@@ -120,8 +120,8 @@
                     </div>
                     <div class="postCommentsContainer d-none pt-3">
                       <p v-if="status.reply_count > 10"class="mb-1 text-center load-more-link d-none"><a href="#" class="text-muted" v-on:click="loadMore">Load more comments</a></p>
-                      <div class="comments" data-min-id="0" data-max-id="0">
-                        <div v-for="(reply, index) in results" class="pb-3">
+                      <div class="comments">
+                        <div v-for="(reply, index) in results" class="pb-3" :key="'tl' + reply.id + '_' + index">
                           <p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
                             <span>
                               <a class="text-dark font-weight-bold mr-1" :href="reply.account.url" v-bind:title="reply.account.username">{{truncate(reply.account.username,15)}}</a>
@@ -133,21 +133,32 @@
                             </span>
                           </p>
                           <p class="">
-                            <span class="text-muted mr-3" style="width: 20px;" v-text="timeAgo(reply.created_at)"></span>
+                            <a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(reply.created_at)" :href="reply.url"></a>
                             <span v-if="reply.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{reply.favourites_count == 1 ? '1 like' : reply.favourites_count + ' likes'}}</span>
-                            <span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply)">Reply</span>
+                            <span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply, index)">Reply</span>
                           </p>
                           <div v-if="reply.reply_count > 0" class="cursor-pointer" style="margin-left:30px;" v-on:click="toggleReplies(reply)">
                              <span class="show-reply-bar"></span>
                              <span class="comment-reaction font-weight-bold text-muted">{{reply.thread ? 'Hide' : 'View'}} Replies ({{reply.reply_count}})</span>
                           </div>
                           <div v-if="reply.thread == true" class="comment-thread">
-                            <p class="d-flex justify-content-between align-items-top read-more pb-3" style="overflow-y: hidden;" v-for="(s, index) in reply.replies">
-                              <span>
-                                <a class="text-dark font-weight-bold mr-1" :href="s.account.url" :title="s.account.username">{{s.account.username}}</a>
-                                <span class="text-break" v-html="s.content"></span>
-                              </span>
-                            </p>
+                            <div v-for="(s, sindex) in reply.replies" class="pb-3" :key="'cr' + s.id + '_' + index">
+                              <p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
+                                <span>
+                                  <a class="text-dark font-weight-bold mr-1" :href="s.account.url" :title="s.account.username">{{s.account.username}}</a>
+                                  <span class="text-break" v-html="s.content"></span>
+                                </span>
+                                <span class="pl-2" style="min-width:38px">
+                                  <span v-on:click="likeReply(s, $event)"><i v-bind:class="[s.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
+                                    <post-menu :status="s" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteCommentReply(s.id, sindex, index) "></post-menu>
+                                </span>
+                              </p>
+                              <p class="">
+                                <a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(s.created_at)" :href="s.url"></a>
+                                <span v-if="s.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{s.favourites_count == 1 ? '1 like' : s.favourites_count + ' likes'}}</span>
+                                <span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(s, sindex)">Reply</span>
+                              </p>
+                            </div>
                           </div>
                         </div>
                       </div>
@@ -473,6 +484,7 @@ export default {
             loaded: false,
             loading: null,
             replyingToId: this.statusId,
+            replyToIndex: 0,
             emoji: ['😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','😗','😙','😚','☺️','🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹️','🙁','😖','😞','😟','😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🙌','👏','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👌','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'],
           }
     },
@@ -750,7 +762,11 @@ export default {
             let elem = $('.status-comments')[0];
             elem.scrollTop = elem.clientHeight;
           } else {
-
+            if(self.replyToIndex >= 0) {
+              let el = self.results[self.replyToIndex];
+              el.replies.push(entity);
+              el.reply_count = el.reply_count + 1;
+            }
           }
           self.replyText = '';
         });
@@ -767,13 +783,26 @@ export default {
         });
       },
 
+      deleteCommentReply(id, i, pi) {
+        axios.post('/i/delete', {
+          type: 'comment',
+          item: id
+        }).then(res => {
+          this.results[pi].replies.splice(i, 1);
+          --this.results[pi].reply_count;
+        }).catch(err => {
+          swal('Something went wrong!', 'Please try again later', 'error');
+        });
+      },
+
       l(e) {
         let len = e.length;
         if(len < 10) { return e; }
         return e.substr(0, 10)+'...';
       },
 
-      replyFocus(e) {
+      replyFocus(e, index) {
+          this.replyToIndex = index;
           this.replyingToId = e.id;
           this.reply_to_profile_id = e.account.id;
           this.replyText = '@' + e.account.username + ' ';

+ 44 - 0
tests/Unit/Lexer/StatusLexerTest.php

@@ -62,4 +62,48 @@ class StatusLexerTest extends TestCase
         $expected = '@<a class="u-url mention" href="https://pixelfed.dev/pixelfed" rel="external nofollow noopener" target="_blank">pixelfed</a> hi, really like the website! <a href="https://pixelfed.dev/discover/tags/píxelfed?src=hash" title="#píxelfed" class="u-url hashtag" rel="external nofollow noopener">#píxelfed</a>';
         $this->assertEquals($this->autolink, $expected);
     }
+
+    /** @test **/
+    public function remoteMention()
+    {
+        $expected = [
+            "hashtags" => [
+                "dansup",
+            ],
+            "urls" => [],
+            "mentions" => [
+                "@dansup@mstdn.io",
+                "test",
+            ],
+            "replyto" => null,
+            "hashtags_with_indices" => [
+                [
+                    "hashtag" => "dansup",
+                    "indices" => [
+                        0,
+                        7,
+                    ],
+                ],
+            ],
+            "urls_with_indices" => [],
+            "mentions_with_indices" => [
+                [
+                    "screen_name" => "@dansup@mstdn.io",
+                    "indices" => [
+                        8,
+                        24,
+                    ],
+                ],
+                [
+                    "screen_name" => "test",
+                    "indices" => [
+                        25,
+                        30,
+                    ],
+                ],
+            ],
+        ];
+        $actual = Extractor::create()->extract('#dansup @dansup@mstdn.io @test');
+        $this->assertEquals($actual, $expected);
+    }
 }