Browse Source

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

Frontend ui refactor
daniel 6 năm trước cách đây
mục cha
commit
1b16e5d696
30 tập tin đã thay đổi với 1662 bổ sung1553 xóa
  1. 10 0
      app/FollowRequest.php
  2. 66 0
      app/Http/Controllers/Api/InstanceApiController.php
  3. 20 3
      app/Http/Controllers/CommentController.php
  4. 1 0
      app/Http/Controllers/FederationController.php
  5. 6 0
      app/Http/Controllers/SettingsController.php
  6. 1 1
      app/Http/Controllers/StatusController.php
  7. 57 0
      app/Jobs/FollowPipeline/FollowActivityPubDeliver.php
  8. 4 1
      app/Jobs/StatusPipeline/NewStatusPipeline.php
  9. 6 1
      app/Providers/AuthServiceProvider.php
  10. 1 1
      app/Transformer/ActivityPub/Verb/Follow.php
  11. 23 23
      app/Transformer/Api/AccountTransformer.php
  12. 7 7
      app/Transformer/Api/ApplicationTransformer.php
  13. 28 0
      app/Transformer/Api/AttachmentTransformer.php
  14. 16 0
      app/Transformer/Api/ContextTransformer.php
  15. 9 9
      app/Transformer/Api/EmojiTransformer.php
  16. 20 0
      app/Transformer/Api/FilterTransformer.php
  17. 7 7
      app/Transformer/Api/HashtagTransformer.php
  18. 14 14
      app/Transformer/Api/MediaTransformer.php
  19. 24 0
      app/Transformer/Api/ResultsTransformer.php
  20. 10 31
      app/Util/ActivityPub/Inbox.php
  21. 28 0
      app/Util/ActivityPub/Validator/Announce.php
  22. 22 0
      config/pixelfed.php
  23. 32 0
      database/migrations/2018_12_30_065102_update_profiles_table_use_text_for_bio.php
  24. 1030 1430
      package-lock.json
  25. 6 1
      package.json
  26. 2 2
      resources/assets/js/components/PostComments.vue
  27. 1 1
      resources/assets/js/components/PostComponent.vue
  28. 32 18
      resources/assets/js/components/Timeline.vue
  29. 11 3
      resources/views/site/help/your-profile.blade.php
  30. 168 0
      tests/Unit/ActivityPub/Verb/AnnounceTest.php

+ 10 - 0
app/FollowRequest.php

@@ -17,4 +17,14 @@ class FollowRequest extends Model
     {
     {
         return $this->belongsTo(Profile::class, 'following_id', 'id');
         return $this->belongsTo(Profile::class, 'following_id', 'id');
     }
     }
+
+    public function actor()
+    {
+        return $this->belongsTo(Profile::class, 'follower_id', 'id');
+    }
+
+    public function target()
+    {
+        return $this->belongsTo(Profile::class, 'following_id', 'id');
+    }
 }
 }

+ 66 - 0
app/Http/Controllers/Api/InstanceApiController.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use App\{Profile, Status, User};
+use Cache;
+
+class InstanceApiController extends Controller {
+
+	protected function getData()
+	{
+		$contact = Cache::remember('api:v1:instance:contact', 1440, function() {
+			$admin = User::whereIsAdmin(true)->first()->profile;
+			return [
+				'id' 			  => $admin->id,
+				'username' 		  => $admin->username,
+				'acct'			  => $admin->username,
+				'display_name' 	  => e($admin->name),
+				'locked' 		  => (bool) $admin->is_private,
+				'bot'			  => false,
+				'created_at' 	  => $admin->created_at->format('c'),
+				'note' 			  => e($admin->bio),
+				'url' 			  => $admin->url(),
+				'avatar' 		  => $admin->avatarUrl(),
+				'avatar_static'   => $admin->avatarUrl(),
+				'header'          => null,
+				'header_static'   => null,
+				'moved'           => null,
+				'fields'          => null,
+				'bot'             => null,
+			];
+		});
+
+		$res = [
+			'uri' => config('pixelfed.domain.app'),
+			'title' => config('app.name'),
+			'description' => '',
+			'version' => config('pixelfed.version'),
+			'urls' => [],
+			'stats' => [
+				'user_count' => User::count(),
+				'status_count' => Status::whereNull('uri')->count(),
+				'domain_count' => Profile::whereNotNull('domain')
+					->groupBy('domain')
+					->pluck('domain')
+					->count()
+			],
+			'thumbnail' => '',
+			'languages' => [],
+			'contact_account' => $contact
+		];
+		return $res;
+	}
+
+	public function instance()
+	{
+		$res = Cache::remember('api:v1:instance', 60, function() {
+			return json_encode($this->getData());
+		});
+
+		return response($res)->header('Content-Type', 'application/json');
+	}
+
+}

+ 20 - 3
app/Http/Controllers/CommentController.php

