diff --git a/app/Jobs/CommentPipeline/CommentPipeline.php b/app/Jobs/CommentPipeline/CommentPipeline.php index 4876138ce..db0951181 100644 --- a/app/Jobs/CommentPipeline/CommentPipeline.php +++ b/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); + }); } } diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php index 528617e96..fea254a8b 100644 --- a/app/Jobs/StatusPipeline/StatusDelete.php +++ b/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(); - Notification::whereItemType('App\Status') - ->whereItemId($comment->id) - ->delete(); + if($status->in_reply_to_id) { + DB::transaction(function() use($status) { + $parent = Status::findOrFail($status->in_reply_to_id); + --$parent->reply_count; + $parent->save(); + }); } - - $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(); + 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($status->id) + ->delete(); + StatusHashtag::whereStatusId($status->id)->delete(); + Report::whereObjectType('App\Status') + ->whereObjectId($status->id) + ->delete(); + $status->delete(); + }); return true; } diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index dd1c352a7..df1ed6775 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/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; diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index 91b2d21c7..4827a6c6f 100644 --- a/app/Services/NotificationService.php +++ b/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) diff --git a/app/Util/Lexer/Autolink.php b/app/Util/Lexer/Autolink.php index fdd06d9cb..285ff65da 100755 --- a/app/Util/Lexer/Autolink.php +++ b/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 { diff --git a/app/Util/Lexer/Extractor.php b/app/Util/Lexer/Extractor.php index c7b75682b..734938011 100755 --- a/app/Util/Lexer/Extractor.php +++ b/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], ]; diff --git a/app/Util/Lexer/Regex.php b/app/Util/Lexer/Regex.php index 2b65fb326..c24e0d4b0 100755 --- a/app/Util/Lexer/Regex.php +++ b/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'; diff --git a/public/js/status.js b/public/js/status.js index 4e80f52ea..0ebabeea0 100644 Binary files a/public/js/status.js and b/public/js/status.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 4ebfd970a..c25d1d605 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/components/PostComponent.vue b/resources/assets/js/components/PostComponent.vue index 9c9fb9a7f..f3edddbea 100644 --- a/resources/assets/js/components/PostComponent.vue +++ b/resources/assets/js/components/PostComponent.vue @@ -107,7 +107,7 @@
-

+

{{statusUsername}}

@@ -120,8 +120,8 @@
-
-
+
+

{{truncate(reply.account.username,15)}} @@ -133,21 +133,32 @@

- + {{reply.favourites_count == 1 ? '1 like' : reply.favourites_count + ' likes'}} - Reply + Reply

{{reply.thread ? 'Hide' : 'View'}} Replies ({{reply.reply_count}})
-

- - {{s.account.username}} - - -

+
+

+ + {{s.account.username}} + + + + + + +

+

+ + {{s.favourites_count == 1 ? '1 like' : s.favourites_count + ' likes'}} + Reply +

+
@@ -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 + ' '; diff --git a/tests/Unit/Lexer/StatusLexerTest.php b/tests/Unit/Lexer/StatusLexerTest.php index 5b0fe37de..fb25ee306 100644 --- a/tests/Unit/Lexer/StatusLexerTest.php +++ b/tests/Unit/Lexer/StatusLexerTest.php @@ -62,4 +62,48 @@ class StatusLexerTest extends TestCase $expected = '@pixelfed hi, really like the website! #pรญxelfed'; $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); + } }