Merge pull request #2738 from pixelfed/staging

Staging
This commit is contained in:
daniel 2021-04-29 23:55:10 -06:00 committed by GitHub
commit 1b7ef61a4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 871 additions and 421 deletions

View file

@ -67,6 +67,11 @@
- Updated AdminInstanceController, invalidate banned domain cache when updated. ([35393edf](https://github.com/pixelfed/pixelfed/commit/35393edf)) - Updated AdminInstanceController, invalidate banned domain cache when updated. ([35393edf](https://github.com/pixelfed/pixelfed/commit/35393edf))
- Updated AP Helpers, use instance filtering. ([66b4f8c7](https://github.com/pixelfed/pixelfed/commit/66b4f8c7)) - Updated AP Helpers, use instance filtering. ([66b4f8c7](https://github.com/pixelfed/pixelfed/commit/66b4f8c7))
- Updated ApiV1Controller, add missing instance api attributes. ([64b86546](https://github.com/pixelfed/pixelfed/commit/64b86546)) - Updated ApiV1Controller, add missing instance api attributes. ([64b86546](https://github.com/pixelfed/pixelfed/commit/64b86546))
- Updated story garbage collection, handle non active stories and new ephemeral story media directory. ([c43f8bcc](https://github.com/pixelfed/pixelfed/commit/c43f8bcc))
- Updated Stories, add crop and duration settings to composer. ([c8edca69](https://github.com/pixelfed/pixelfed/commit/c8edca69))
- Updated instance endpoint, add custom description. ([668e936e](https://github.com/pixelfed/pixelfed/commit/668e936e))
- Updated StoryCompose component, improve full screen preview. ([39a76103](https://github.com/pixelfed/pixelfed/commit/39a76103))
- Updated Helpers, fix broken tests. ([22dddaa0](https://github.com/pixelfed/pixelfed/commit/22dddaa0))
- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10) ## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10)

View file

@ -3,14 +3,10 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\{ use Illuminate\Support\Facades\DB;
DB, use Illuminate\Support\Facades\Storage;
Storage use App\Story;
}; use App\StoryView;
use App\{
Story,
StoryView
};
class StoryGC extends Command class StoryGC extends Command
{ {
@ -52,9 +48,9 @@ class StoryGC extends Command
protected function directoryScan() protected function directoryScan()
{ {
$day = now()->day; $hour = now()->hour;
if($day != 3) { if($hour !== 1) {
return; return;
} }
@ -72,6 +68,18 @@ class StoryGC extends Command
$this->deleteDirectory($dir); $this->deleteDirectory($dir);
} }
} }
$mh = hash('sha256', date('Y').'-.-'.date('m'));
$monthHash = date('Y').date('m').substr($mh, 0, 6).substr($mh, 58, 6);
$dirs = Storage::directories('public/_esm.t3');
foreach($dirs as $dir) {
$hash = last(explode('/', $dir));
if($hash != $monthHash) {
$this->info('Found directory to delete: ' . $dir);
$this->deleteDirectory($dir);
}
}
} }
protected function deleteDirectory($path) protected function deleteDirectory($path)
@ -86,7 +94,23 @@ class StoryGC extends Command
protected function deleteStories() protected function deleteStories()
{ {
$stories = Story::where('created_at', '<', now()->subMinutes(1441))->take(50)->get(); $stories = Story::where('created_at', '>', now()->subMinutes(30))
->whereNull('active')
->get();
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();
});
}
$stories = Story::where('created_at', '<', now()
->subMinutes(1441))
->get();
if($stories->count() == 0) { if($stories->count() == 0) {
exit; exit;

View file

@ -48,6 +48,11 @@ class Handler extends ExceptionHandler
*/ */
public function render($request, Throwable $exception) public function render($request, Throwable $exception)
{ {
if ($request->wantsJson())
return response()->json(
['error' => $exception->getMessage()],
method_exists($exception, 'getStatusCode') ? $exception->getStatusCode() : 500
);
return parent::render($request, $exception); return parent::render($request, $exception);
} }
} }

View file

@ -960,7 +960,7 @@ class ApiV1Controller extends Controller
$res = [ $res = [
'approval_required' => false, 'approval_required' => false,
'contact_account' => null, 'contact_account' => null,
'description' => 'Pixelfed - Photo sharing for everyone', 'description' => config('instance.description'),
'email' => config('instance.email'), 'email' => config('instance.email'),
'invites_enabled' => false, 'invites_enabled' => false,
'rules' => [], 'rules' => [],

View file

@ -12,7 +12,7 @@ use App\Services\StoryService;
use Cache, Storage; use Cache, Storage;
use Image as Intervention; use Image as Intervention;
use App\Services\FollowerService; use App\Services\FollowerService;
use App\Services\MediaPathService;
class StoryController extends Controller class StoryController extends Controller
{ {
@ -37,7 +37,7 @@ class StoryController extends Controller
} }
$photo = $request->file('file'); $photo = $request->file('file');
$path = $this->storePhoto($photo); $path = $this->storePhoto($photo, $user);
$story = new Story(); $story = new Story();
$story->duration = 3; $story->duration = 3;
@ -47,21 +47,18 @@ class StoryController extends Controller
$story->path = $path; $story->path = $path;
$story->local = true; $story->local = true;
$story->size = $photo->getSize(); $story->size = $photo->getSize();
$story->expires_at = now()->addHours(24);
$story->save(); $story->save();
return [ return [
'code' => 200, 'code' => 200,
'msg' => 'Successfully added', 'msg' => 'Successfully added',
'media_id' => (string) $story->id,
'media_url' => url(Storage::url($story->path)) 'media_url' => url(Storage::url($story->path))
]; ];
} }
protected function storePhoto($photo) protected function storePhoto($photo, $user)
{ {
$monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12);
$sid = (string) Str::uuid();
$rid = Str::random(9).'.'.Str::random(9);
$mimes = explode(',', config('pixelfed.media_types')); $mimes = explode(',', config('pixelfed.media_types'));
if(in_array($photo->getMimeType(), [ if(in_array($photo->getMimeType(), [
'image/jpeg', 'image/jpeg',
@ -72,9 +69,9 @@ class StoryController extends Controller
return; return;
} }
$storagePath = "public/_esm.t2/{$monthHash}/{$sid}/{$rid}"; $storagePath = MediaPathService::story($user->profile);
$path = $photo->store($storagePath); $path = $photo->store($storagePath);
if(in_array($photo->getMimeType(), ['image/jpeg','image/png',])) { if(in_array($photo->getMimeType(), ['image/jpeg','image/png'])) {
$fpath = storage_path('app/' . $path); $fpath = storage_path('app/' . $path);
$img = Intervention::make($fpath); $img = Intervention::make($fpath);
$img->orientate(); $img->orientate();
@ -84,6 +81,68 @@ class StoryController extends Controller
return $path; return $path;
} }
public function cropPhoto(Request $request)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'media_id' => 'required|integer|min:1',
'width' => 'required',
'height' => 'required',
'x' => 'required',
'y' => 'required'
]);
$user = $request->user();
$id = $request->input('media_id');
$width = round($request->input('width'));
$height = round($request->input('height'));
$x = round($request->input('x'));
$y = round($request->input('y'));
$story = Story::whereProfileId($user->profile_id)->findOrFail($id);
$path = storage_path('app/' . $story->path);
if(!is_file($path)) {
abort(400, 'Invalid or missing media.');
}
$img = Intervention::make($path);
$img->crop($width, $height, $x, $y);
$img->save($path, config('pixelfed.image_quality'));
return [
'code' => 200,
'msg' => 'Successfully cropped',
];
}
public function publishStory(Request $request)
{
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$this->validate($request, [
'media_id' => 'required',
'duration' => 'required|integer|min:3|max:10'
]);
$id = $request->input('media_id');
$user = $request->user();
$story = Story::whereProfileId($user->profile_id)
->findOrFail($id);
$story->active = true;
$story->duration = $request->input('duration', 10);
$story->expires_at = now()->addHours(24);
$story->save();
return [
'code' => 200,
'msg' => 'Successfully published',
];
}
public function apiV1Delete(Request $request, $id) public function apiV1Delete(Request $request, $id)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
@ -114,6 +173,7 @@ class StoryController extends Controller
if(config('database.default') == 'pgsql') { if(config('database.default') == 'pgsql') {
$db = Story::with('profile') $db = Story::with('profile')
->whereActive(true)
->whereIn('profile_id', $following) ->whereIn('profile_id', $following)
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->distinct('profile_id') ->distinct('profile_id')
@ -121,8 +181,9 @@ class StoryController extends Controller
->get(); ->get();
} else { } else {
$db = Story::with('profile') $db = Story::with('profile')
->whereActive(true)
->whereIn('profile_id', $following) ->whereIn('profile_id', $following)
->where('expires_at', '>', now()) ->where('created_at', '>', now()->subDay())
->orderByDesc('expires_at') ->orderByDesc('expires_at')
->groupBy('profile_id') ->groupBy('profile_id')
->take(9) ->take(9)
@ -158,6 +219,7 @@ class StoryController extends Controller
} }
$stories = Story::whereProfileId($profile->id) $stories = Story::whereProfileId($profile->id)
->whereActive(true)
->orderBy('expires_at', 'desc') ->orderBy('expires_at', 'desc')
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->when(!$publicOnly, function($query, $publicOnly) { ->when(!$publicOnly, function($query, $publicOnly) {
@ -187,6 +249,7 @@ class StoryController extends Controller
$authed = $request->user()->profile; $authed = $request->user()->profile;
$story = Story::with('profile') $story = Story::with('profile')
->whereActive(true)
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->findOrFail($id); ->findOrFail($id);
@ -202,7 +265,7 @@ class StoryController extends Controller
$res = [ $res = [
'id' => (string) $story->id, 'id' => (string) $story->id,
'type' => Str::endsWith($story->path, '.mp4') ? 'video' :'photo', 'type' => Str::endsWith($story->path, '.mp4') ? 'video' :'photo',
'length' => 3, 'length' => 10,
'src' => url(Storage::url($story->path)), 'src' => url(Storage::url($story->path)),
'preview' => null, 'preview' => null,
'link' => null, 'link' => null,
@ -227,6 +290,7 @@ class StoryController extends Controller
} }
$stories = Story::whereProfileId($profile->id) $stories = Story::whereProfileId($profile->id)
->whereActive(true)
->orderBy('expires_at') ->orderBy('expires_at')
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->when(!$publicOnly, function($query, $publicOnly) { ->when(!$publicOnly, function($query, $publicOnly) {
@ -237,7 +301,7 @@ class StoryController extends Controller
return [ return [
'id' => $s->id, 'id' => $s->id,
'type' => Str::endsWith($s->path, '.mp4') ? 'video' :'photo', 'type' => Str::endsWith($s->path, '.mp4') ? 'video' :'photo',
'length' => 3, 'length' => 10,
'src' => url(Storage::url($s->path)), 'src' => url(Storage::url($s->path)),
'preview' => null, 'preview' => null,
'link' => null, 'link' => null,
@ -272,19 +336,21 @@ class StoryController extends Controller
'id' => 'required|integer|min:1|exists:stories', 'id' => 'required|integer|min:1|exists:stories',
]); ]);
$id = $request->input('id'); $id = $request->input('id');
$authed = $request->user()->profile; $authed = $request->user()->profile;
$story = Story::with('profile') $story = Story::with('profile')
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->orderByDesc('expires_at') ->orderByDesc('expires_at')
->findOrFail($id); ->findOrFail($id);
$profile = $story->profile; $profile = $story->profile;
if($story->profile_id == $authed->id) { if($story->profile_id == $authed->id) {
$publicOnly = true; return [];
} else {
$publicOnly = (bool) $profile->followedBy($authed);
} }
$publicOnly = (bool) $profile->followedBy($authed);
abort_if(!$publicOnly, 403); abort_if(!$publicOnly, 403);
StoryView::firstOrCreate([ StoryView::firstOrCreate([
@ -292,6 +358,9 @@ class StoryController extends Controller
'profile_id' => $authed->id 'profile_id' => $authed->id
]); ]);
$story->view_count = $story->view_count + 1;
$story->save();
return ['code' => 200]; return ['code' => 200];
} }
@ -300,6 +369,7 @@ class StoryController extends Controller
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$res = (bool) Story::whereProfileId($id) $res = (bool) Story::whereProfileId($id)
->whereActive(true)
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->count(); ->count();
@ -312,6 +382,7 @@ class StoryController extends Controller
$profile = $request->user()->profile; $profile = $request->user()->profile;
$stories = Story::whereProfileId($profile->id) $stories = Story::whereProfileId($profile->id)
->whereActive(true)
->orderBy('expires_at') ->orderBy('expires_at')
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
->get() ->get()

View file

@ -101,7 +101,11 @@ class ImportInstagram implements ShouldQueue
$path = storage_path("app/$importData->path"); $path = storage_path("app/$importData->path");
$storagePath = "public/m/{$monthHash}/{$userHash}"; $storagePath = "public/m/{$monthHash}/{$userHash}";
$newPath = "app/$storagePath/$filename"; $dir = "app/$storagePath";
if(!is_dir(storage_path($dir))) {
mkdir(storage_path($dir), 0755, true);
}
$newPath = "$dir/$filename";
$fs->move($path,storage_path($newPath)); $fs->move($path,storage_path($newPath));
$path = $newPath; $path = $newPath;
$hash = \hash_file('sha256', storage_path($path)); $hash = \hash_file('sha256', storage_path($path));

View file

@ -176,11 +176,13 @@ class Helpers {
} }
} }
if(app()->environment() === 'production') {
$bannedInstances = InstanceService::getBannedDomains(); $bannedInstances = InstanceService::getBannedDomains();
if(in_array($host, $bannedInstances)) { if(in_array($host, $bannedInstances)) {
return false; return false;
} }
}
if(in_array($host, $localhosts)) { if(in_array($host, $localhosts)) {
return false; return false;

193
composer.lock generated
View file

@ -130,16 +130,16 @@
}, },
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.178.6", "version": "3.179.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "0aa83b522d5ffa794c02e7411af87a0e241a3082" "reference": "f4f2c01b53f71379a1ed8ccccf4305949a69ccbe"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0aa83b522d5ffa794c02e7411af87a0e241a3082", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f4f2c01b53f71379a1ed8ccccf4305949a69ccbe",
"reference": "0aa83b522d5ffa794c02e7411af87a0e241a3082", "reference": "f4f2c01b53f71379a1ed8ccccf4305949a69ccbe",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -214,9 +214,9 @@
"support": { "support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues", "issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.178.6" "source": "https://github.com/aws/aws-sdk-php/tree/3.179.1"
}, },
"time": "2021-04-19T18:13:17+00:00" "time": "2021-04-29T18:17:25+00:00"
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -656,40 +656,39 @@
}, },
{ {
"name": "doctrine/cache", "name": "doctrine/cache",
"version": "1.10.2", "version": "1.11.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/cache.git", "url": "https://github.com/doctrine/cache.git",
"reference": "13e3381b25847283a91948d04640543941309727" "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", "url": "https://api.github.com/repos/doctrine/cache/zipball/a9c1b59eba5a08ca2770a76eddb88922f504e8e0",
"reference": "13e3381b25847283a91948d04640543941309727", "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "~7.1 || ^8.0" "php": "~7.1 || ^8.0"
}, },
"conflict": { "conflict": {
"doctrine/common": ">2.2,<2.4" "doctrine/common": ">2.2,<2.4",
"psr/cache": ">=3"
}, },
"require-dev": { "require-dev": {
"alcaeus/mongo-php-adapter": "^1.1", "alcaeus/mongo-php-adapter": "^1.1",
"doctrine/coding-standard": "^6.0", "cache/integration-tests": "dev-master",
"doctrine/coding-standard": "^8.0",
"mongodb/mongodb": "^1.1", "mongodb/mongodb": "^1.1",
"phpunit/phpunit": "^7.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"predis/predis": "~1.0" "predis/predis": "~1.0",
"psr/cache": "^1.0 || ^2.0",
"symfony/cache": "^4.4 || ^5.2"
}, },
"suggest": { "suggest": {
"alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9.x-dev"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
@ -736,7 +735,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/cache/issues", "issues": "https://github.com/doctrine/cache/issues",
"source": "https://github.com/doctrine/cache/tree/1.10.x" "source": "https://github.com/doctrine/cache/tree/1.11.0"
}, },
"funding": [ "funding": [
{ {
@ -752,7 +751,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-07-07T18:54:01+00:00" "time": "2021-04-13T14:46:17+00:00"
}, },
{ {
"name": "doctrine/dbal", "name": "doctrine/dbal",
@ -1517,16 +1516,16 @@
}, },
{ {
"name": "fruitcake/laravel-cors", "name": "fruitcake/laravel-cors",
"version": "v2.0.3", "version": "v2.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fruitcake/laravel-cors.git", "url": "https://github.com/fruitcake/laravel-cors.git",
"reference": "01de0fe5f71c70d1930ee9a80385f9cc28e0f63a" "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/01de0fe5f71c70d1930ee9a80385f9cc28e0f63a", "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/a8ccedc7ca95189ead0e407c43b530dc17791d6a",
"reference": "01de0fe5f71c70d1930ee9a80385f9cc28e0f63a", "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1539,8 +1538,8 @@
}, },
"require-dev": { "require-dev": {
"laravel/framework": "^6|^7|^8", "laravel/framework": "^6|^7|^8",
"orchestra/testbench-dusk": "^4|^5|^6", "orchestra/testbench-dusk": "^4|^5|^6|^7",
"phpunit/phpunit": "^6|^7|^8", "phpunit/phpunit": "^6|^7|^8|^9",
"squizlabs/php_codesniffer": "^3.5" "squizlabs/php_codesniffer": "^3.5"
}, },
"type": "library", "type": "library",
@ -1582,7 +1581,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/fruitcake/laravel-cors/issues", "issues": "https://github.com/fruitcake/laravel-cors/issues",
"source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.3" "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.4"
}, },
"funding": [ "funding": [
{ {
@ -1590,7 +1589,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-10-22T13:57:20+00:00" "time": "2021-04-26T11:24:25+00:00"
}, },
{ {
"name": "geerlingguy/ping", "name": "geerlingguy/ping",
@ -1823,16 +1822,16 @@
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "1.8.1", "version": "1.8.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "35ea11d335fd638b5882ff1725228b3d35496ab1" "reference": "dc960a912984efb74d0a90222870c72c87f10c91"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/35ea11d335fd638b5882ff1725228b3d35496ab1", "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
"reference": "35ea11d335fd638b5882ff1725228b3d35496ab1", "reference": "dc960a912984efb74d0a90222870c72c87f10c91",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1892,9 +1891,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.8.1" "source": "https://github.com/guzzle/psr7/tree/1.8.2"
}, },
"time": "2021-03-21T16:25:00+00:00" "time": "2021-04-26T09:17:50+00:00"
}, },
{ {
"name": "intervention/image", "name": "intervention/image",
@ -2107,16 +2106,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v8.38.0", "version": "v8.40.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "26a73532c54d2c090692bf2e3e64e449669053ba" "reference": "a654897ad7f97aea9d7ef292803939798c4a02a4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/26a73532c54d2c090692bf2e3e64e449669053ba", "url": "https://api.github.com/repos/laravel/framework/zipball/a654897ad7f97aea9d7ef292803939798c4a02a4",
"reference": "26a73532c54d2c090692bf2e3e64e449669053ba", "reference": "a654897ad7f97aea9d7ef292803939798c4a02a4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2271,7 +2270,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2021-04-20T13:50:21+00:00" "time": "2021-04-28T14:38:56+00:00"
}, },
{ {
"name": "laravel/helpers", "name": "laravel/helpers",
@ -2331,16 +2330,16 @@
}, },
{ {
"name": "laravel/horizon", "name": "laravel/horizon",
"version": "v5.7.5", "version": "v5.7.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/horizon.git", "url": "https://github.com/laravel/horizon.git",
"reference": "9bf0ef4873d9e52f58a9cd1de69dcdd98a5c4fe8" "reference": "24ffd819df749ef11a4eb20e14150b671d4f5717"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/horizon/zipball/9bf0ef4873d9e52f58a9cd1de69dcdd98a5c4fe8", "url": "https://api.github.com/repos/laravel/horizon/zipball/24ffd819df749ef11a4eb20e14150b671d4f5717",
"reference": "9bf0ef4873d9e52f58a9cd1de69dcdd98a5c4fe8", "reference": "24ffd819df749ef11a4eb20e14150b671d4f5717",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2402,9 +2401,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/laravel/horizon/issues", "issues": "https://github.com/laravel/horizon/issues",
"source": "https://github.com/laravel/horizon/tree/v5.7.5" "source": "https://github.com/laravel/horizon/tree/v5.7.6"
}, },
"time": "2021-04-06T14:17:52+00:00" "time": "2021-04-27T18:00:46+00:00"
}, },
{ {
"name": "laravel/passport", "name": "laravel/passport",
@ -3913,16 +3912,16 @@
}, },
{ {
"name": "pbmedia/laravel-ffmpeg", "name": "pbmedia/laravel-ffmpeg",
"version": "7.5.10", "version": "7.5.11",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/protonemedia/laravel-ffmpeg.git", "url": "https://github.com/protonemedia/laravel-ffmpeg.git",
"reference": "d3c3b77e5de08d4038ebcb9e9d405d51ec8ce2f9" "reference": "95b75f41edce12f513df12928b10b8f6949ffe56"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/d3c3b77e5de08d4038ebcb9e9d405d51ec8ce2f9", "url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/95b75f41edce12f513df12928b10b8f6949ffe56",
"reference": "d3c3b77e5de08d4038ebcb9e9d405d51ec8ce2f9", "reference": "95b75f41edce12f513df12928b10b8f6949ffe56",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3934,7 +3933,7 @@
"illuminate/support": "^6.0|^7.0|^8.0", "illuminate/support": "^6.0|^7.0|^8.0",
"league/flysystem": "^1.0.34", "league/flysystem": "^1.0.34",
"php": "^7.3|^8.0", "php": "^7.3|^8.0",
"php-ffmpeg/php-ffmpeg": "^0.17.0|^0.18.0" "php-ffmpeg/php-ffmpeg": "^0.18.0"
}, },
"require-dev": { "require-dev": {
"league/flysystem-memory": "^1.0", "league/flysystem-memory": "^1.0",
@ -3986,7 +3985,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/protonemedia/laravel-ffmpeg/issues", "issues": "https://github.com/protonemedia/laravel-ffmpeg/issues",
"source": "https://github.com/protonemedia/laravel-ffmpeg/tree/7.5.10" "source": "https://github.com/protonemedia/laravel-ffmpeg/tree/7.5.11"
}, },
"funding": [ "funding": [
{ {
@ -3994,7 +3993,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2021-03-31T14:18:52+00:00" "time": "2021-04-25T20:47:01+00:00"
}, },
{ {
"name": "php-ffmpeg/php-ffmpeg", "name": "php-ffmpeg/php-ffmpeg",
@ -5315,16 +5314,16 @@
}, },
{ {
"name": "spatie/image-optimizer", "name": "spatie/image-optimizer",
"version": "1.3.2", "version": "1.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/image-optimizer.git", "url": "https://github.com/spatie/image-optimizer.git",
"reference": "6aa170eb292758553d332efee5e0c3977341080c" "reference": "c22202fdd57856ed18a79cfab522653291a6e96a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/image-optimizer/zipball/6aa170eb292758553d332efee5e0c3977341080c", "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/c22202fdd57856ed18a79cfab522653291a6e96a",
"reference": "6aa170eb292758553d332efee5e0c3977341080c", "reference": "c22202fdd57856ed18a79cfab522653291a6e96a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5363,9 +5362,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/spatie/image-optimizer/issues", "issues": "https://github.com/spatie/image-optimizer/issues",
"source": "https://github.com/spatie/image-optimizer/tree/1.3.2" "source": "https://github.com/spatie/image-optimizer/tree/1.4.0"
}, },
"time": "2020-11-28T12:37:58+00:00" "time": "2021-04-22T06:17:27+00:00"
}, },
{ {
"name": "spatie/laravel-backup", "name": "spatie/laravel-backup",
@ -8287,16 +8286,16 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "brianium/paratest", "name": "brianium/paratest",
"version": "v6.2.0", "version": "v6.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/paratestphp/paratest.git", "url": "https://github.com/paratestphp/paratest.git",
"reference": "9a94366983ce32c7724fc92e3b544327d4adb9be" "reference": "268d5b2b4237c0abf76c4aa9633ad8580be01e1e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/9a94366983ce32c7724fc92e3b544327d4adb9be", "url": "https://api.github.com/repos/paratestphp/paratest/zipball/268d5b2b4237c0abf76c4aa9633ad8580be01e1e",
"reference": "9a94366983ce32c7724fc92e3b544327d4adb9be", "reference": "268d5b2b4237c0abf76c4aa9633ad8580be01e1e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8305,28 +8304,28 @@
"ext-reflection": "*", "ext-reflection": "*",
"ext-simplexml": "*", "ext-simplexml": "*",
"php": "^7.3 || ^8.0", "php": "^7.3 || ^8.0",
"phpunit/php-code-coverage": "^9.2.5", "phpunit/php-code-coverage": "^9.2.6",
"phpunit/php-file-iterator": "^3.0.5", "phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-timer": "^5.0.3", "phpunit/php-timer": "^5.0.3",
"phpunit/phpunit": "^9.5.1", "phpunit/phpunit": "^9.5.4",
"sebastian/environment": "^5.1.3", "sebastian/environment": "^5.1.3",
"symfony/console": "^4.4 || ^5.2", "symfony/console": "^4.4.21 || ^5.2.6",
"symfony/process": "^4.4 || ^5.2" "symfony/process": "^4.4.21 || ^5.2.4"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^8.2.0", "doctrine/coding-standard": "^9.0.0",
"ekino/phpstan-banned-code": "^0.3.1", "ekino/phpstan-banned-code": "^0.4.0",
"ergebnis/phpstan-rules": "^0.15.3", "ergebnis/phpstan-rules": "^0.15.3",
"ext-posix": "*", "ext-posix": "*",
"infection/infection": "^0.20.2", "infection/infection": "^0.21.5",
"phpstan/phpstan": "^0.12.70", "phpstan/phpstan": "^0.12.84",
"phpstan/phpstan-deprecation-rules": "^0.12.6", "phpstan/phpstan-deprecation-rules": "^0.12.6",
"phpstan/phpstan-phpunit": "^0.12.17", "phpstan/phpstan-phpunit": "^0.12.18",
"phpstan/phpstan-strict-rules": "^0.12.9", "phpstan/phpstan-strict-rules": "^0.12.9",
"squizlabs/php_codesniffer": "^3.5.8", "squizlabs/php_codesniffer": "^3.6.0",
"symfony/filesystem": "^5.2.2", "symfony/filesystem": "^5.2.6",
"thecodingmachine/phpstan-strict-rules": "^0.12.1", "thecodingmachine/phpstan-strict-rules": "^0.12.1",
"vimeo/psalm": "^4.4.1" "vimeo/psalm": "^4.7.1"
}, },
"bin": [ "bin": [
"bin/paratest" "bin/paratest"
@ -8347,8 +8346,12 @@
{ {
"name": "Brian Scaturro", "name": "Brian Scaturro",
"email": "scaturrob@gmail.com", "email": "scaturrob@gmail.com",
"homepage": "http://brianscaturro.com", "role": "Developer"
"role": "Lead" },
{
"name": "Filippo Tessarotto",
"email": "zoeslam@gmail.com",
"role": "Developer"
} }
], ],
"description": "Parallel testing for PHP", "description": "Parallel testing for PHP",
@ -8361,9 +8364,19 @@
], ],
"support": { "support": {
"issues": "https://github.com/paratestphp/paratest/issues", "issues": "https://github.com/paratestphp/paratest/issues",
"source": "https://github.com/paratestphp/paratest/tree/v6.2.0" "source": "https://github.com/paratestphp/paratest/tree/v6.3.0"
}, },
"time": "2021-01-29T15:25:31+00:00" "funding": [
{
"url": "https://github.com/sponsors/Slamdunk",
"type": "github"
},
{
"url": "https://paypal.me/filippotessarotto",
"type": "paypal"
}
],
"time": "2021-04-27T09:24:27+00:00"
}, },
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
@ -8501,16 +8514,16 @@
}, },
{ {
"name": "facade/ignition", "name": "facade/ignition",
"version": "2.8.3", "version": "2.8.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/facade/ignition.git", "url": "https://github.com/facade/ignition.git",
"reference": "a8201d51aae83addceaef9344592a3b068b5d64d" "reference": "87fb348dab0ae1a7c206c3e902a5a44ba541742f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/a8201d51aae83addceaef9344592a3b068b5d64d", "url": "https://api.github.com/repos/facade/ignition/zipball/87fb348dab0ae1a7c206c3e902a5a44ba541742f",
"reference": "a8201d51aae83addceaef9344592a3b068b5d64d", "reference": "87fb348dab0ae1a7c206c3e902a5a44ba541742f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8574,7 +8587,7 @@
"issues": "https://github.com/facade/ignition/issues", "issues": "https://github.com/facade/ignition/issues",
"source": "https://github.com/facade/ignition" "source": "https://github.com/facade/ignition"
}, },
"time": "2021-04-09T20:45:59+00:00" "time": "2021-04-29T13:55:26+00:00"
}, },
{ {
"name": "facade/ignition-contracts", "name": "facade/ignition-contracts",
@ -8631,16 +8644,16 @@
}, },
{ {
"name": "filp/whoops", "name": "filp/whoops",
"version": "2.12.0", "version": "2.12.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filp/whoops.git", "url": "https://github.com/filp/whoops.git",
"reference": "d501fd2658d55491a2295ff600ae5978eaad7403" "reference": "c13c0be93cff50f88bbd70827d993026821914dd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/d501fd2658d55491a2295ff600ae5978eaad7403", "url": "https://api.github.com/repos/filp/whoops/zipball/c13c0be93cff50f88bbd70827d993026821914dd",
"reference": "d501fd2658d55491a2295ff600ae5978eaad7403", "reference": "c13c0be93cff50f88bbd70827d993026821914dd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8690,7 +8703,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/filp/whoops/issues", "issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.12.0" "source": "https://github.com/filp/whoops/tree/2.12.1"
}, },
"funding": [ "funding": [
{ {
@ -8698,7 +8711,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2021-03-30T12:00:00+00:00" "time": "2021-04-25T12:00:00+00:00"
}, },
{ {
"name": "fzaninotto/faker", "name": "fzaninotto/faker",

View file

@ -2,7 +2,7 @@
return [ return [
'description' => env('INSTANCE_DESCRIPTION', null), 'description' => env('INSTANCE_DESCRIPTION', 'Pixelfed - Photo sharing for everyone'),
'contact' => [ 'contact' => [
'enabled' => env('INSTANCE_CONTACT_FORM', false), 'enabled' => env('INSTANCE_CONTACT_FORM', false),

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddActiveToStoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('stories', function (Blueprint $table) {
$table->boolean('active')->nullable()->index();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('stories', function (Blueprint $table) {
$table->dropColumn('active');
});
}
}

BIN
public/css/app.css vendored

Binary file not shown.

BIN
public/css/appdark.css vendored

Binary file not shown.

BIN
public/css/landing.css vendored

Binary file not shown.

BIN
public/js/activity.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/compose.js vendored

Binary file not shown.

BIN
public/js/direct.js vendored

Binary file not shown.

BIN
public/js/hashtag.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/profile.js vendored

Binary file not shown.

BIN
public/js/rempos.js vendored

Binary file not shown.

BIN
public/js/rempro.js vendored

Binary file not shown.

BIN
public/js/search.js vendored

Binary file not shown.

BIN
public/js/status.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/timeline.js vendored

Binary file not shown.

BIN
public/js/vendor.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -89,6 +89,7 @@
<a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a> <a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a>
<a v-if="!pageLoading && page == 3" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a> <a v-if="!pageLoading && page == 3" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
<a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a> <a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a>
<a v-if="!pageLoading && page == 'video-2'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
</span> </span>
</div> </div>
</div> </div>
@ -107,8 +108,10 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="page == 'textOptions'" class="w-100 h-100" style="min-height: 280px;"> <div v-if="page == 'textOptions'" class="w-100 h-100" style="min-height: 280px;">
</div> </div>
<div v-if="page == 'addText'" class="w-100 h-100" style="min-height: 280px;"> <div v-if="page == 'addText'" class="w-100 h-100" style="min-height: 280px;">
<div class="mt-2"> <div class="mt-2">
<div class="media px-3"> <div class="media px-3">
@ -606,6 +609,69 @@
</p> </p>
</div> </div>
<div v-if="page == 'video-2'" class="w-100 h-100">
<div v-if="video.title.length" class="border-bottom">
<div class="media p-3">
<img :src="media[0].url" width="100px" height="70px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']">
<div class="media-body">
<p class="font-weight-bold mb-1">{{video.title ? video.title.slice(0,70) : 'Untitled'}}</p>
<p class="mb-0 text-muted small">{{video.description ? video.description.slice(0,90) : 'No description'}}</p>
</div>
</div>
</div>
<div class="border-bottom d-flex justify-content-between px-4 mb-0 py-2 ">
<div>
<div class="text-dark ">Contains NSFW Media</div>
</div>
<div>
<div class="custom-control custom-switch" style="z-index: 9999;">
<input type="checkbox" class="custom-control-input" id="asnsfw" v-model="nsfw">
<label class="custom-control-label" for="asnsfw"></label>
</div>
</div>
</div>
<div class="border-bottom">
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showLicenseCard()">Add license</p>
</div>
<div class="border-bottom">
<p class="px-4 mb-0 py-2">
<span>Audience</span>
<span class="float-right">
<a href="#" @click.prevent="showVisibilityCard()" class="btn btn-outline-secondary btn-sm small mr-3 mt-n1 disabled" style="font-size:10px;padding:3px;text-transform: uppercase" disabled>{{visibilityTag}}</a>
<a href="#" @click.prevent="showVisibilityCard()" class="text-decoration-none"><i class="fas fa-chevron-right fa-lg text-lighter"></i></a>
</span>
</p>
</div>
<div class="p-3">
<!-- <div class="card card-body shadow-none border d-flex justify-content-center align-items-center mb-3 p-5">
<div class="d-flex align-items-center">
<p class="mb-0 text-center">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</p>
<p class="ml-3 mb-0 text-center font-weight-bold">
Processing video
</p>
</div>
</div> -->
<div class="form-group">
<p class="small font-weight-bold text-muted mb-0">Title</p>
<input class="form-control" v-model="video.title" placeholder="Add a good title">
<p class="help-text mb-0 small text-muted">{{video.title.length}}/70</p>
</div>
<div class="form-group mb-0">
<p class="small font-weight-bold text-muted mb-0">Description</p>
<textarea class="form-control" v-model="video.description" placeholder="Add an optional description" maxlength="5000" rows="5"></textarea>
<p class="help-text mb-0 small text-muted">{{video.description.length}}/5000</p>
</div>
</div>
</div>
</div> </div>
<!-- card-footers --> <!-- card-footers -->
@ -700,12 +766,16 @@ export default {
carouselCursor: 0, carouselCursor: 0,
uploading: false, uploading: false,
uploadProgress: 100, uploadProgress: 100,
composeType: false, mode: 'photo',
modes: [
'photo',
'video',
'plain'
],
page: 1, page: 1,
composeState: 'publish', composeState: 'publish',
visibility: 'public', visibility: 'public',
visibilityTag: 'Public', visibilityTag: 'Public',
nsfw: false,
place: false, place: false,
commentsDisabled: false, commentsDisabled: false,
optimizeMedia: true, optimizeMedia: true,
@ -810,15 +880,16 @@ export default {
name: "Attribution-NonCommercial-NoDerivs" name: "Attribution-NonCommercial-NoDerivs"
} }
], ],
licenseIndex: 0 licenseIndex: 0,
video: {
title: '',
description: ''
}
} }
}, },
beforeMount() { beforeMount() {
this.fetchProfile(); this.fetchProfile();
if(this.config.uploader.media_types.includes('video/mp4') == false) {
this.composeType = 'post'
}
this.filters = window.App.util.filters; this.filters = window.App.util.filters;
}, },
@ -860,6 +931,7 @@ export default {
this.pageTitle = 'New Text Post'; this.pageTitle = 'New Text Post';
this.page = 'addText'; this.page = 'addText';
this.textMode = true; this.textMode = true;
this.mode = 'text';
}, },
mediaWatcher() { mediaWatcher() {
@ -910,7 +982,14 @@ export default {
self.media.push(e.data); self.media.push(e.data);
self.uploading = false; self.uploading = false;
setTimeout(function() { setTimeout(function() {
self.page = 2; // if(type === 'video/mp4') {
// self.pageTitle = 'Edit Video Details';
// self.mode = 'video';
// self.page = 'video-2';
// } else {
// self.page = 2;
// }
self.page = 3;
}, 300); }, 300);
}).catch(function(e) { }).catch(function(e) {
switch(e.response.status) { switch(e.response.status) {
@ -1001,7 +1080,8 @@ export default {
place: this.place, place: this.place,
tagged: this.taggedUsernames, tagged: this.taggedUsernames,
optimize_media: this.optimizeMedia, optimize_media: this.optimizeMedia,
license: this.availableLicenses[this.licenseIndex].id license: this.availableLicenses[this.licenseIndex].id,
video: this.video
}; };
axios.post('/api/compose/v0/publish', data) axios.post('/api/compose/v0/publish', data)
.then(res => { .then(res => {
@ -1068,13 +1148,14 @@ export default {
}, },
closeModal() { closeModal() {
this.composeType = '';
$('#composeModal').modal('hide'); $('#composeModal').modal('hide');
}, },
goBack() { goBack() {
this.pageTitle = ''; this.pageTitle = '';
switch(this.mode) {
case 'photo':
switch(this.page) { switch(this.page) {
case 'addText': case 'addText':
this.page = 1; this.page = 1;
@ -1097,12 +1178,69 @@ export default {
this.page = 3; this.page = 3;
break; break;
case 'video-2':
this.page = 1;
break;
default: default:
this.namedPages.indexOf(this.page) != -1 ? this.namedPages.indexOf(this.page) != -1 ?
this.page = (this.textMode ? 'addText' : 3) : this.page = 3 : this.page--;
(this.textMode ? 'addText' : this.page--);
break; break;
} }
break;
case 'video':
switch(this.page) {
case 'licensePicker':
this.page = 'video-2';
break;
case 'video-2':
this.page = 'video-2';
break;
default:
this.page = 'video-2';
break;
}
break;
default:
switch(this.page) {
case 'addText':
this.page = 1;
break;
case 'textOptions':
this.page = 'addText';
break;
case 'cropPhoto':
case 'editMedia':
this.page = 2;
break;
case 'tagPeopleHelp':
this.showTagCard();
break;
case 'licensePicker':
this.page = 3;
break;
case 'video-2':
this.page = 1;
break;
default:
this.namedPages.indexOf(this.page) != -1 ?
this.page = (this.mode == 'text' ? 'addText' : 3) :
(this.mode == 'text' ? 'addText' : this.page--);
break;
}
break;
}
}, },
nextPage() { nextPage() {
@ -1199,8 +1337,22 @@ export default {
onSubmitLocation(result) { onSubmitLocation(result) {
this.place = result; this.place = result;
this.pageTitle = this.textMode ? 'New Text Post' : ''; switch(this.mode) {
this.page = (this.textMode ? 'addText' : 3); case 'photo':
this.pageTitle = '';
this.page = 3;
break;
case 'video':
this.pageTitle = 'Edit Video Details';
this.page = 'video-2';
break;
case 'text':
this.pageTitle = 'New Text Post';
this.page = 'addText';
break;
}
return; return;
}, },
@ -1227,8 +1379,23 @@ export default {
} }
this.visibility = state; this.visibility = state;
this.visibilityTag = tags[state]; this.visibilityTag = tags[state];
switch(this.mode) {
case 'photo':
this.pageTitle = ''; this.pageTitle = '';
this.page = this.textMode ? 'addText' : 3; this.page = 3;
break;
case 'video':
this.pageTitle = 'Edit Video Details';
this.page = 'video-2';
break;
case 'text':
this.pageTitle = 'New Text Post';
this.page = 'addText';
break;
}
}, },
showMediaDescriptionsCard() { showMediaDescriptionsCard() {
@ -1350,8 +1517,23 @@ export default {
toggleLicense(index) { toggleLicense(index) {
this.licenseIndex = index; this.licenseIndex = index;
switch(this.mode) {
case 'photo':
this.pageTitle = ''; this.pageTitle = '';
this.page = 3; this.page = 3;
break;
case 'video':
this.pageTitle = 'Edit Video Details';
this.page = 'video-2';
break;
case 'text':
this.pageTitle = 'New Text Post';
this.page = 'addText';
break;
}
}, },
} }
} }

View file

@ -1,60 +1,92 @@
<template> <template>
<div class="container mt-2 mt-md-5"> <div class="container mt-2 mt-md-5 bg-black">
<input type="file" id="pf-dz" name="media" class="d-none file-input" v-bind:accept="config.mimes"> <input type="file" id="pf-dz" name="media" class="d-none file-input" v-bind:accept="config.mimes">
<span class="fixed-top text-right m-3 cursor-pointer" @click="navigateTo()">
<i class="fas fa-times fa-lg text-white"></i>
</span>
<div v-if="loaded" class="row"> <div v-if="loaded" class="row">
<div class="col-12 col-md-6 offset-md-3"> <div class="col-12 col-md-6 offset-md-3 bg-dark rounded-lg">
<!-- LANDING --> <!-- 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 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"> <div class="text-center flex-fill pt-3">
<img src="/img/pixelfed-icon-grey.svg" width="60px" height="60px"> <p class="text-muted font-weight-light mb-1">
<p class="font-weight-bold lead text-lighter mt-1">Stories</p> <i class="fas fa-history fa-5x"></i>
<!-- <p v-if="loaded" class="font-weight-bold small text-uppercase text-muted"> </p>
<span>{{stories.length}} Active</span> <p class="text-muted font-weight-bold mb-0">STORIES</p>
<span class="px-2">|</span>
<span>30K Views</span>
</p> -->
</div> </div>
<div class="flex-fill py-4"> <div class="flex-fill py-4">
<div class="card w-100 shadow-none"> <div class="card w-100 shadow-none bg-transparent">
<div class="list-group"> <div class="list-group bg-transparent">
<!-- <a class="list-group-item text-center lead text-decoration-none text-dark" href="#">Camera</a> --> <!-- <a class="list-group-item text-center lead text-decoration-none text-dark" href="#">Camera</a> -->
<a class="list-group-item text-center lead text-decoration-none text-dark" href="#" @click.prevent="upload()">Add Photo</a> <a class="list-group-item bg-transparent lead text-decoration-none text-light font-weight-bold border-light" href="#" @click.prevent="upload()">
<a v-if="stories.length" class="list-group-item text-center lead text-decoration-none text-dark" href="#" @click.prevent="edit()">Edit</a> <i class="fas fa-plus-square mr-2"></i>
Add to Story
</a>
<a v-if="stories.length" class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="#" @click.prevent="edit()">
<i class="far fa-clone mr-2"></i>
My Story
</a>
<a v-if="stories.length" class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="#" @click.prevent="viewMyStory()">
<i class="fas fa-history mr-2"></i>
View My Story
</a>
<!-- <a v-if="stories.length" class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="#" @click.prevent="edit()">
<i class="fas fa-network-wired mr-1"></i>
Audience
</a> -->
<!-- <a v-if="stories.length" class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="#" @click.prevent="edit()">
<i class="far fa-chart-bar mr-2"></i>
Stats
</a> -->
<!-- <a class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="#" @click.prevent="edit()">
<i class="far fa-folder mr-2"></i>
Archived
</a> -->
<!-- <a class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="#" @click.prevent="edit()">
<i class="far fa-question-circle mr-2"></i>
Help
</a> -->
<a class="list-group-item bg-transparent lead text-decoration-none text-lighter font-weight-bold border-muted" href="/">
<i class="fas fa-arrow-left mr-2"></i>
Go back
</a>
<!-- <a class="list-group-item text-center lead text-decoration-none text-dark" href="#">Options</a> --> <!-- <a class="list-group-item text-center lead text-decoration-none text-dark" href="#">Options</a> -->
</div> </div>
</div> </div>
</div> </div>
<div class="text-center flex-fill"> <div class="text-center flex-fill">
<p class="text-lighter small text-uppercase"> <!-- <p class="text-lighter small text-uppercase">
<a href="/" class="text-muted font-weight-bold">Home</a> <a href="/" class="text-muted font-weight-bold">Home</a>
<span class="px-2 text-lighter">|</span> <span class="px-2 text-lighter">|</span>
<a href="/i/my/story" class="text-muted font-weight-bold">View My Story</a> <a href="/i/my/story" class="text-muted font-weight-bold">View My Story</a>
<span class="px-2 text-lighter">|</span> <span class="px-2 text-lighter">|</span>
<a href="/site/help" class="text-muted font-weight-bold">Help</a> <a href="/site/help" class="text-muted font-weight-bold">Help</a>
</p> </p> -->
</div> </div>
</div> </div>
<!-- CROP --> <!-- 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 v-if="page == 'crop'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center" style="height: 90vh;">
<div class="text-center pt-5 mb-3 d-flex justify-content-between align-items-center"> <div class="text-center py-3 d-flex justify-content-between align-items-center">
<div> <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> <button class="btn btn-outline-lighter btn-sm py-1 px-md-3" @click="deleteCurrentStory()"><i class="pr-2 fas fa-chevron-left fa-sm"></i> Delete</button>
</div> </div>
<div class="d-flex align-items-center"> <div class="">
<img class="d-inline-block mr-2" src="/img/pixelfed-icon-grey.svg" width="30px" height="30px"> <p class="text-muted font-weight-light mb-1">
<span class="font-weight-bold lead text-lighter">Stories</span> <i class="fas fa-history fa-5x"></i>
</p>
<p class="text-muted font-weight-bold mb-0">STORIES</p>
</div> </div>
<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> <button class="btn btn-primary btn-sm py-1 px-md-3" @click="performCrop()">Crop <i class="pl-2 fas fa-chevron-right fa-sm"></i></button>
</div> </div>
</div> </div>
<div class="flex-fill"> <div class="flex-fill">
<div class="card w-100 mt-3"> <div class="card w-100 mt-3">
<div class="card-body p-0"> <div class="card-body p-0">
<vue-cropper <vue-cropper
ref="cropper" ref="croppa"
:relativeZoom="cropper.zoom" :relativeZoom="cropper.zoom"
:aspectRatio="cropper.aspectRatio" :aspectRatio="cropper.aspectRatio"
:viewMode="cropper.viewMode" :viewMode="cropper.viewMode"
@ -66,20 +98,11 @@
</div> </div>
</div> </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> </div>
<!-- ERROR --> <!-- 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;"> <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="h3 mb-0 text-light">Oops!</p>
<p class="text-muted lead">An error occurred, please try again later.</p> <p class="text-muted lead">An error occurred, please try again later.</p>
<p class="text-muted mb-0"> <p class="text-muted mb-0">
<a class="btn btn-outline-secondary py-0 px-5 font-weight-bold" href="/">Go back</a> <a class="btn btn-outline-secondary py-0 px-5 font-weight-bold" href="/">Go back</a>
@ -88,30 +111,63 @@
<!-- UPLOADING --> <!-- UPLOADING -->
<div v-if="page == 'uploading'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;"> <div v-if="page == 'uploading'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;">
<p v-if="uploadProgress != 100" class="display-4 mb-0">Uploading {{uploadProgress}}%</p> <p v-if="uploadProgress != 100" class="display-4 mb-0 text-muted">Uploading {{uploadProgress}}%</p>
<p v-else class="display-4 mb-0">Publishing Story</p> <p v-else class="display-4 mb-0 text-muted">Processing ...</p>
</div> </div>
<div v-if="page == 'edit'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center" style="height: 90vh;"> <!-- CROPPING -->
<div class="text-center flex-fill mt-5 pt-5"> <div v-if="page == 'cropping'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;">
<img src="/img/pixelfed-icon-grey.svg" width="60px" height="60px"> <p class="display-4 mb-0 text-muted">Cropping ...</p>
<p class="font-weight-bold lead text-lighter mt-1">Stories</p>
</div> </div>
<div class="flex-fill py-5">
<div class="card w-100 shadow-none" style="max-height: 500px; overflow-y: auto"> <!-- PREVIEW -->
<div v-if="page == 'preview'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;">
<div>
<div class="form-group">
<label for="durationSlider" class="text-light lead font-weight-bold">Story Duration</label>
<input type="range" class="custom-range" min="3" max="10" id="durationSlider" v-model="duration">
<p class="help-text text-center">
<span class="text-light">{{duration}} seconds</span>
</p>
</div>
<hr class="my-3">
<a class="btn btn-primary btn-block px-5 font-weight-bold my-3" href="#" @click.prevent="shareStoryToFollowers()">
Share Story with followers
</a>
<a class="btn btn-outline-muted btn-block px-5 font-weight-bold" href="/" @click.prevent="deleteCurrentStory()">
Cancel
</a>
</div>
<!-- <a class="btn btn-outline-secondary btn-block px-5 font-weight-bold" href="#">
Share Story with everyone
</a> -->
</div>
<!-- EDIT -->
<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">
<p class="text-muted font-weight-light mb-1">
<i class="fas fa-history fa-5x"></i>
</p>
<p class="text-muted font-weight-bold mb-0">STORIES</p>
</div>
<div class="flex-fill py-4">
<p class="lead font-weight-bold text-lighter">My Stories</p>
<div class="card w-100 shadow-none bg-transparent" style="max-height: 50vh; overflow-y: scroll">
<div class="list-group"> <div class="list-group">
<div v-for="(story, index) in stories" class="list-group-item text-center text-dark" href="#"> <div v-for="(story, index) in stories" class="list-group-item bg-transparent text-center border-muted text-lighter" href="#">
<div class="media align-items-center"> <div class="media align-items-center">
<div class="mr-3 cursor-pointer" @click="showLightbox(story)"> <div class="mr-3 cursor-pointer" @click="showLightbox(story)">
<img :src="story.src" class="img-fluid" width="70px" height="70px"> <img :src="story.src" class="rounded-circle border" width="40px" height="40px" style="object-fit: cover;">
<p class="small text-muted text-center mb-0">(expand)</p>
</div> </div>
<div class="media-body"> <div class="media-body text-left">
<p class="mb-0">Expires</p> <p class="mb-0 text-muted font-weight-bold"><span>{{story.created_ago}} ago</span></p>
<p class="mb-0 text-muted small"><span>{{expiresTimestamp(story.expires_at)}}</span></p>
</div> </div>
<div class="float-right"> <div class="flex-grow-1 text-right">
<button @click="deleteStory(story, index)" class="btn btn-danger btn-sm font-weight-bold text-uppercase">Delete</button> <button @click="deleteStory(story, index)" class="btn btn-link btn-sm">
<i class="fas fa-trash-alt fa-lg text-muted"></i>
</button>
</div> </div>
</div> </div>
</div> </div>
@ -119,7 +175,7 @@
</div> </div>
</div> </div>
<div class="flex-fill text-center"> <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> <a class="btn btn-outline-secondary btn-block px-5 font-weight-bold" href="/i/stories/new" @click.prevent="goBack()">Go back</a>
</div> </div>
</div> </div>
</div> </div>
@ -130,18 +186,24 @@
hide-header hide-header
hide-footer hide-footer
centered centered
size="lg" size="md"
body-class="p-0" class="bg-transparent"
body-class="p-0 bg-transparent"
> >
<div v-if="lightboxMedia" class="w-100 h-100"> <div v-if="lightboxMedia" class="w-100 h-100 bg-transparent">
<img :src="lightboxMedia.url" style="max-height: 100%; max-width: 100%"> <img :src="lightboxMedia.url" style="max-height: 90vh; width: 100%; object-fit: contain;">
</div> </div>
</b-modal> </b-modal>
</div> </div>
</template> </template>
<style type="text/css" scoped> <style type="text/css">
.bg-black {
background-color: #262626;
}
#lightbox .modal-content {
background: transparent;
}
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
@ -182,12 +244,15 @@
zoom: null zoom: null
}, },
mediaUrl: null, mediaUrl: null,
mediaId: null,
stories: [], stories: [],
lightboxMedia: false, lightboxMedia: false,
duration: 3
}; };
}, },
mounted() { mounted() {
$('body').addClass('bg-black');
this.mediaWatcher(); this.mediaWatcher();
axios.get('/api/stories/v0/fetch/' + this.profileId) axios.get('/api/stories/v0/fetch/' + this.profileId)
.then(res => { .then(res => {
@ -241,19 +306,22 @@
} }
}; };
io.value = null;
axios.post('/api/stories/v0/add', form, xhrConfig) axios.post('/api/stories/v0/add', form, xhrConfig)
.then(function(e) { .then(function(e) {
self.uploadProgress = 100; self.uploadProgress = 100;
self.uploading = false; self.uploading = false;
window.location.href = '/i/my/story';
self.mediaUrl = e.data.media_url; self.mediaUrl = e.data.media_url;
self.mediaId = e.data.media_id;
self.page = e.data.media_type === 'video' ? 'preview' : 'crop';
// window.location.href = '/i/my/story';
}).catch(function(e) { }).catch(function(e) {
self.uploading = false; self.uploading = false;
io.value = null; io.value = null;
let msg = e.response.data.message ? e.response.data.message : 'Something went wrong.' let msg = e.response.data.message ? e.response.data.message : 'Something went wrong.'
swal('Oops!', msg, 'warning'); swal('Oops!', msg, 'warning');
self.page = 'error';
}); });
io.value = null;
self.uploadProgress = 0; self.uploadProgress = 0;
}); });
}, },
@ -286,7 +354,49 @@
window.location.href = '/i/stories/new'; window.location.href = '/i/stories/new';
} }
}); });
},
navigateTo(path = '/') {
window.location.href = path;
},
goBack() {
this.page = 'landing';
},
performCrop() {
this.page = 'cropping';
let data = this.$refs.croppa.getData();
axios.post('/api/stories/v0/crop', {
media_id: this.mediaId,
width: data.width,
height: data.height,
x: data.x,
y: data.y
}).then(res => {
this.page = 'preview';
});
},
deleteCurrentStory() {
let story = {
id: this.mediaId
};
this.deleteStory(story);
this.page = 'landing';
},
shareStoryToFollowers() {
axios.post('/api/stories/v0/publish', {
media_id: this.mediaId,
duration: this.duration
}).then(res => {
window.location.href = '/i/my/story?id=' + this.mediaId;
})
},
viewMyStory() {
window.location.href = '/i/my/story';
} }
} }
} }

View file

@ -230,6 +230,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::delete('v0/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440'); Route::delete('v0/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440');
Route::get('v0/me', 'StoryController@apiV1Me'); Route::get('v0/me', 'StoryController@apiV1Me');
Route::get('v0/item/{id}', 'StoryController@apiV1Item'); Route::get('v0/item/{id}', 'StoryController@apiV1Item');
Route::post('v0/crop', 'StoryController@cropPhoto');
Route::post('v0/publish', 'StoryController@publishStory');
}); });
}); });