@@ -2,13 +2,18 @@
 
 
 namespace App\Http\Controllers;
 namespace App\Http\Controllers;
 
 
+use Illuminate\Http\Request;
+use Auth;
+
 use App\Comment;
 use App\Comment;
 use App\Jobs\CommentPipeline\CommentPipeline;
 use App\Jobs\CommentPipeline\CommentPipeline;
 use App\Jobs\StatusPipeline\NewStatusPipeline;
 use App\Jobs\StatusPipeline\NewStatusPipeline;
 use App\Profile;
 use App\Profile;
 use App\Status;
 use App\Status;
-use Auth;
-use Illuminate\Http\Request;
+use League\Fractal;
+use App\Transformer\Api\StatusTransformer;
+use League\Fractal\Serializer\ArraySerializer;
+use League\Fractal\Pagination\IlluminatePaginatorAdapter;
 
 
 class CommentController extends Controller
 class CommentController extends Controller
 {
 {
@@ -57,7 +62,19 @@ class CommentController extends Controller
         CommentPipeline::dispatch($status, $reply);
         CommentPipeline::dispatch($status, $reply);
 
 
         if ($request->ajax()) {
         if ($request->ajax()) {
-            $response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption];
+            $fractal = new Fractal\Manager();
+            $fractal->setSerializer(new ArraySerializer());
+            $entity = new Fractal\Resource\Item($reply, new StatusTransformer());
+            $entity = $fractal->createData($entity)->toArray();
+            $response = [
+                'code' => 200, 
+                'msg' => 'Comment saved', 
+                'username' => $profile->username, 
+                'url' => $reply->url(), 
+                'profile' => $profile->url(), 
+                'comment' => $reply->caption,
+                'entity' => $entity,
+            ];
         } else {
         } else {
             $response = redirect($status->url());
             $response = redirect($status->url());
         }
         }

+ 1 - 0
app/Http/Controllers/FederationController.php

@@ -15,6 +15,7 @@ use Illuminate\Http\Request;
 use League\Fractal;
 use League\Fractal;
 use App\Util\ActivityPub\Helpers;
 use App\Util\ActivityPub\Helpers;
 use App\Util\ActivityPub\HttpSignature;
 use App\Util\ActivityPub\HttpSignature;
+use \Zttp\Zttp;
 
 
 class FederationController extends Controller
 class FederationController extends Controller
 {
 {

+ 6 - 0
app/Http/Controllers/SettingsController.php

@@ -149,11 +149,17 @@ class SettingsController extends Controller
 
 
     public function removeAccountPermanent(Request $request)
     public function removeAccountPermanent(Request $request)
     {
     {
+        if(config('pixelfed.account_deletion') == false) {
+            abort(404);
+        }
         return view('settings.remove.permanent');
         return view('settings.remove.permanent');
     }
     }
 
 
     public function removeAccountPermanentSubmit(Request $request)
     public function removeAccountPermanentSubmit(Request $request)
     {
     {
+        if(config('pixelfed.account_deletion') == false) {
+            abort(404);
+        }
         $user = Auth::user();
         $user = Auth::user();
         if($user->is_admin == true) {
         if($user->is_admin == true) {
             return abort(400, 'You cannot delete an admin account.');
             return abort(400, 'You cannot delete an admin account.');

+ 1 - 1
app/Http/Controllers/StatusController.php

@@ -19,7 +19,7 @@ class StatusController extends Controller
 {
 {
     public function show(Request $request, $username, int $id)
     public function show(Request $request, $username, int $id)
     {
     {
-        $user = Profile::whereUsername($username)->firstOrFail();
+        $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
 
 
         if($user->status != null) {
         if($user->status != null) {
             return ProfileController::accountCheck($user);
             return ProfileController::accountCheck($user);

+ 57 - 0
app/Jobs/FollowPipeline/FollowActivityPubDeliver.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace App\Jobs\FollowPipeline;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+
+use Cache, Log, Redis;
+use League\Fractal;
+use League\Fractal\Serializer\ArraySerializer;
+use App\FollowRequest;
+use App\Util\ActivityPub\Helpers;
+use App\Transformer\ActivityPub\Verb\Follow;
+
+class FollowActivityPubDeliver implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    protected $followRequest;
+
+    /**
+     * Create a new job instance.
+     *
+     * @return void
+     */
+    public function __construct(FollowRequest $followRequest)
+    {
+        $this->followRequest = $followRequest;
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @return void
+     */
+    public function handle()
+    {
+        $follow = $this->followRequest;
+        $actor = $follow->actor;
+        $target = $follow->target;
+
+        if($target->domain == null || $target->inbox_url == null) {
+        	return;
+        }
+
+        $fractal = new Fractal\Manager();
+        $fractal->setSerializer(new ArraySerializer());
+        $resource = new Fractal\Resource\Item($follow, new Follow());
+        $activity = $fractal->createData($resource)->toArray();
+        $url = $target->sharedInbox ?? $target->inbox_url;
+        
+        Helpers::sendSignedObject($actor, $url, $activity);
+    }
+}

+ 4 - 1
app/Jobs/StatusPipeline/NewStatusPipeline.php

@@ -37,7 +37,10 @@ class NewStatusPipeline implements ShouldQueue
         $status = $this->status;
         $status = $this->status;
 
 
         StatusEntityLexer::dispatch($status);
         StatusEntityLexer::dispatch($status);
-        StatusActivityPubDeliver::dispatch($status);
+
+        if(config('pixelfed.activitypub_enabled') == true) {
+            StatusActivityPubDeliver::dispatch($status);
+        }
         
         
         // Cache::forever('post.'.$status->id, $status);
         // Cache::forever('post.'.$status->id, $status);
         // $redis = Redis::connection();
         // $redis = Redis::connection();

+ 6 - 1
app/Providers/AuthServiceProvider.php

@@ -3,6 +3,7 @@
 namespace App\Providers;
 namespace App\Providers;
 
 
 use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
 use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
+use Laravel\Passport\Passport;
 
 
 class AuthServiceProvider extends ServiceProvider
 class AuthServiceProvider extends ServiceProvider
 {
 {
@@ -24,6 +25,10 @@ class AuthServiceProvider extends ServiceProvider
     {
     {
         $this->registerPolicies();
         $this->registerPolicies();
 
 
-        //
+        // Passport::routes();
+
+        // Passport::tokensExpireIn(now()->addDays(15));
+
+        // Passport::refreshTokensExpireIn(now()->addDays(30));
     }
     }
 }
 }

+ 1 - 1
app/Transformer/ActivityPub/Verb/Follow.php

@@ -7,7 +7,7 @@ use League\Fractal;
 
 
 class Follow extends Fractal\TransformerAbstract
 class Follow extends Fractal\TransformerAbstract
 {
 {
-    public function transform(Follower $follower)
+    public function transform($follower)
     {
     {
     	return [
     	return [
     		'@context'  => 'https://www.w3.org/ns/activitystreams',
     		'@context'  => 'https://www.w3.org/ns/activitystreams',

+ 23 - 23
app/Transformer/Api/AccountTransformer.php

@@ -7,27 +7,27 @@ use League\Fractal;
 
 
 class AccountTransformer extends Fractal\TransformerAbstract
 class AccountTransformer extends Fractal\TransformerAbstract
 {
 {
-    public function transform(Profile $profile)
-    {
-        return [
-          'id'              => $profile->id,
-          'username'        => $profile->username,
-          'acct'            => $profile->username,
-          'display_name'    => $profile->name,
-          'locked'          => (bool) $profile->is_private,
-          'created_at'      => $profile->created_at->format('c'),
-          'followers_count' => $profile->followerCount(),
-          'following_count' => $profile->followingCount(),
-          'statuses_count'  => $profile->statusCount(),
-          'note'            => $profile->bio,
-          'url'             => $profile->url(),
-          'avatar'          => $profile->avatarUrl(),
-          'avatar_static'   => $profile->avatarUrl(),
-          'header'          => null,
-          'header_static'   => null,
-          'moved'           => null,
-          'fields'          => null,
-          'bot'             => null,
-      ];
-    }
+	public function transform(Profile $profile)
+	{
+		return [
+			'id' => $profile->id,
+			'username' => $profile->username,
+			'acct' => $profile->username,
+			'display_name' => $profile->name,
+			'locked' => (bool) $profile->is_private,
+			'created_at' => $profile->created_at->format('c'),
+			'followers_count' => $profile->followerCount(),
+			'following_count' => $profile->followingCount(),
+			'statuses_count' => $profile->statusCount(),
+			'note' => $profile->bio,
+			'url' => $profile->url(),
+			'avatar' => $profile->avatarUrl(),
+			'avatar_static' => $profile->avatarUrl(),
+			'header' => null,
+			'header_static' => null,
+			'moved' => null,
+			'fields' => null,
+			'bot' => null,
+		];
+	}
 }
 }

+ 7 - 7
app/Transformer/Api/ApplicationTransformer.php

@@ -6,11 +6,11 @@ use League\Fractal;
 
 
 class ApplicationTransformer extends Fractal\TransformerAbstract
 class ApplicationTransformer extends Fractal\TransformerAbstract
 {
 {
-    public function transform()
-    {
-        return [
-        'name'    => '',
-        'website' => null,
-      ];
-    }
+	public function transform()
+	{
+		return [
+			'name'    => '',
+			'website' => null,
+		];
+	}
 }
 }

+ 28 - 0
app/Transformer/Api/AttachmentTransformer.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Transformer\Api;
+
+use League\Fractal;
+
+class AttachmentTransformer extends Fractal\TransformerAbstract
+{
+	public function transform(Media $media)
+	{
+		return [
+			'id'			=> $media->id,
+			'type'			=> $media->activityVerb(),
+			'url'			=> $media->url(),
+			'remote_url' 	=> null,
+			'preview_url'	=> $media->thumbnailUrl(),
+			'text_url'		=> null,
+			'meta'			=> null,
+			'description'	=> $media->caption,
+			'license'		=> $media->license,
+			'is_nsfw'		=> $media->is_nsfw,
+			'orientation'	=> $media->orientation,
+			'filter_name'	=> $media->filter_name,
+			'filter_class'	=> $media->filter_class,
+			'mime'			=> $media->mime,
+		];
+	}
+}

+ 16 - 0
app/Transformer/Api/ContextTransformer.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Transformer\Api;
+
+use League\Fractal;
+
+class ContextTransformer extends Fractal\TransformerAbstract
+{
+	public function transform()
+	{
+		return [
+			'ancestors' => [],
+			'descendants' => []
+		];
+	}
+}

+ 9 - 9
app/Transformer/Api/EmojiTransformer.php

@@ -6,13 +6,13 @@ use League\Fractal;
 
 
 class EmojiTransformer extends Fractal\TransformerAbstract
 class EmojiTransformer extends Fractal\TransformerAbstract
 {
 {
-    public function transform($emoji)
-    {
-        return [
-            'shortcode' 			=> '',
-            'static_url'			=> '',
-            'url'					=> '',
-            'visible_in_picker' 	=> false
-        ];
-    }
+	public function transform($emoji)
+	{
+		return [
+			'shortcode' 			=> '',
+			'static_url'			=> '',
+			'url'					=> '',
+			'visible_in_picker' 	=> false
+		];
+	}
 }
 }

+ 20 - 0
app/Transformer/Api/FilterTransformer.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Transformer\Api;
+
+use League\Fractal;
+
+class FilterTransformer extends Fractal\TransformerAbstract
+{
+	public function transform()
+	{
+		return [
+			'id' => (string) '',
+			'phrase' => (string) '',
+			'context' => [],
+			'expires_at' => null,
+			'irreversible' => (bool) false,
+			'whole_word' => (bool) false
+		];
+	}
+}

+ 7 - 7
app/Transformer/Api/HashtagTransformer.php

@@ -7,11 +7,11 @@ use League\Fractal;
 
 
 class HashtagTransformer extends Fractal\TransformerAbstract
 class HashtagTransformer extends Fractal\TransformerAbstract
 {
 {
-    public function transform(Hashtag $hashtag)
-    {
-        return [
-            'name' => $hashtag->name,
-            'url'  => $hashtag->url(),
-        ];
-    }
+	public function transform(Hashtag $hashtag)
+	{
+		return [
+			'name' => $hashtag->name,
+			'url'  => $hashtag->url(),
+		];
+	}
 }
 }

+ 14 - 14
app/Transformer/Api/MediaTransformer.php

@@ -10,20 +10,20 @@ class MediaTransformer extends Fractal\TransformerAbstract
     public function transform(Media $media)
     public function transform(Media $media)
     {
     {
         return [
         return [
-            'id'          => $media->id,
-            'type'        => $media->activityVerb(),
-            'url'         => $media->url(),
-            'remote_url'  => null,
-            'preview_url' => $media->thumbnailUrl(),
-            'text_url'    => null,
-            'meta'        => null,
-            'description' => $media->caption,
-            'license'     => $media->license,
-            'is_nsfw'     => $media->is_nsfw,
-            'orientation' => $media->orientation,
-            'filter_name' => $media->filter_name,
-            'filter_class' => $media->filter_class,
-            'mime'        => $media->mime,
+            'id'            => $media->id,
+            'type'          => $media->activityVerb(),
+            'url'           => $media->url(),
+            'remote_url'    => null,
+            'preview_url'   => $media->thumbnailUrl(),
+            'text_url'      => null,
+            'meta'          => null,
+            'description'   => $media->caption,
+            'license'       => $media->license,
+            'is_nsfw'       => $media->is_nsfw,
+            'orientation'   => $media->orientation,
+            'filter_name'   => $media->filter_name,
+            'filter_class'  => $media->filter_class,
+            'mime'          => $media->mime,
         ];
         ];
     }
     }
 }
 }

+ 24 - 0
app/Transformer/Api/ResultsTransformer.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Transformer\Api;
+
+use League\Fractal;
+
+class ResultsTransformer extends Fractal\TransformerAbstract
+{
+
+	protected $defaultIncludes = [
+		'account',
+		'mentions',
+		'media_attachments',
+		'tags',
+	];
+	public function transform()
+	{
+		return [
+			'accounts' => [],
+			'statuses' => [],
+			'hashtags' => []
+		];
+	}
+}

+ 10 - 31
app/Util/ActivityPub/Inbox.php

@@ -2,7 +2,7 @@
 
 
 namespace App\Util\ActivityPub;
 namespace App\Util\ActivityPub;
 
 
-use Cache, DB, Log, Redis, Validator;
+use Cache, DB, Log, Purify, Redis, Validator;
 use App\{
 use App\{
     Activity,
     Activity,
     Follower,
     Follower,
@@ -16,6 +16,10 @@ use Carbon\Carbon;
 use App\Util\ActivityPub\Helpers;
 use App\Util\ActivityPub\Helpers;
 use App\Jobs\LikePipeline\LikePipeline;
 use App\Jobs\LikePipeline\LikePipeline;
 
 
+use App\Util\ActivityPub\Validator\{
+    Follow
+};
+
 class Inbox
 class Inbox
 {
 {
     protected $headers;
     protected $headers;
@@ -35,30 +39,6 @@ class Inbox
         $this->handleVerb();
         $this->handleVerb();
     }
     }
 
 
-    public function authenticatePayload()
-    {
-        try {
-           $signature = Helpers::validateSignature($this->headers, $this->payload);
-           $payload = Helpers::validateObject($this->payload);
-           if($signature == false) {
-            return;
-           }
-        } catch (Exception $e) {
-           return; 
-        }
-        $this->payloadLogger(); 
-    }
-
-    public function payloadLogger()
-    {
-        $logger = new Activity;
-        $logger->data = json_encode($this->payload);
-        $logger->save();
-        $this->logger = $logger;
-        Log::info('AP:inbox:activity:new:'.$this->logger->id);
-        $this->handleVerb();
-    }
-
     public function handleVerb()
     public function handleVerb()
     {
     {
         $verb = $this->payload['type'];
         $verb = $this->payload['type'];
@@ -76,6 +56,7 @@ class Inbox
                 break;
                 break;
 
 
             case 'Accept':
             case 'Accept':
+                if(Accept::validate($this->payload) == false) { return; }
                 $this->handleAcceptActivity();
                 $this->handleAcceptActivity();
                 break;
                 break;
 
 
@@ -171,7 +152,8 @@ class Inbox
             $caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length'));
             $caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length'));
             $status = new Status;
             $status = new Status;
             $status->profile_id = $actor->id;
             $status->profile_id = $actor->id;
-            $status->caption = $caption;
+            $status->caption = strip_tags($caption);
+            $status->rendered = Purify::clean($caption);
             $status->visibility = $status->scope = 'public';
             $status->visibility = $status->scope = 'public';
             $status->uri = $url;
             $status->uri = $url;
             $status->url = $url;
             $status->url = $url;
@@ -275,13 +257,10 @@ class Inbox
         $obj = $this->payload['object'];
         $obj = $this->payload['object'];
         if(is_string($obj) && Helpers::validateUrl($obj)) {
         if(is_string($obj) && Helpers::validateUrl($obj)) {
             // actor object detected
             // actor object detected
-
+            // todo delete actor
         } else if (is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
         } else if (is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
             // tombstone detected
             // tombstone detected
-            $status = Status::whereUri($obj['id'])->first();
-            if($status == null) {
-                return;
-            }
+            $status = Status::whereUri($obj['id'])->firstOrFail();
             $status->forceDelete();
             $status->forceDelete();
         }
         }
     }
     }

+ 28 - 0
app/Util/ActivityPub/Validator/Announce.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Util\ActivityPub\Validator;
+
+use Validator;
+use Illuminate\Validation\Rule;
+
+class Announce {
+
+	public static function validate($payload)
+	{
+		$valid = Validator::make($payload, [
+			'@context' => 'required',
+			'id' => 'required|string',
+			'type' => [
+				'required',
+				Rule::in(['Announce'])
+			],
+			'actor' => 'required|url|active_url',
+			'published' => 'required|date',
+			'to'	=> 'required',
+			'cc'	=> 'required',
+			'object' => 'required|url|active_url'
+		])->passes();
+
+		return $valid;
+	}
+}

+ 22 - 0
config/pixelfed.php

@@ -177,6 +177,28 @@ return [
     */
     */
     'image_quality'  => (int) env('IMAGE_QUALITY', 80),
     'image_quality'  => (int) env('IMAGE_QUALITY', 80),
 
 
+    /*
+    |--------------------------------------------------------------------------
+    | Account deletion
+    |--------------------------------------------------------------------------
+    |
+    | Enable account deletion.
+    |
+    */
+    'account_deletion' => env('ACCOUNT_DELETION', true),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Account deletion after X days
+    |--------------------------------------------------------------------------
+    |
+    | Set account deletion queue after X days, set to false to delete accounts
+    | immediately.
+    |
+    */
+    'account_delete_after' => env('ACCOUNT_DELETE_AFTER', false),
+
+
     'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
     'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
     'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
     'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
     'ap_inbox' => env('ACTIVITYPUB_INBOX', false),
     'ap_inbox' => env('ACTIVITYPUB_INBOX', false),

+ 32 - 0
database/migrations/2018_12_30_065102_update_profiles_table_use_text_for_bio.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class UpdateProfilesTableUseTextForBio extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('profiles', function (Blueprint $table) {
+            $table->text('bio')->nullable()->change();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('profiles', function (Blueprint $table) {
+            $table->string('bio')->nullable()->change();
+        });
+    }
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1030 - 1430
package-lock.json


+ 6 - 1
package.json

@@ -15,9 +15,11 @@
         "bootstrap": "^4.2.1",
         "bootstrap": "^4.2.1",
         "cross-env": "^5.2.0",
         "cross-env": "^5.2.0",
         "jquery": "^3.2",
         "jquery": "^3.2",
-        "laravel-mix": "^2.1.14",
         "lodash": "^4.17.11",
         "lodash": "^4.17.11",
         "popper.js": "^1.14.6",
         "popper.js": "^1.14.6",
+        "resolve-url-loader": "^2.3.1",
+        "sass": "^1.15.2",
+        "sass-loader": "^7.1.0",
         "vue": "^2.5.21",
         "vue": "^2.5.21",
         "vue-template-compiler": "^2.5.21"
         "vue-template-compiler": "^2.5.21"
     },
     },
@@ -27,9 +29,12 @@
         "filesize": "^3.6.1",
         "filesize": "^3.6.1",
         "infinite-scroll": "^3.0.4",
         "infinite-scroll": "^3.0.4",
         "laravel-echo": "^1.5.2",
         "laravel-echo": "^1.5.2",
+        "laravel-mix": "^4.0.12",
+        "node-sass": "^4.11.0",
         "opencollective": "^1.0.3",
         "opencollective": "^1.0.3",
         "opencollective-postinstall": "^2.0.1",
         "opencollective-postinstall": "^2.0.1",
         "plyr": "^3.4.7",
         "plyr": "^3.4.7",
+        "promise-polyfill": "8.1.0",
         "pusher-js": "^4.2.2",
         "pusher-js": "^4.2.2",
         "readmore-js": "^2.2.1",
         "readmore-js": "^2.2.1",
         "socket.io-client": "^2.2.0",
         "socket.io-client": "^2.2.0",

+ 2 - 2
resources/assets/js/components/PostComments.vue

@@ -1,4 +1,4 @@
-<style>
+<style scoped>
  span {
  span {
   font-size: 14px;
   font-size: 14px;
  }
  }
@@ -92,7 +92,7 @@ export default {
           axios.get(url)
           axios.get(url)
             .then(response => {
             .then(response => {
                 let self = this;
                 let self = this;
-                this.results = response.data.data;
+                this.results = _.reverse(response.data.data);
                 this.pagination = response.data.meta.pagination;
                 this.pagination = response.data.meta.pagination;
                 if(this.results.length > 0) {
                 if(this.results.length > 0) {
                   $('.load-more-link').removeClass('d-none');
                   $('.load-more-link').removeClass('d-none');

+ 1 - 1
resources/assets/js/components/PostComponent.vue

@@ -1,4 +1,4 @@
-<style>
+<style scoped>
 #l-modal .modal-body,
 #l-modal .modal-body,
 #s-modal .modal-body {
 #s-modal .modal-body {
   max-height: 70vh;
   max-height: 70vh;

+ 32 - 18
resources/assets/js/components/Timeline.vue

@@ -2,11 +2,6 @@
 <div class="container" style="">
 <div class="container" style="">
 	<div class="row">
 	<div class="row">
 		<div class="col-md-8 col-lg-8 pt-2 px-0 my-3 timeline order-2 order-md-1">
 		<div class="col-md-8 col-lg-8 pt-2 px-0 my-3 timeline order-2 order-md-1">
-			<div class="loader text-center">
-				<div class="spinner-border" role="status">
-				  <span class="sr-only">Loading...</span>
-				</div>
-			</div>
 			<div class="card mb-4 status-card card-md-rounded-0" :data-status-id="status.id" v-for="(status, index) in feed" :key="status.id">
 			<div class="card mb-4 status-card card-md-rounded-0" :data-status-id="status.id" v-for="(status, index) in feed" :key="status.id">
 
 
 				<div class="card-header d-inline-flex align-items-center bg-white">
 				<div class="card-header d-inline-flex align-items-center bg-white">
@@ -97,6 +92,10 @@
 					</form>
 					</form>
 				</div>
 				</div>
 			</div>
 			</div>
+			<infinite-loading @infinite="infiniteTimeline">
+				<div slot="no-more" class="font-weight-bold text-light">No more posts to load</div>
+				<div slot="no-results" class="font-weight-bold text-light">No posts found</div>
+			</infinite-loading>
 		</div>
 		</div>
 
 
 		<div class="col-md-4 col-lg-4 pt-2 my-3 order-1 order-md-2">
 		<div class="col-md-4 col-lg-4 pt-2 my-3 order-1 order-md-2">
@@ -185,7 +184,7 @@
 				<div class="container pb-5">
 				<div class="container pb-5">
 					<p class="mb-0 text-uppercase font-weight-bold text-muted small">
 					<p class="mb-0 text-uppercase font-weight-bold text-muted small">
 						<a href="/site/about" class="text-dark pr-2">About Us</a>
 						<a href="/site/about" class="text-dark pr-2">About Us</a>
-						<a href="/site/help" class="text-dark pr-2">Support</a>
+						<a href="/site/help" class="text-dark pr-2">Help</a>
 						<a href="/site/open-source" class="text-dark pr-2">Open Source</a>
 						<a href="/site/open-source" class="text-dark pr-2">Open Source</a>
 						<a href="/site/language" class="text-dark pr-2">Language</a>
 						<a href="/site/language" class="text-dark pr-2">Language</a>
 						<a href="/site/terms" class="text-dark pr-2">Terms</a>
 						<a href="/site/terms" class="text-dark pr-2">Terms</a>
@@ -202,7 +201,7 @@
 </div>
 </div>
 </template>
 </template>
 
 
-<style type="text/css">
+<style type="text/css" scoped>
 	.postPresenterContainer {
 	.postPresenterContainer {
 		display: flex;
 		display: flex;
 		align-items: center;
 		align-items: center;
@@ -241,7 +240,6 @@
 		},
 		},
 
 
 		updated() {
 		updated() {
-			this.scroll();
 		},
 		},
 
 
 		methods: {
 		methods: {
@@ -279,6 +277,32 @@
 				});
 				});
 			},
 			},
 
 
+			infiniteTimeline($state) {
+				let homeTimeline = '/api/v1/timelines/home';
+				let localTimeline = '/api/v1/timelines/public';
+				let apiUrl = this.scope == '/' ? homeTimeline : localTimeline;
+				axios.get(apiUrl, {
+					params: {
+						page: this.page,
+					},
+				}).then(res => {
+					if (res.data.length) {
+						$('.timeline .loader').addClass('d-none');
+						let data = res.data;
+						this.feed.push(...data);
+						let ids = data.map(status => status.id);
+						this.min_id = Math.min(...ids);
+						if(this.page == 1) {
+							this.max_id = Math.max(...ids);
+						}
+						this.page += 1;
+						$state.loaded();
+					} else {
+						$state.complete();
+					}
+				});
+			},
+
 			fetchNotifications() {
 			fetchNotifications() {
 				axios.get('/api/v1/notifications')
 				axios.get('/api/v1/notifications')
 				.then(res => {
 				.then(res => {
@@ -288,16 +312,6 @@
 				});
 				});
 			},
 			},
 
 
-			scroll() {
-				window.onscroll = () => {
-				  let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight == document.documentElement.offsetHeight;
-
-				  if (bottomOfWindow) {
-				  	this.fetchTimelineApi();
-				  }
-				};
-			},
-
 			reportUrl(status) {
 			reportUrl(status) {
 				let type = status.in_reply_to ? 'comment' : 'post';
 				let type = status.in_reply_to ? 'comment' : 'post';
 				let id = status.id;
 				let id = status.id;

+ 11 - 3
resources/views/site/help/your-profile.blade.php

@@ -140,7 +140,7 @@
       </div>
       </div>
     </div>
     </div>
   </p>
   </p>
-  {{-- <hr>
+  <hr>
   <p class="h5 text-muted font-weight-light" id="delete-your-account">Delete Your Account</p>
   <p class="h5 text-muted font-weight-light" id="delete-your-account">Delete Your Account</p>
   <p> 
   <p> 
     <a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse1" role="button" aria-expanded="false" aria-controls="del-collapse1">
     <a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse1" role="button" aria-expanded="false" aria-controls="del-collapse1">
@@ -159,6 +159,7 @@
       </div>
       </div>
     </div>
     </div>
   </p>
   </p>
+  @if(config('pixelfed.account_deletion'))
   <p> 
   <p> 
     <a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse2" role="button" aria-expanded="false" aria-controls="del-collapse2">
     <a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse2" role="button" aria-expanded="false" aria-controls="del-collapse2">
       <i class="fas fa-chevron-down mr-2"></i>
       <i class="fas fa-chevron-down mr-2"></i>
@@ -166,9 +167,15 @@
     </a>
     </a>
     <div class="collapse" id="del-collapse2">
     <div class="collapse" id="del-collapse2">
       <div>
       <div>
+        @if(config('pixelfed.account_delete_after') == false)
+        <div class="bg-light p-3 mb-4">
+          <p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be <b>permanently removed</b>. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
+        </div>
+        @else
         <div class="bg-light p-3 mb-4">
         <div class="bg-light p-3 mb-4">
-          <p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be permanently removed. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
+          <p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be <b>permanently removed</b> after {{config('pixelfed.account_delete_after')}} days. You can log in during that period to prevent your account from permanent deletion. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
         </div>
         </div>
+        @endif
         <p>After you delete your account, you can't sign up again with the same username on this instance or add that username to another account on this instance, and we can't reactivate deleted accounts.</p>
         <p>After you delete your account, you can't sign up again with the same username on this instance or add that username to another account on this instance, and we can't reactivate deleted accounts.</p>
         <p>To permanently delete your account:</p>
         <p>To permanently delete your account:</p>
         <ol class="font-weight-light">
         <ol class="font-weight-light">
@@ -178,5 +185,6 @@
         </ol>
         </ol>
       </div>
       </div>
     </div>
     </div>
-  </p> --}}
+  </p>
+  @endif
 @endsection
 @endsection

+ 168 - 0
tests/Unit/ActivityPub/Verb/AnnounceTest.php

@@ -0,0 +1,168 @@
+<?php
+
+namespace Tests\Unit\ActivityPub\Verb;
+
+use Tests\TestCase;
+use Illuminate\Foundation\Testing\WithFaker;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use App\Util\ActivityPub\Validator\Announce;
+
+class AnnounceTest extends TestCase
+{
+
+	public function setUp()
+	{
+		parent::setUp();
+
+		$this->validAnnounce = [
+			"@context" => "https://www.w3.org/ns/activitystreams",
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"actor" => "https://example.org/users/alice",
+			"published" => "2018-12-31T23:59:59Z",
+			"to" => [
+				"https://www.w3.org/ns/activitystreams#Public"
+			],
+			"cc" => [
+				"https://example.org/users/bob",
+				"https://example.org/users/alice/followers"
+			],
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->invalidAnnounce = [
+			"@context" => "https://www.w3.org/ns/activitystreams",
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce2",
+			"actor" => "https://example.org/users/alice",
+			"published" => "2018-12-31T23:59:59Z",
+			"to" => [
+				"https://www.w3.org/ns/activitystreams#Public"
+			],
+			"cc" => [
+				"https://example.org/users/bob",
+				"https://example.org/users/alice/followers"
+			],
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->invalidDate = [
+			"@context" => "https://www.w3.org/ns/activitystreams",
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"actor" => "https://example.org/users/alice",
+			"published" => "2018-12-31T23:59:59ZEZE",
+			"to" => [
+				"https://www.w3.org/ns/activitystreams#Public"
+			],
+			"cc" => [
+				"https://example.org/users/bob",
+				"https://example.org/users/alice/followers"
+			],
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->contextMissing = [
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"actor" => "https://example.org/users/alice",
+			"published" => "2018-12-31T23:59:59Z",
+			"to" => [
+				"https://www.w3.org/ns/activitystreams#Public"
+			],
+			"cc" => [
+				"https://example.org/users/bob",
+				"https://example.org/users/alice/followers"
+			],
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->audienceMissing = [
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"actor" => "https://example.org/users/alice",
+			"published" => "2018-12-31T23:59:59Z",
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->audienceMissing2 = [
+			"@context" => "https://www.w3.org/ns/activitystreams",
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"actor" => "https://example.org/users/alice",
+			"published" => "2018-12-31T23:59:59Z",
+			"to" => null,
+			"cc" => null,
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->invalidActor = [
+			"@context" => "https://www.w3.org/ns/activitystreams",
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"actor" => "10000",
+			"published" => "2018-12-31T23:59:59Z",
+			"to" => [
+				"https://www.w3.org/ns/activitystreams#Public"
+			],
+			"cc" => [
+				"https://example.org/users/bob",
+				"https://example.org/users/alice/followers"
+			],
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+
+		$this->invalidActor2 = [
+			"@context" => "https://www.w3.org/ns/activitystreams",
+			"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
+			"type" => "Announce",
+			"published" => "2018-12-31T23:59:59Z",
+			"to" => [
+				"https://www.w3.org/ns/activitystreams#Public"
+			],
+			"cc" => [
+				"https://example.org/users/bob",
+				"https://example.org/users/alice/followers"
+			],
+			"object" => "https://example.org/p/bob/100000000000000",
+		];
+	}
+
+	/** @test */
+	public function basic_accept()
+	{
+		$this->assertTrue(Announce::validate($this->validAnnounce));
+	}
+
+	/** @test */
+	public function invalid_accept()
+	{
+		$this->assertFalse(Announce::validate($this->invalidAnnounce));
+	}
+
+	/** @test */
+	public function invalid_date()
+	{
+		$this->assertFalse(Announce::validate($this->invalidDate));
+	}
+
+	/** @test */
+	public function context_missing()
+	{
+		$this->assertFalse(Announce::validate($this->contextMissing));
+	}
+
+	/** @test */
+	public function audience_missing()
+	{
+		$this->assertFalse(Announce::validate($this->audienceMissing));
+		$this->assertFalse(Announce::validate($this->audienceMissing2));
+	}
+
+	/** @test */
+	public function invalid_actor()
+	{
+		$this->assertFalse(Announce::validate($this->invalidActor));
+		$this->assertFalse(Announce::validate($this->invalidActor2));
+	}
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác