mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-20 03:53:17 +00:00
Merge pull request #735 from pixelfed/frontend-ui-refactor
Frontend ui refactor
This commit is contained in:
commit
1b16e5d696
30 changed files with 4095 additions and 5707 deletions
|
@ -17,4 +17,14 @@ class FollowRequest extends Model
|
|||
{
|
||||
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
app/Http/Controllers/Api/InstanceApiController.php
Normal file
66
app/Http/Controllers/Api/InstanceApiController.php
Normal file
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
|
||||
use App\Comment;
|
||||
use App\Jobs\CommentPipeline\CommentPipeline;
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Profile;
|
||||
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
|
||||
{
|
||||
|
@ -57,7 +62,19 @@ class CommentController extends Controller
|
|||
CommentPipeline::dispatch($status, $reply);
|
||||
|
||||
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 {
|
||||
$response = redirect($status->url());
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use Illuminate\Http\Request;
|
|||
use League\Fractal;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use \Zttp\Zttp;
|
||||
|
||||
class FederationController extends Controller
|
||||
{
|
||||
|
|
|
@ -149,11 +149,17 @@ class SettingsController extends Controller
|
|||
|
||||
public function removeAccountPermanent(Request $request)
|
||||
{
|
||||
if(config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
return view('settings.remove.permanent');
|
||||
}
|
||||
|
||||
public function removeAccountPermanentSubmit(Request $request)
|
||||
{
|
||||
if(config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
$user = Auth::user();
|
||||
if($user->is_admin == true) {
|
||||
return abort(400, 'You cannot delete an admin account.');
|
||||
|
|
|
@ -19,7 +19,7 @@ class StatusController extends Controller
|
|||
{
|
||||
public function show(Request $request, $username, int $id)
|
||||
{
|
||||
$user = Profile::whereUsername($username)->firstOrFail();
|
||||
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||
|
||||
if($user->status != null) {
|
||||
return ProfileController::accountCheck($user);
|
||||
|
|
57
app/Jobs/FollowPipeline/FollowActivityPubDeliver.php
Normal file
57
app/Jobs/FollowPipeline/FollowActivityPubDeliver.php
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -37,7 +37,10 @@ class NewStatusPipeline implements ShouldQueue
|
|||
$status = $this->status;
|
||||
|
||||
StatusEntityLexer::dispatch($status);
|
||||
StatusActivityPubDeliver::dispatch($status);
|
||||
|
||||
if(config('pixelfed.activitypub_enabled') == true) {
|
||||
StatusActivityPubDeliver::dispatch($status);
|
||||
}
|
||||
|
||||
// Cache::forever('post.'.$status->id, $status);
|
||||
// $redis = Redis::connection();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -24,6 +25,10 @@ class AuthServiceProvider extends ServiceProvider
|
|||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
// Passport::routes();
|
||||
|
||||
// Passport::tokensExpireIn(now()->addDays(15));
|
||||
|
||||
// Passport::refreshTokensExpireIn(now()->addDays(30));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use League\Fractal;
|
|||
|
||||
class Follow extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Follower $follower)
|
||||
public function transform($follower)
|
||||
{
|
||||
return [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
|
|
|
@ -7,27 +7,27 @@ use League\Fractal;
|
|||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ use League\Fractal;
|
|||
|
||||
class ApplicationTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'website' => null,
|
||||
];
|
||||
}
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'website' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
28
app/Transformer/Api/AttachmentTransformer.php
Normal file
28
app/Transformer/Api/AttachmentTransformer.php
Normal file
|
@ -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
app/Transformer/Api/ContextTransformer.php
Normal file
16
app/Transformer/Api/ContextTransformer.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class ContextTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'ancestors' => [],
|
||||
'descendants' => []
|
||||
];
|
||||
}
|
||||
}
|
|
@ -6,13 +6,13 @@ use League\Fractal;
|
|||
|
||||
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
app/Transformer/Api/FilterTransformer.php
Normal file
20
app/Transformer/Api/FilterTransformer.php
Normal file
|
@ -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,11 +7,11 @@ use League\Fractal;
|
|||
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,20 +10,20 @@ class MediaTransformer 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,
|
||||
'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
app/Transformer/Api/ResultsTransformer.php
Normal file
24
app/Transformer/Api/ResultsTransformer.php
Normal file
|
@ -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' => []
|
||||
];
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Util\ActivityPub;
|
||||
|
||||
use Cache, DB, Log, Redis, Validator;
|
||||
use Cache, DB, Log, Purify, Redis, Validator;
|
||||
use App\{
|
||||
Activity,
|
||||
Follower,
|
||||
|
@ -16,6 +16,10 @@ use Carbon\Carbon;
|
|||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Jobs\LikePipeline\LikePipeline;
|
||||
|
||||
use App\Util\ActivityPub\Validator\{
|
||||
Follow
|
||||
};
|
||||
|
||||
class Inbox
|
||||
{
|
||||
protected $headers;
|
||||
|
@ -35,30 +39,6 @@ class Inbox
|
|||
$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()
|
||||
{
|
||||
$verb = $this->payload['type'];
|
||||
|
@ -76,6 +56,7 @@ class Inbox
|
|||
break;
|
||||
|
||||
case 'Accept':
|
||||
if(Accept::validate($this->payload) == false) { return; }
|
||||
$this->handleAcceptActivity();
|
||||
break;
|
||||
|
||||
|
@ -171,7 +152,8 @@ class Inbox
|
|||
$caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length'));
|
||||
$status = new Status;
|
||||
$status->profile_id = $actor->id;
|
||||
$status->caption = $caption;
|
||||
$status->caption = strip_tags($caption);
|
||||
$status->rendered = Purify::clean($caption);
|
||||
$status->visibility = $status->scope = 'public';
|
||||
$status->uri = $url;
|
||||
$status->url = $url;
|
||||
|
@ -275,13 +257,10 @@ class Inbox
|
|||
$obj = $this->payload['object'];
|
||||
if(is_string($obj) && Helpers::validateUrl($obj)) {
|
||||
// actor object detected
|
||||
|
||||
// todo delete actor
|
||||
} else if (is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
|
||||
// tombstone detected
|
||||
$status = Status::whereUri($obj['id'])->first();
|
||||
if($status == null) {
|
||||
return;
|
||||
}
|
||||
$status = Status::whereUri($obj['id'])->firstOrFail();
|
||||
$status->forceDelete();
|
||||
}
|
||||
}
|
||||
|
|
28
app/Util/ActivityPub/Validator/Announce.php
Normal file
28
app/Util/ActivityPub/Validator/Announce.php
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -177,6 +177,28 @@ return [
|
|||
*/
|
||||
'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'),
|
||||
'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
|
||||
'ap_inbox' => env('ACTIVITYPUB_INBOX', false),
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
9047
package-lock.json
generated
9047
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -15,9 +15,11 @@
|
|||
"bootstrap": "^4.2.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"jquery": "^3.2",
|
||||
"laravel-mix": "^2.1.14",
|
||||
"lodash": "^4.17.11",
|
||||
"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-template-compiler": "^2.5.21"
|
||||
},
|
||||
|
@ -27,9 +29,12 @@
|
|||
"filesize": "^3.6.1",
|
||||
"infinite-scroll": "^3.0.4",
|
||||
"laravel-echo": "^1.5.2",
|
||||
"laravel-mix": "^4.0.12",
|
||||
"node-sass": "^4.11.0",
|
||||
"opencollective": "^1.0.3",
|
||||
"opencollective-postinstall": "^2.0.1",
|
||||
"plyr": "^3.4.7",
|
||||
"promise-polyfill": "8.1.0",
|
||||
"pusher-js": "^4.2.2",
|
||||
"readmore-js": "^2.2.1",
|
||||
"socket.io-client": "^2.2.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<style>
|
||||
<style scoped>
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ export default {
|
|||
axios.get(url)
|
||||
.then(response => {
|
||||
let self = this;
|
||||
this.results = response.data.data;
|
||||
this.results = _.reverse(response.data.data);
|
||||
this.pagination = response.data.meta.pagination;
|
||||
if(this.results.length > 0) {
|
||||
$('.load-more-link').removeClass('d-none');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<style>
|
||||
<style scoped>
|
||||
#l-modal .modal-body,
|
||||
#s-modal .modal-body {
|
||||
max-height: 70vh;
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
<div class="container" style="">
|
||||
<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="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-header d-inline-flex align-items-center bg-white">
|
||||
|
@ -97,6 +92,10 @@
|
|||
</form>
|
||||
</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 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">
|
||||
<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/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/language" class="text-dark pr-2">Language</a>
|
||||
<a href="/site/terms" class="text-dark pr-2">Terms</a>
|
||||
|
@ -202,7 +201,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style type="text/css">
|
||||
<style type="text/css" scoped>
|
||||
.postPresenterContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -241,7 +240,6 @@
|
|||
},
|
||||
|
||||
updated() {
|
||||
this.scroll();
|
||||
},
|
||||
|
||||
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() {
|
||||
axios.get('/api/v1/notifications')
|
||||
.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) {
|
||||
let type = status.in_reply_to ? 'comment' : 'post';
|
||||
let id = status.id;
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</p>
|
||||
{{-- <hr>
|
||||
<hr>
|
||||
<p class="h5 text-muted font-weight-light" id="delete-your-account">Delete Your Account</p>
|
||||
<p>
|
||||
<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>
|
||||
</p>
|
||||
@if(config('pixelfed.account_deletion'))
|
||||
<p>
|
||||
<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>
|
||||
|
@ -166,9 +167,15 @@
|
|||
</a>
|
||||
<div class="collapse" id="del-collapse2">
|
||||
<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 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>. 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">
|
||||
<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>
|
||||
@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>To permanently delete your account:</p>
|
||||
<ol class="font-weight-light">
|
||||
|
@ -178,5 +185,6 @@
|
|||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</p> --}}
|
||||
</p>
|
||||
@endif
|
||||
@endsection
|
168
tests/Unit/ActivityPub/Verb/AnnounceTest.php
Normal file
168
tests/Unit/ActivityPub/Verb/AnnounceTest.php
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue