mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-23 05:23:17 +00:00
commit
700c7805ce
41 changed files with 1052 additions and 183 deletions
64
app/Console/Commands/StoryGC.php
Normal file
64
app/Console/Commands/StoryGC.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\{
|
||||
DB,
|
||||
Storage
|
||||
};
|
||||
use App\{
|
||||
Story,
|
||||
StoryView
|
||||
};
|
||||
|
||||
class StoryGC extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'story:gc';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clear expired Stories';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$stories = Story::where('expires_at', '<', now())->take(50)->get();
|
||||
|
||||
if($stories->count() == 0) {
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach($stories as $story) {
|
||||
if(Storage::exists($story->path) == true) {
|
||||
Storage::delete($story->path);
|
||||
}
|
||||
DB::transaction(function() use($story) {
|
||||
StoryView::whereStoryId($story->id)->delete();
|
||||
$story->delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ class Kernel extends ConsoleKernel
|
|||
$schedule->command('media:gc')
|
||||
->hourly();
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('story:gc')->everyFiveMinutes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -111,6 +111,10 @@ class FollowerController extends Controller
|
|||
Cache::forget('api:local:exp:rec:'.$user->id);
|
||||
Cache::forget('user:account:id:'.$target->user_id);
|
||||
Cache::forget('user:account:id:'.$user->user_id);
|
||||
Cache::forget('px:profile:followers-v1.3:'.$user->id);
|
||||
Cache::forget('px:profile:followers-v1.3:'.$target->id);
|
||||
Cache::forget('px:profile:following-v1.3:'.$user->id);
|
||||
Cache::forget('px:profile:following-v1.3:'.$target->id);
|
||||
|
||||
return $target->url();
|
||||
}
|
||||
|
|
|
@ -244,7 +244,7 @@ class InternalApiController extends Controller
|
|||
'cw' => 'nullable|boolean',
|
||||
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
|
||||
'place' => 'nullable',
|
||||
'comments_disabled' => 'nullable|boolean'
|
||||
'comments_disabled' => 'nullable'
|
||||
]);
|
||||
|
||||
if(config('costar.enabled') == true) {
|
||||
|
@ -301,7 +301,7 @@ class InternalApiController extends Controller
|
|||
}
|
||||
|
||||
if($request->filled('comments_disabled')) {
|
||||
$status->comments_disabled = $request->input('comments_disabled');
|
||||
$status->comments_disabled = (bool) $request->input('comments_disabled');
|
||||
}
|
||||
|
||||
$status->caption = strip_tags($request->caption);
|
||||
|
@ -314,10 +314,6 @@ class InternalApiController extends Controller
|
|||
$media->save();
|
||||
}
|
||||
|
||||
// $resource = new Fractal\Resource\Collection($status->media()->orderBy('order')->get(), new StatusMediaContainerTransformer());
|
||||
// $mediaContainer = $this->fractal->createData($resource)->toArray();
|
||||
// $status->media_container = json_encode($mediaContainer);
|
||||
|
||||
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
||||
$cw = $profile->cw == true ? true : $cw;
|
||||
$status->is_nsfw = $cw;
|
||||
|
|
|
@ -9,6 +9,7 @@ use View;
|
|||
use App\Follower;
|
||||
use App\FollowRequest;
|
||||
use App\Profile;
|
||||
use App\Story;
|
||||
use App\User;
|
||||
use App\UserFilter;
|
||||
use League\Fractal;
|
||||
|
@ -135,6 +136,21 @@ class ProfileController extends Controller
|
|||
return false;
|
||||
}
|
||||
|
||||
public static function accountCheck(Profile $profile)
|
||||
{
|
||||
switch ($profile->status) {
|
||||
case 'disabled':
|
||||
case 'suspended':
|
||||
case 'delete':
|
||||
return view('profile.disabled');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
protected function blockedProfileCheck(Profile $profile)
|
||||
{
|
||||
$pid = Auth::user()->profile->id;
|
||||
|
@ -215,4 +231,18 @@ class ProfileController extends Controller
|
|||
|
||||
return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||
}
|
||||
|
||||
public function stories(Request $request, $username)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||
$pid = $profile->id;
|
||||
$authed = Auth::user()->profile;
|
||||
abort_if($pid != $authed->id && $profile->followedBy($authed) == false, 404);
|
||||
$exists = Story::whereProfileId($pid)
|
||||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
abort_unless($exists > 0, 404);
|
||||
return view('profile.story', compact('pid'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,15 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Story;
|
||||
use App\StoryView;
|
||||
use App\Services\StoryService;
|
||||
use Cache, Storage;
|
||||
use App\Services\FollowerService;
|
||||
|
||||
|
||||
class StoryController extends Controller
|
||||
{
|
||||
|
@ -12,8 +21,235 @@ class StoryController extends Controller
|
|||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function home(Request $request)
|
||||
public function apiV1Add(Request $request)
|
||||
{
|
||||
return view('stories.home');
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'file.*' => function() {
|
||||
return [
|
||||
'required',
|
||||
'mimes:image/jpeg,image/png',
|
||||
'max:' . config('pixelfed.max_photo_size'),
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if(Story::whereProfileId($user->profile_id)->where('expires_at', '>', now())->count() >= Story::MAX_PER_DAY) {
|
||||
abort(400, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
$story = new Story();
|
||||
$story->profile_id = $user->profile_id;
|
||||
$story->save();
|
||||
|
||||
$monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12);
|
||||
$rid = Str::random(6).'.'.Str::random(9);
|
||||
|
||||
$photo = $request->file('file');
|
||||
|
||||
$mimes = explode(',', config('pixelfed.media_types'));
|
||||
if(in_array($photo->getMimeType(), [
|
||||
'image/jpeg',
|
||||
'image/png'
|
||||
]) == false) {
|
||||
abort(400, 'Invalid media type');
|
||||
return;
|
||||
}
|
||||
|
||||
$storagePath = "public/_esm.t1/{$monthHash}/{$story->id}/{$rid}";
|
||||
$path = $photo->store($storagePath);
|
||||
|
||||
$story->path = $path;
|
||||
$story->local = true;
|
||||
$story->expires_at = now()->addHours(24);
|
||||
$story->save();
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully added',
|
||||
'media_url' => url(Storage::url($story->path))
|
||||
];
|
||||
}
|
||||
|
||||
public function apiV1Delete(Request $request, $id)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
$story = Story::whereProfileId($user->profile_id)
|
||||
->findOrFail($id);
|
||||
|
||||
if(Storage::exists($story->path) == true) {
|
||||
Storage::delete($story->path);
|
||||
}
|
||||
|
||||
$story->delete();
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully deleted'
|
||||
];
|
||||
}
|
||||
|
||||
public function apiV1Recent(Request $request)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$profile = $request->user()->profile;
|
||||
$following = FollowerService::build()->profile($profile)->following();
|
||||
|
||||
$stories = Story::with('profile')
|
||||
->whereIn('profile_id', $following)
|
||||
->groupBy('profile_id')
|
||||
->where('expires_at', '>', now())
|
||||
->orderByDesc('expires_at')
|
||||
->take(9)
|
||||
->get()
|
||||
->map(function($s, $k) {
|
||||
return [
|
||||
'id' => (string) $s->id,
|
||||
'photo' => $s->profile->avatarUrl(),
|
||||
'name' => $s->profile->username,
|
||||
'link' => $s->profile->url(),
|
||||
'lastUpdated' => (int) $s->created_at->format('U'),
|
||||
'seen' => $s->seen(),
|
||||
'items' => [],
|
||||
'pid' => (string) $s->profile->id
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function apiV1Fetch(Request $request, $id)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$profile = $request->user()->profile;
|
||||
if($id == $profile->id) {
|
||||
$publicOnly = true;
|
||||
} else {
|
||||
$following = FollowerService::build()->profile($profile)->following();
|
||||
$publicOnly = in_array($id, $following);
|
||||
}
|
||||
|
||||
$stories = Story::whereProfileId($id)
|
||||
->orderBy('expires_at', 'desc')
|
||||
->where('expires_at', '>', now())
|
||||
->when(!$publicOnly, function($query, $publicOnly) {
|
||||
return $query->wherePublic(true);
|
||||
})
|
||||
->get()
|
||||
->map(function($s, $k) {
|
||||
return [
|
||||
'id' => (string) $s->id,
|
||||
'type' => 'photo',
|
||||
'length' => 3,
|
||||
'src' => url(Storage::url($s->path)),
|
||||
'preview' => null,
|
||||
'link' => null,
|
||||
'linkText' => null,
|
||||
'time' => $s->created_at->format('U'),
|
||||
'expires_at' => (int) $s->expires_at->format('U'),
|
||||
'seen' => $s->seen()
|
||||
];
|
||||
})->toArray();
|
||||
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function apiV1Profile(Request $request, $id)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$authed = $request->user()->profile;
|
||||
$profile = Profile::findOrFail($id);
|
||||
if($id == $authed->id) {
|
||||
$publicOnly = true;
|
||||
} else {
|
||||
$following = FollowerService::build()->profile($authed)->following();
|
||||
$publicOnly = in_array($id, $following);
|
||||
}
|
||||
|
||||
$stories = Story::whereProfileId($profile->id)
|
||||
->orderBy('expires_at')
|
||||
->where('expires_at', '>', now())
|
||||
->when(!$publicOnly, function($query, $publicOnly) {
|
||||
return $query->wherePublic(true);
|
||||
})
|
||||
->get()
|
||||
->map(function($s, $k) {
|
||||
return [
|
||||
'id' => $s->id,
|
||||
'type' => 'photo',
|
||||
'length' => 3,
|
||||
'src' => url(Storage::url($s->path)),
|
||||
'preview' => null,
|
||||
'link' => null,
|
||||
'linkText' => null,
|
||||
'time' => $s->created_at->format('U'),
|
||||
'expires_at' => (int) $s->expires_at->format('U'),
|
||||
'seen' => $s->seen()
|
||||
];
|
||||
})->toArray();
|
||||
if(count($stories) == 0) {
|
||||
return [];
|
||||
}
|
||||
$cursor = count($stories) - 1;
|
||||
$stories = [[
|
||||
'id' => (string) $stories[$cursor]['id'],
|
||||
'photo' => $profile->avatarUrl(),
|
||||
'name' => $profile->username,
|
||||
'link' => $profile->url(),
|
||||
'lastUpdated' => (int) now()->format('U'),
|
||||
'seen' => null,
|
||||
'items' => $stories,
|
||||
'pid' => (string) $profile->id
|
||||
]];
|
||||
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function apiV1Viewed(Request $request)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'id' => 'required|integer|min:1|exists:stories',
|
||||
]);
|
||||
|
||||
StoryView::firstOrCreate([
|
||||
'story_id' => $request->input('id'),
|
||||
'profile_id' => $request->user()->profile_id
|
||||
]);
|
||||
|
||||
return ['code' => 200];
|
||||
}
|
||||
|
||||
public function compose(Request $request)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
|
||||
return view('stories.compose');
|
||||
}
|
||||
|
||||
public function apiV1Exists(Request $request, $id)
|
||||
{
|
||||
abort_if(!config('instance.stories.enabled'), 404);
|
||||
|
||||
$res = (bool) Story::whereProfileId($id)
|
||||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function iRedirect(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
abort_if(!$user, 404);
|
||||
$username = $user->username;
|
||||
return redirect("/stories/{$username}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -303,4 +303,9 @@ class Profile extends Model
|
|||
->whereFollowingId($this->id)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function stories()
|
||||
{
|
||||
return $this->hasMany(Story::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ class AuthServiceProvider extends ServiceProvider
|
|||
'read',
|
||||
'write',
|
||||
'follow',
|
||||
'push'
|
||||
]);
|
||||
|
||||
Passport::tokensCan([
|
||||
|
|
|
@ -131,13 +131,9 @@ class Status extends Model
|
|||
$media = $this->firstMedia();
|
||||
$path = $media->media_path;
|
||||
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
|
||||
if(config('pixelfed.cloud_storage') == true) {
|
||||
$url = Storage::disk(config('filesystems.cloud'))->url($path)."?v={$hash}";
|
||||
} else {
|
||||
$url = Storage::url($path)."?v={$hash}";
|
||||
}
|
||||
$url = $media->cdn_url ? $media->cdn_url . "?v={$hash}" : url(Storage::url($path)."?v={$hash}");
|
||||
|
||||
return url($url);
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function likes()
|
||||
|
|
|
@ -10,6 +10,8 @@ class Story extends Model
|
|||
{
|
||||
use HasSnowflakePrimary;
|
||||
|
||||
public const MAX_PER_DAY = 10;
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
|
@ -24,6 +26,8 @@ class Story extends Model
|
|||
*/
|
||||
protected $dates = ['published_at', 'expires_at'];
|
||||
|
||||
protected $fillable = ['profile_id'];
|
||||
|
||||
protected $visible = ['id'];
|
||||
|
||||
public function profile()
|
||||
|
@ -31,16 +35,6 @@ class Story extends Model
|
|||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(StoryItem::class);
|
||||
}
|
||||
|
||||
public function reactions()
|
||||
{
|
||||
return $this->hasMany(StoryReaction::class);
|
||||
}
|
||||
|
||||
public function views()
|
||||
{
|
||||
return $this->hasMany(StoryView::class);
|
||||
|
@ -48,7 +42,13 @@ class Story extends Model
|
|||
|
||||
public function seen($pid = false)
|
||||
{
|
||||
$id = $pid ?? Auth::user()->profile->id;
|
||||
return $this->views()->whereProfileId($id)->exists();
|
||||
return StoryView::whereStoryId($this->id)
|
||||
->whereProfileId(Auth::user()->profile->id)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function permalink()
|
||||
{
|
||||
return url("/story/$this->id");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
|
||||
public function includeMediaAttachments(Status $status)
|
||||
{
|
||||
return Cache::remember('status:transformer:media:attachments:'.$status->id, now()->addDays(14), function() use($status) {
|
||||
return Cache::remember('status:transformer:media:attachments:'.$status->id, now()->addMinutes(14), function() use($status) {
|
||||
if(in_array($status->type, ['photo', 'video', 'video:album', 'photo:album', 'loop', 'photo:video:album'])) {
|
||||
$media = $status->media()->orderBy('order')->get();
|
||||
return $this->collection($media, new MediaTransformer());
|
||||
|
|
|
@ -406,7 +406,6 @@ class Helpers {
|
|||
$remoteUsername = "@{$username}@{$domain}";
|
||||
|
||||
abort_if(!self::validateUrl($res['inbox']), 400);
|
||||
abort_if(!self::validateUrl($res['outbox']), 400);
|
||||
abort_if(!self::validateUrl($res['id']), 400);
|
||||
|
||||
$profile = Profile::whereRemoteUrl($res['id'])->first();
|
||||
|
@ -451,4 +450,20 @@ class Helpers {
|
|||
$response = curl_exec($ch);
|
||||
return;
|
||||
}
|
||||
|
||||
public static function apSignedPostRequest($senderProfile, $url, $body)
|
||||
{
|
||||
abort_if(!self::validateUrl($url), 400);
|
||||
|
||||
$payload = json_encode($body);
|
||||
$headers = HttpSignature::sign($senderProfile, $url, $body);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
$response = curl_exec($ch);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ class RestrictedNames
|
|||
'download',
|
||||
'domainadmin',
|
||||
'domainadministrator',
|
||||
'email',
|
||||
'errors',
|
||||
'events',
|
||||
'example',
|
||||
|
@ -26,7 +25,7 @@ class RestrictedNames
|
|||
'hostmaster',
|
||||
'imap',
|
||||
'info',
|
||||
'info',
|
||||
'information',
|
||||
'is',
|
||||
'isatap',
|
||||
'it',
|
||||
|
@ -142,6 +141,8 @@ class RestrictedNames
|
|||
'drives',
|
||||
'driver',
|
||||
'e',
|
||||
'email',
|
||||
'emails',
|
||||
'error',
|
||||
'explore',
|
||||
'export',
|
||||
|
@ -206,6 +207,10 @@ class RestrictedNames
|
|||
'news',
|
||||
'news',
|
||||
'newsfeed',
|
||||
'newsroom',
|
||||
'newsrooms',
|
||||
'news-room',
|
||||
'news-rooms',
|
||||
'o',
|
||||
'oauth',
|
||||
'official',
|
||||
|
|
|
@ -6,7 +6,7 @@ trait User {
|
|||
|
||||
public function isTrustedAccount()
|
||||
{
|
||||
return $this->created_at->lt(now()->subDays(20));
|
||||
return $this->created_at->lt(now()->subDays(60));
|
||||
}
|
||||
|
||||
public function getMaxPostsPerHourAttribute()
|
||||
|
@ -98,4 +98,19 @@ trait User {
|
|||
{
|
||||
return 5000;
|
||||
}
|
||||
|
||||
public function getMaxStoriesPerHourAttribute()
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
public function getMaxStoriesPerDayAttribute()
|
||||
{
|
||||
return 30;
|
||||
}
|
||||
|
||||
public function getMaxStoryDeletePerDayAttribute()
|
||||
{
|
||||
return 35;
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ class Config {
|
|||
'features' => [
|
||||
'mobile_apis' => config('pixelfed.oauth_enabled'),
|
||||
'circles' => false,
|
||||
'stories' => false,
|
||||
'stories' => config('instance.stories.enabled'),
|
||||
'video' => Str::contains(config('pixelfed.media_types'), 'video/mp4'),
|
||||
'import' => [
|
||||
'instagram' => config('pixelfed.import.instagram.enabled'),
|
||||
|
|
148
composer.lock
generated
148
composer.lock
generated
|
@ -60,16 +60,16 @@
|
|||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.125.0",
|
||||
"version": "3.128.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "d9ffe7cf9cc93d3c49f4f6d2db6cf0c469686f9c"
|
||||
"reference": "a81485e12b2545aff17134bbf29442037f3fcadb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d9ffe7cf9cc93d3c49f4f6d2db6cf0c469686f9c",
|
||||
"reference": "d9ffe7cf9cc93d3c49f4f6d2db6cf0c469686f9c",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a81485e12b2545aff17134bbf29442037f3fcadb",
|
||||
"reference": "a81485e12b2545aff17134bbf29442037f3fcadb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -94,7 +94,8 @@
|
|||
"nette/neon": "^2.3",
|
||||
"phpunit/phpunit": "^4.8.35|^5.4.3",
|
||||
"psr/cache": "^1.0",
|
||||
"psr/simple-cache": "^1.0"
|
||||
"psr/simple-cache": "^1.0",
|
||||
"sebastian/comparator": "^1.2.3"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
|
||||
|
@ -139,7 +140,7 @@
|
|||
"s3",
|
||||
"sdk"
|
||||
],
|
||||
"time": "2019-12-02T23:15:42+00:00"
|
||||
"time": "2019-12-10T19:12:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "barryvdh/laravel-cors",
|
||||
|
@ -448,25 +449,25 @@
|
|||
},
|
||||
{
|
||||
"name": "dnoegel/php-xdg-base-dir",
|
||||
"version": "0.1",
|
||||
"version": "v0.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dnoegel/php-xdg-base-dir.git",
|
||||
"reference": "265b8593498b997dc2d31e75b89f053b5cc9621a"
|
||||
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a",
|
||||
"reference": "265b8593498b997dc2d31e75b89f053b5cc9621a",
|
||||
"url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
|
||||
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "@stable"
|
||||
"phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
|
||||
},
|
||||
"type": "project",
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"XdgBaseDir\\": "src/"
|
||||
|
@ -477,7 +478,7 @@
|
|||
"MIT"
|
||||
],
|
||||
"description": "implementation of xdg base directory specification for php",
|
||||
"time": "2014-10-24T07:27:01+00:00"
|
||||
"time": "2019-12-04T15:06:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
|
@ -1246,16 +1247,16 @@
|
|||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.4.1",
|
||||
"version": "6.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11"
|
||||
"reference": "dbc2bc3a293ed6b1ae08a3651e2bfd213d19b6a5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/dbc2bc3a293ed6b1ae08a3651e2bfd213d19b6a5",
|
||||
"reference": "dbc2bc3a293ed6b1ae08a3651e2bfd213d19b6a5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1270,12 +1271,13 @@
|
|||
"psr/log": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.3-dev"
|
||||
"dev-master": "6.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -1308,7 +1310,7 @@
|
|||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"time": "2019-10-23T15:58:00+00:00"
|
||||
"time": "2019-12-07T18:20:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
|
@ -1592,16 +1594,16 @@
|
|||
},
|
||||
{
|
||||
"name": "jaybizzle/crawler-detect",
|
||||
"version": "v1.2.89",
|
||||
"version": "v1.2.90",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
|
||||
"reference": "374d699ce4944107015eee0798eab072e3c47df9"
|
||||
"reference": "35f963386e6a189697fe4b14dc91fb42b17fda4b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/374d699ce4944107015eee0798eab072e3c47df9",
|
||||
"reference": "374d699ce4944107015eee0798eab072e3c47df9",
|
||||
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/35f963386e6a189697fe4b14dc91fb42b17fda4b",
|
||||
"reference": "35f963386e6a189697fe4b14dc91fb42b17fda4b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1637,7 +1639,7 @@
|
|||
"crawlerdetect",
|
||||
"php crawler detect"
|
||||
],
|
||||
"time": "2019-11-16T13:47:52+00:00"
|
||||
"time": "2019-12-08T20:03:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jenssegers/agent",
|
||||
|
@ -1710,16 +1712,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v6.6.0",
|
||||
"version": "v6.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "b48528ba5422ac909dbabf0b1cc34534928e7bce"
|
||||
"reference": "ba4204f3a8b9672b6116398c165bd9c0c6eac077"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/b48528ba5422ac909dbabf0b1cc34534928e7bce",
|
||||
"reference": "b48528ba5422ac909dbabf0b1cc34534928e7bce",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/ba4204f3a8b9672b6116398c165bd9c0c6eac077",
|
||||
"reference": "ba4204f3a8b9672b6116398c165bd9c0c6eac077",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1815,7 +1817,7 @@
|
|||
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).",
|
||||
"moontoast/math": "Required to use ordered UUIDs (^1.1).",
|
||||
"pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
|
||||
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0)",
|
||||
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).",
|
||||
"symfony/cache": "Required to PSR-6 cache bridge (^4.3.4).",
|
||||
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.2).",
|
||||
|
@ -1852,7 +1854,7 @@
|
|||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2019-11-26T15:33:08+00:00"
|
||||
"time": "2019-12-10T16:01:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/helpers",
|
||||
|
@ -1909,16 +1911,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/horizon",
|
||||
"version": "v3.4.3",
|
||||
"version": "v3.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/horizon.git",
|
||||
"reference": "37226dd66318014fac20351b4cc7ca209dd4ccb6"
|
||||
"reference": "7c36d24b200b60a059ab20f5b53f5bb6f4d2da40"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/horizon/zipball/37226dd66318014fac20351b4cc7ca209dd4ccb6",
|
||||
"reference": "37226dd66318014fac20351b4cc7ca209dd4ccb6",
|
||||
"url": "https://api.github.com/repos/laravel/horizon/zipball/7c36d24b200b60a059ab20f5b53f5bb6f4d2da40",
|
||||
"reference": "7c36d24b200b60a059ab20f5b53f5bb6f4d2da40",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1926,9 +1928,9 @@
|
|||
"ext-json": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-posix": "*",
|
||||
"illuminate/contracts": "~5.7.0|~5.8.0|^6.0|^7.0",
|
||||
"illuminate/queue": "~5.7.0|~5.8.0|^6.0|^7.0",
|
||||
"illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0",
|
||||
"illuminate/contracts": "~5.7.0|~5.8.0|^6.0",
|
||||
"illuminate/queue": "~5.7.0|~5.8.0|^6.0",
|
||||
"illuminate/support": "~5.7.0|~5.8.0|^6.0",
|
||||
"php": ">=7.1.0",
|
||||
"predis/predis": "^1.1",
|
||||
"ramsey/uuid": "^3.5",
|
||||
|
@ -1937,7 +1939,7 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^3.7|^4.0|^5.0",
|
||||
"orchestra/testbench": "^3.7|^4.0",
|
||||
"phpunit/phpunit": "^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
|
@ -1974,7 +1976,7 @@
|
|||
"laravel",
|
||||
"queue"
|
||||
],
|
||||
"time": "2019-11-19T16:23:21+00:00"
|
||||
"time": "2019-12-10T16:50:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/passport",
|
||||
|
@ -2217,16 +2219,16 @@
|
|||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "1.0.57",
|
||||
"version": "1.0.61",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "0e9db7f0b96b9f12dcf6f65bc34b72b1a30ea55a"
|
||||
"reference": "4fb13c01784a6c9f165a351e996871488ca2d8c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0e9db7f0b96b9f12dcf6f65bc34b72b1a30ea55a",
|
||||
"reference": "0e9db7f0b96b9f12dcf6f65bc34b72b1a30ea55a",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fb13c01784a6c9f165a351e996871488ca2d8c9",
|
||||
"reference": "4fb13c01784a6c9f165a351e996871488ca2d8c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2297,7 +2299,7 @@
|
|||
"sftp",
|
||||
"storage"
|
||||
],
|
||||
"time": "2019-10-16T21:01:05+00:00"
|
||||
"time": "2019-12-08T21:46:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-aws-s3-v3",
|
||||
|
@ -4061,20 +4063,20 @@
|
|||
},
|
||||
{
|
||||
"name": "psy/psysh",
|
||||
"version": "v0.9.11",
|
||||
"version": "v0.9.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bobthecow/psysh.git",
|
||||
"reference": "75d9ac1c16db676de27ab554a4152b594be4748e"
|
||||
"reference": "90da7f37568aee36b116a030c5f99c915267edd4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/75d9ac1c16db676de27ab554a4152b594be4748e",
|
||||
"reference": "75d9ac1c16db676de27ab554a4152b594be4748e",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/90da7f37568aee36b116a030c5f99c915267edd4",
|
||||
"reference": "90da7f37568aee36b116a030c5f99c915267edd4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dnoegel/php-xdg-base-dir": "0.1",
|
||||
"dnoegel/php-xdg-base-dir": "0.1.*",
|
||||
"ext-json": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"jakub-onderka/php-console-highlighter": "0.3.*|0.4.*",
|
||||
|
@ -4131,7 +4133,7 @@
|
|||
"interactive",
|
||||
"shell"
|
||||
],
|
||||
"time": "2019-11-27T22:44:29+00:00"
|
||||
"time": "2019-12-06T14:19:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
|
@ -6485,19 +6487,19 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
||||
"reference": "55cd3f5e892eee6f5aca414d465cc224b062bea6"
|
||||
"reference": "35638e4f5e714a12dec5ca062e68c625c1309c1c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/55cd3f5e892eee6f5aca414d465cc224b062bea6",
|
||||
"reference": "55cd3f5e892eee6f5aca414d465cc224b062bea6",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/35638e4f5e714a12dec5ca062e68c625c1309c1c",
|
||||
"reference": "35638e4f5e714a12dec5ca062e68c625c1309c1c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/routing": "^5.5|^6",
|
||||
"illuminate/session": "^5.5|^6",
|
||||
"illuminate/support": "^5.5|^6",
|
||||
"maximebf/debugbar": "~1.15.0",
|
||||
"maximebf/debugbar": "^1.15",
|
||||
"php": ">=7.0",
|
||||
"symfony/debug": "^3|^4|^5",
|
||||
"symfony/finder": "^3|^4|^5"
|
||||
|
@ -6545,7 +6547,7 @@
|
|||
"profiler",
|
||||
"webprofiler"
|
||||
],
|
||||
"time": "2019-11-24T09:49:45+00:00"
|
||||
"time": "2019-12-07T09:33:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
|
@ -7460,20 +7462,20 @@
|
|||
},
|
||||
{
|
||||
"name": "maximebf/debugbar",
|
||||
"version": "v1.15.1",
|
||||
"version": "v1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maximebf/php-debugbar.git",
|
||||
"reference": "6c4277f6117e4864966c9cb58fb835cee8c74a1e"
|
||||
"reference": "6ca3502de5e5889dc21311d2461f8cc3b6a094b1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6c4277f6117e4864966c9cb58fb835cee8c74a1e",
|
||||
"reference": "6c4277f6117e4864966c9cb58fb835cee8c74a1e",
|
||||
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6ca3502de5e5889dc21311d2461f8cc3b6a094b1",
|
||||
"reference": "6ca3502de5e5889dc21311d2461f8cc3b6a094b1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"php": "^7.1",
|
||||
"psr/log": "^1.0",
|
||||
"symfony/var-dumper": "^2.6|^3|^4"
|
||||
},
|
||||
|
@ -7488,7 +7490,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
"dev-master": "1.16-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -7517,7 +7519,7 @@
|
|||
"debug",
|
||||
"debugbar"
|
||||
],
|
||||
"time": "2019-09-24T14:55:42+00:00"
|
||||
"time": "2019-10-18T14:34:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
|
@ -8617,16 +8619,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "8.4.3",
|
||||
"version": "8.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e"
|
||||
"reference": "3ee1c1fd6fc264480c25b6fb8285edefe1702dab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67f9e35bffc0dd52d55d565ddbe4230454fd6a4e",
|
||||
"reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3ee1c1fd6fc264480c25b6fb8285edefe1702dab",
|
||||
"reference": "3ee1c1fd6fc264480c25b6fb8285edefe1702dab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -8670,7 +8672,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "8.4-dev"
|
||||
"dev-master": "8.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -8696,7 +8698,7 @@
|
|||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2019-11-06T09:42:23+00:00"
|
||||
"time": "2019-12-06T05:41:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "scrivo/highlight.php",
|
||||
|
@ -9602,16 +9604,16 @@
|
|||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.5.2",
|
||||
"version": "3.5.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7"
|
||||
"reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
|
||||
"reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/557a1fc7ac702c66b0bbfe16ab3d55839ef724cb",
|
||||
"reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -9649,7 +9651,7 @@
|
|||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"time": "2019-10-28T04:36:32+00:00"
|
||||
"time": "2019-12-04T04:46:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
|
|
|
@ -47,4 +47,8 @@ return [
|
|||
'custom' => env('USERNAME_REMOTE_CUSTOM_TEXT', null)
|
||||
]
|
||||
],
|
||||
|
||||
'stories' => [
|
||||
'enabled' => env('STORIES_ENABLED', false),
|
||||
]
|
||||
];
|
20
config/passport.php
Normal file
20
config/passport.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Keys
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Passport uses encryption keys while generating secure access tokens for
|
||||
| your application. By default, the keys are stored as local files but
|
||||
| can be set via environment variables when that is more convenient.
|
||||
|
|
||||
*/
|
||||
|
||||
'private_key' => env('PASSPORT_PRIVATE_KEY'),
|
||||
|
||||
'public_key' => env('PASSPORT_PUBLIC_KEY'),
|
||||
|
||||
];
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your Pixelfed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.10.6',
|
||||
'version' => '0.10.7',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateStoriesTable extends Migration
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||
}
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('stories');
|
||||
Schema::dropIfExists('story_items');
|
||||
Schema::dropIfExists('story_reactions');
|
||||
Schema::dropIfExists('story_views');
|
||||
|
||||
Schema::create('stories', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('profile_id')->unsigned()->index();
|
||||
$table->string('type')->nullable();
|
||||
$table->unsignedInteger('size')->nullable();
|
||||
$table->string('mime')->nullable();
|
||||
$table->smallInteger('duration')->unsigned();
|
||||
$table->string('path')->nullable();
|
||||
$table->string('cdn_url')->nullable();
|
||||
$table->boolean('public')->default(false)->index();
|
||||
$table->boolean('local')->default(false)->index();
|
||||
$table->unsignedInteger('view_count')->nullable();
|
||||
$table->unsignedInteger('comment_count')->nullable();
|
||||
$table->json('story')->nullable();
|
||||
$table->unique(['profile_id', 'path']);
|
||||
$table->timestamp('expires_at')->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('story_views', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('story_id')->unsigned()->index();
|
||||
$table->bigInteger('profile_id')->unsigned()->index();
|
||||
$table->unique(['profile_id', 'story_id']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('stories');
|
||||
Schema::dropIfExists('story_views');
|
||||
}
|
||||
}
|
BIN
public/js/components.js
vendored
BIN
public/js/components.js
vendored
Binary file not shown.
BIN
public/js/compose.js
vendored
BIN
public/js/compose.js
vendored
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/story-compose.js
vendored
Normal file
BIN
public/js/story-compose.js
vendored
Normal file
Binary file not shown.
BIN
public/js/theme-monokai.js
vendored
BIN
public/js/theme-monokai.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -84,52 +84,6 @@
|
|||
<div class="card-body p-0 border-top">
|
||||
<div v-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;">
|
||||
<div class="text-center">
|
||||
<a class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/compose">
|
||||
<div class="card-body">
|
||||
<div class="media">
|
||||
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
|
||||
<i class="far fa-image text-white fa-lg"></i>
|
||||
</div>
|
||||
<div class="media-body text-left">
|
||||
<h5 class="mt-0 font-weight-bold text-primary">New Post</h5>
|
||||
<p class="mb-0 text-muted">Share up to {{config.uploader.album_limit}} photos or videos.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="d-none card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" :click="showAddToStoryCard">
|
||||
<div class="card-body">
|
||||
<div class="media">
|
||||
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
|
||||
<i class="fas fa-history text-white fa-lg"></i>
|
||||
</div>
|
||||
<div class="media-body text-left">
|
||||
<p class="mb-0">
|
||||
<span class="h5 mt-0 font-weight-bold text-primary">Add to Story</span>
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Add a photo or video to your story.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/collections/create">
|
||||
<div class="card-body">
|
||||
<div class="media">
|
||||
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
|
||||
<i class="fas fa-images text-white fa-lg"></i>
|
||||
</div>
|
||||
<div class="media-body text-left">
|
||||
<p class="mb-0">
|
||||
<span class="h5 mt-0 font-weight-bold text-primary">New Collection</span>
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Create a curated collection of photos.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div v-if="media.length == 0" class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark">
|
||||
<div @click.prevent="addMedia" class="card-body">
|
||||
<div class="media">
|
||||
|
@ -138,17 +92,53 @@
|
|||
</div>
|
||||
<div class="media-body text-left">
|
||||
<p class="mb-0">
|
||||
<span class="h5 mt-0 font-weight-bold text-primary">Try ComposeUI v4</span>
|
||||
<sup>
|
||||
<span class="badge badge-primary pb-1">BETA</span>
|
||||
</sup>
|
||||
<span class="h5 mt-0 font-weight-bold text-primary">New Post</span>
|
||||
</p>
|
||||
<p class="mb-0 text-muted">The next generation compose experience.</p>
|
||||
<p class="mb-0 text-muted">Share up to {{config.uploader.album_limit}} photos or videos</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pt-3">
|
||||
<a v-if="config.features.stories == true" class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/stories/new">
|
||||
<div class="card-body">
|
||||
<div class="media">
|
||||
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;border: 2px solid #008DF5">
|
||||
<i class="fas fa-history text-primary fa-lg"></i>
|
||||
</div>
|
||||
<div class="media-body text-left">
|
||||
<p class="mb-0">
|
||||
<span class="h5 mt-0 font-weight-bold text-primary">New Story</span>
|
||||
<sup class="float-right mt-2">
|
||||
<span class="btn btn-outline-lighter p-1 btn-sm font-weight-bold py-0" style="font-size:10px;line-height: 0.6">BETA</span>
|
||||
</sup>
|
||||
</p>
|
||||
<p class="mb-0 text-muted">Add Photo to Story</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/collections/create">
|
||||
<div class="card-body">
|
||||
<div class="media">
|
||||
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;border: 2px solid #008DF5">
|
||||
<i class="fas fa-images text-primary fa-lg"></i>
|
||||
</div>
|
||||
<div class="media-body text-left">
|
||||
<p class="mb-0">
|
||||
<span class="h5 mt-0 font-weight-bold text-primary">New Collection</span>
|
||||
<sup class="float-right mt-2">
|
||||
<span class="btn btn-outline-lighter p-1 btn-sm font-weight-bold py-0" style="font-size:10px;line-height: 0.6">BETA</span>
|
||||
</sup>
|
||||
</p>
|
||||
<p class="mb-0 text-muted">New collection of posts</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
<p class="py-3">
|
||||
<a class="font-weight-bold" href="/site/help">Help</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -35,7 +35,12 @@
|
|||
<div class="d-block d-md-none mt-n3 mb-3">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle border mr-2" :src="profile.avatar" width="77px" height="77px">
|
||||
<div v-if="hasStory" class="has-story cursor-pointer shadow-sm" @click="storyRedirect()">
|
||||
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle" :src="profile.avatar" width="77px" height="77px">
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle border" :src="profile.avatar" width="77px" height="77px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="d-block d-md-none mt-3 py-2">
|
||||
|
@ -72,7 +77,12 @@
|
|||
|
||||
<!-- DESKTOP PROFILE PICTURE -->
|
||||
<div class="d-none d-md-block pb-5">
|
||||
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle box-shadow" :src="profile.avatar" width="150px" height="150px">
|
||||
<div v-if="hasStory" class="has-story-lg cursor-pointer shadow-sm" @click="storyRedirect()">
|
||||
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle box-shadow cursor-pointer" :src="profile.avatar" width="150px" height="150px">
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :alt="profileUsername + '\'s profile picture'" class="rounded-circle box-shadow" :src="profile.avatar" width="150px" height="150px">
|
||||
</div>
|
||||
<p v-if="sponsorList.patreon || sponsorList.liberapay || sponsorList.opencollective" class="text-center mt-3">
|
||||
<button type="button" @click="showSponsorModal" class="btn btn-outline-secondary font-weight-bold py-0">
|
||||
<i class="fas fa-heart text-danger"></i>
|
||||
|
@ -523,6 +533,34 @@
|
|||
.nav-topbar .nav-link .small {
|
||||
font-weight: 600;
|
||||
}
|
||||
.has-story {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
background: radial-gradient(ellipse at 70% 70%, #ee583f 8%, #d92d77 42%, #bd3381 58%);
|
||||
}
|
||||
.has-story img {
|
||||
width: 76px;
|
||||
height: 76px;
|
||||
border-radius: 50%;
|
||||
padding: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
.has-story-lg {
|
||||
width: 159px;
|
||||
height: 159px;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
background: radial-gradient(ellipse at 70% 70%, #ee583f 8%, #d92d77 42%, #bd3381 58%);
|
||||
}
|
||||
.has-story-lg img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 50%;
|
||||
padding: 6px;
|
||||
background:#fff;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
import VueMasonry from 'vue-masonry-css'
|
||||
|
@ -565,7 +603,8 @@
|
|||
collectionsPage: 2,
|
||||
isMobile: false,
|
||||
ctxEmbedPayload: null,
|
||||
copiedEmbed: false
|
||||
copiedEmbed: false,
|
||||
hasStory: null
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
@ -620,6 +659,10 @@
|
|||
this.profile = res.data;
|
||||
}).then(res => {
|
||||
this.fetchPosts();
|
||||
axios.get('/api/stories/v1/exists/' + this.profileId)
|
||||
.then(res => {
|
||||
this.hasStory = res.data == true;
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1133,6 +1176,10 @@
|
|||
this.$refs.embedModal.hide();
|
||||
this.$refs.visitorContextMenu.hide();
|
||||
},
|
||||
|
||||
storyRedirect() {
|
||||
window.location.href = '/stories/' + this.profileUsername;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,42 +1,268 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="container">
|
||||
<p class="display-4 text-center py-5">Share Your Story</p>
|
||||
<div class="container mt-2 mt-md-5">
|
||||
<input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" draggable="true" v-bind:accept="config.mimes">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 offset-md-3">
|
||||
|
||||
<div class="d-flex justify-content-center align-item-center">
|
||||
<div class="bg-dark" style="width:400px;height:600px">
|
||||
<p class="text-center text-light font-weight-bold">Add Photo</p>
|
||||
<!-- LANDING -->
|
||||
<div v-if="page == 'landing'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center" style="height: 90vh;">
|
||||
<div class="text-center flex-fill mt-5 pt-5">
|
||||
<img src="/img/pixelfed-icon-grey.svg" width="60px" height="60px">
|
||||
<p class="font-weight-bold lead text-lighter mt-1">Stories</p>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="card w-100 shadow-none">
|
||||
<div class="list-group">
|
||||
<a class="list-group-item text-center lead text-decoration-none text-dark" href="#" @click.prevent="upload()">Add Photo</a>
|
||||
<a v-if="stories.length" class="list-group-item text-center lead text-decoration-none text-dark" href="#" @click.prevent="edit()">Edit Story</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center flex-fill">
|
||||
<p class="text-lighter small text-uppercase">
|
||||
<a href="/" class="text-muted font-weight-bold">Home</a>
|
||||
<span class="px-2 text-lighter">|</span>
|
||||
<a href="/i/my/story" class="text-muted font-weight-bold">View My Story</a>
|
||||
<span class="px-2 text-lighter">|</span>
|
||||
<a href="/site/help" class="text-muted font-weight-bold">Help</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CROP -->
|
||||
<div v-if="page == 'crop'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center" style="height: 95vh;">
|
||||
<div class="text-center pt-5 mb-3 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<button class="btn btn-outline-lighter btn-sm py-0 px-md-3"><i class="pr-2 fas fa-chevron-left fa-sm"></i> Delete</button>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<img class="d-inline-block mr-2" src="/img/pixelfed-icon-grey.svg" width="30px" height="30px">
|
||||
<span class="font-weight-bold lead text-lighter">Stories</span>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-success btn-sm py-0 px-md-3">Crop <i class="pl-2 fas fa-chevron-right fa-sm"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="card w-100 mt-3">
|
||||
<div class="card-body p-0">
|
||||
<vue-cropper
|
||||
ref="cropper"
|
||||
:relativeZoom="cropper.zoom"
|
||||
:aspectRatio="cropper.aspectRatio"
|
||||
:viewMode="cropper.viewMode"
|
||||
:zoomable="cropper.zoomable"
|
||||
:rotatable="true"
|
||||
:src="mediaUrl"
|
||||
>
|
||||
</vue-cropper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center flex-fill">
|
||||
<p class="text-lighter small text-uppercase pt-2">
|
||||
<!-- <a href="#" class="text-muted font-weight-bold">Home</a>
|
||||
<span class="px-2 text-lighter">|</span>
|
||||
<a href="#" class="text-muted font-weight-bold">View My Story</a>
|
||||
<span class="px-2 text-lighter">|</span> -->
|
||||
<a href="/site/help" class="text-muted font-weight-bold mb-0">Help</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ERROR -->
|
||||
<div v-if="page == 'error'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;">
|
||||
<p class="h3 mb-0">Oops!</p>
|
||||
<p class="text-muted lead">An error occurred, please try again later.</p>
|
||||
<p class="text-muted mb-0">
|
||||
<a class="btn btn-outline-secondary py-0 px-5 font-weight-bold" href="/">Go back</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="page == 'edit'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center" style="height: 90vh;">
|
||||
<div class="text-center flex-fill mt-5 pt-5">
|
||||
<img src="/img/pixelfed-icon-grey.svg" width="60px" height="60px">
|
||||
<p class="font-weight-bold lead text-lighter mt-1">Stories</p>
|
||||
</div>
|
||||
<div class="flex-fill py-5">
|
||||
<div class="card w-100 shadow-none" style="max-height: 500px; overflow-y: auto">
|
||||
<div class="list-group">
|
||||
<div v-for="(story, index) in stories" class="list-group-item text-center text-dark" href="#">
|
||||
<div class="media align-items-center">
|
||||
<img :src="story.src" class="img-fluid mr-3 cursor-pointer" width="70px" height="70px" @click="showLightbox(story)">
|
||||
<div class="media-body">
|
||||
<p class="mb-0">Expires</p>
|
||||
<p class="mb-0 text-muted small"><span>{{expiresTimestamp(story.expires_at)}}</span></p>
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<button @click="deleteStory(story, index)" class="btn btn-danger btn-sm font-weight-bold text-uppercase">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-fill text-center">
|
||||
<a class="btn btn-outline-secondary py-0 px-5 font-weight-bold" href="/i/stories/new">Go back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<b-modal
|
||||
id="lightbox"
|
||||
ref="lightboxModal"
|
||||
hide-header
|
||||
hide-footer
|
||||
centered
|
||||
size="lg"
|
||||
body-class="p-0"
|
||||
>
|
||||
<div v-if="lightboxMedia" class="w-100 h-100">
|
||||
<img :src="lightboxMedia.url" style="max-height: 100%; max-width: 100%">
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style type="text/css" scoped>
|
||||
.navtab .nav-link {
|
||||
color: #657786;
|
||||
}
|
||||
|
||||
.navtab .nav-link.active {
|
||||
color: #08d;
|
||||
border-bottom: 4px solid #08d;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
import VueTimeago from 'vue-timeago';
|
||||
import VueCropper from 'vue-cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
export default {
|
||||
components: {
|
||||
VueCropper,
|
||||
VueTimeago
|
||||
},
|
||||
|
||||
props: ['profile-id'],
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'upload',
|
||||
config: window.App.config,
|
||||
mimes: [
|
||||
'image/jpeg',
|
||||
'image/png'
|
||||
],
|
||||
page: 'landing',
|
||||
pages: [
|
||||
'landing',
|
||||
'crop',
|
||||
'edit',
|
||||
'confirm',
|
||||
'error'
|
||||
],
|
||||
uploading: false,
|
||||
uploadProgress: 100,
|
||||
cropper: {
|
||||
aspectRatio: 9/16,
|
||||
viewMode: 1,
|
||||
zoomable: true,
|
||||
zoom: null
|
||||
},
|
||||
mediaUrl: null,
|
||||
stories: [],
|
||||
lightboxMedia: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.welcomeMessage();
|
||||
this.mediaWatcher();
|
||||
axios.get('/api/stories/v1/fetch/' + this.profileId)
|
||||
.then(res => this.stories = res.data);
|
||||
},
|
||||
|
||||
methods: {
|
||||
welcomeMessage() {
|
||||
|
||||
upload() {
|
||||
let fi = $('.file-input[name="media"]');
|
||||
fi.trigger('click');
|
||||
},
|
||||
|
||||
mediaWatcher() {
|
||||
let self = this;
|
||||
$(document).on('change', '#pf-dz', function(e) {
|
||||
self.triggerUpload();
|
||||
});
|
||||
},
|
||||
|
||||
triggerUpload() {
|
||||
let self = this;
|
||||
self.uploading = true;
|
||||
let io = document.querySelector('#pf-dz');
|
||||
Array.prototype.forEach.call(io.files, function(io, i) {
|
||||
if(self.media && self.media.length + i >= self.config.uploader.album_limit) {
|
||||
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
|
||||
self.uploading = false;
|
||||
self.page = 2;
|
||||
return;
|
||||
}
|
||||
let type = io.type;
|
||||
let validated = $.inArray(type, self.mimes);
|
||||
if(validated == -1) {
|
||||
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.mimes+' only.', 'error');
|
||||
self.uploading = false;
|
||||
self.page = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
let form = new FormData();
|
||||
form.append('file', io);
|
||||
|
||||
let xhrConfig = {
|
||||
onUploadProgress: function(e) {
|
||||
let progress = Math.round( (e.loaded * 100) / e.total );
|
||||
self.uploadProgress = progress;
|
||||
}
|
||||
};
|
||||
|
||||
axios.post('/api/stories/v1/add', form, xhrConfig)
|
||||
.then(function(e) {
|
||||
self.uploadProgress = 100;
|
||||
self.uploading = false;
|
||||
window.location.href = '/i/my/story';
|
||||
self.mediaUrl = e.data.media_url;
|
||||
}).catch(function(e) {
|
||||
self.uploading = false;
|
||||
io.value = null;
|
||||
swal('Oops!', e.response.data.message, 'warning');
|
||||
});
|
||||
io.value = null;
|
||||
self.uploadProgress = 0;
|
||||
});
|
||||
},
|
||||
|
||||
expiresTimestamp(ts) {
|
||||
ts = new Date(ts * 1000);
|
||||
return ts.toDateString() + ' ' + ts.toLocaleTimeString();
|
||||
},
|
||||
|
||||
edit() {
|
||||
this.page = 'edit';
|
||||
},
|
||||
|
||||
showLightbox(story) {
|
||||
this.lightboxMedia = {
|
||||
url: story.src
|
||||
}
|
||||
this.$refs.lightboxModal.show();
|
||||
},
|
||||
|
||||
deleteStory(story, index) {
|
||||
if(window.confirm('Are you sure you want to delete this Story?') != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.delete('/api/stories/v1/delete/' + story.id)
|
||||
.then(res => {
|
||||
this.stories.splice(index, 1);
|
||||
if(this.stories.length == 0) {
|
||||
window.location.href = '/i/stories/new';
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
102
resources/assets/js/components/StoryViewer.vue
Normal file
102
resources/assets/js/components/StoryViewer.vue
Normal file
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div v-if="loading" class="row">
|
||||
<div class="col-12 mt-5 pt-5">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="stories.length != 0">
|
||||
<div id="storyContainer" class="d-none m-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
import 'zuck.js/dist/zuck.css';
|
||||
import 'zuck.js/dist/skins/snapgram.css';
|
||||
window.Zuck = require('zuck.js');
|
||||
|
||||
export default {
|
||||
props: ['pid'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
stories: {},
|
||||
}
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
this.fetchStories();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchStories() {
|
||||
axios.get('/api/stories/v1/profile/' + this.pid)
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
if(data.length == 0) {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
window._storyData = data;
|
||||
window.stories = new Zuck('storyContainer', {
|
||||
stories: data,
|
||||
localStorage: false,
|
||||
callbacks: {
|
||||
onOpen (storyId, callback) {
|
||||
document.body.style.overflow = "hidden";
|
||||
callback()
|
||||
},
|
||||
|
||||
onEnd (storyId, callback) {
|
||||
axios.post('/i/stories/viewed', {
|
||||
id: storyId
|
||||
});
|
||||
callback();
|
||||
},
|
||||
|
||||
onClose (storyId, callback) {
|
||||
document.body.style.overflow = "auto";
|
||||
callback();
|
||||
window.location.href = '/';
|
||||
},
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
|
||||
// todo: refactor this mess
|
||||
document.querySelectorAll('#storyContainer .story')[0].click()
|
||||
})
|
||||
.catch(err => {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
#storyContainer .story {
|
||||
margin-right: 2rem;
|
||||
width: 100%;
|
||||
max-width: 64px;
|
||||
}
|
||||
.stories.carousel .story > .item-link > .item-preview {
|
||||
height: 64px;
|
||||
}
|
||||
#zuck-modal.with-effects {
|
||||
width: 100%;
|
||||
}
|
||||
.stories.carousel .story > .item-link > .info .name {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
.stories.carousel .story > .item-link > .info {
|
||||
}
|
||||
</style>
|
|
@ -2,7 +2,7 @@
|
|||
<div class="container" style="">
|
||||
<div v-if="layout === 'feed'" class="row">
|
||||
<div :class="[modes.distractionFree ? 'col-md-8 col-lg-8 offset-md-2 px-0 my-sm-3 timeline order-2 order-md-1':'col-md-8 col-lg-8 px-0 my-sm-3 timeline order-2 order-md-1']">
|
||||
<div class="d-none" data-id="StoryTimelineComponent"></div>
|
||||
<story-component v-if="config.features.stories"></story-component>
|
||||
<div style="padding-top:10px;">
|
||||
<div v-if="loading" class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
|
@ -255,9 +255,9 @@
|
|||
<announcements-card v-on:show-tips="showTips = $event"></announcements-card>
|
||||
</div>
|
||||
|
||||
<div v-show="modes.notify == true && !loading" class="mb-4">
|
||||
<!-- <div v-show="modes.notify == true && !loading" class="mb-4">
|
||||
<notification-card></notification-card>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div v-show="showSuggestions == true && suggestions.length && config.ab && config.ab.rec == true" class="mb-4">
|
||||
<div class="card">
|
||||
|
|
5
resources/assets/js/profile.js
vendored
5
resources/assets/js/profile.js
vendored
|
@ -28,6 +28,11 @@ Vue.component(
|
|||
require('./components/PostMenu.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'story-viewer',
|
||||
require('./components/StoryViewer.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'profile',
|
||||
require('./components/Profile.vue').default
|
||||
|
|
4
resources/assets/js/story-compose.js
vendored
Normal file
4
resources/assets/js/story-compose.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Vue.component(
|
||||
'story-compose',
|
||||
require('./components/StoryCompose.vue').default
|
||||
);
|
5
resources/assets/js/timeline.js
vendored
5
resources/assets/js/timeline.js
vendored
|
@ -41,4 +41,9 @@ Vue.component(
|
|||
Vue.component(
|
||||
'announcements-card',
|
||||
require('./components/AnnouncementsCard.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'story-component',
|
||||
require('./components/StoryTimelineComponent.vue').default
|
||||
);
|
11
resources/views/profile/story.blade.php
Normal file
11
resources/views/profile/story.blade.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<story-viewer pid="{{$pid}}"></story-viewer>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{mix('js/compose.js')}}"></script>
|
||||
<script type="text/javascript" src="{{mix('js/profile.js')}}"></script>
|
||||
<script type="text/javascript">App.boot();</script>
|
||||
@endpush
|
|
@ -179,8 +179,8 @@
|
|||
</div>
|
||||
<div class="form-check pb-3">
|
||||
<input class="form-check-input" type="checkbox" id="show_tips">
|
||||
<label class="form-check-label font-weight-bold">Show Tips</label>
|
||||
<p class="text-muted small help-text">Show Tips on Timelines (Desktop Only)</p>
|
||||
<label class="form-check-label font-weight-bold">Show Announcements</label>
|
||||
<p class="text-muted small help-text">Show Announcements on Timelines (Desktop Only)</p>
|
||||
</div>
|
||||
<div class="form-check pb-3">
|
||||
<input class="form-check-input" type="checkbox" id="force_metro">
|
||||
|
|
11
resources/views/stories/compose.blade.php
Normal file
11
resources/views/stories/compose.blade.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
|
||||
@section('content')
|
||||
<story-compose profile-id="{{auth()->user()->profile_id}}"></story-compose>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/story-compose.js') }}"></script>
|
||||
<script type="text/javascript">window.App.boot()</script>
|
||||
@endpush
|
|
@ -178,6 +178,14 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::group(['prefix' => 'admin'], function () {
|
||||
Route::post('moderate', 'Api\AdminApiController@moderate');
|
||||
});
|
||||
Route::group(['prefix' => 'stories'], function () {
|
||||
Route::get('v1/recent', 'StoryController@apiV1Recent');
|
||||
Route::post('v1/add', 'StoryController@apiV1Add')->middleware('throttle:maxStoriesPerDay,1440');
|
||||
Route::get('v1/fetch/{id}', 'StoryController@apiV1Fetch');
|
||||
Route::get('v1/profile/{id}', 'StoryController@apiV1Profile');
|
||||
Route::get('v1/exists/{id}', 'StoryController@apiV1Exists');
|
||||
Route::delete('v1/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -238,6 +246,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
|
||||
Route::get('me', 'ProfileController@meRedirect');
|
||||
Route::get('intent/follow', 'SiteController@followIntent');
|
||||
Route::post('stories/viewed', 'StoryController@apiV1Viewed');
|
||||
Route::get('stories/new', 'StoryController@compose');
|
||||
Route::get('my/story', 'StoryController@iRedirect');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'account'], function () {
|
||||
|
@ -389,6 +400,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('{username}', 'ProfileController@permalinkRedirect');
|
||||
});
|
||||
|
||||
Route::get('stories/{username}', 'ProfileController@stories');
|
||||
Route::get('c/{collection}', 'CollectionController@show');
|
||||
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
|
||||
Route::get('p/{username}/{id}/embed', 'StatusController@showEmbed');
|
||||
|
|
1
webpack.mix.js
vendored
1
webpack.mix.js
vendored
|
@ -33,6 +33,7 @@ mix.js('resources/assets/js/app.js', 'public/js')
|
|||
.js('resources/assets/js/collectioncompose.js', 'public/js')
|
||||
.js('resources/assets/js/collections.js', 'public/js')
|
||||
.js('resources/assets/js/profile-directory.js', 'public/js')
|
||||
.js('resources/assets/js/story-compose.js', 'public/js')
|
||||
// .js('resources/assets/js/embed.js', 'public')
|
||||
// .js('resources/assets/js/direct.js', 'public/js')
|
||||
// .js('resources/assets/js/admin.js', 'public/js')
|
||||
|
|
Loading…
Reference in a new issue