* Added current title as value for input so that the current value remains stored by default

* Added parameter 'show_legal_notice_link' => (bool) config_cache('instance.has_legal_notice'),

* Added conditional display of a link to legal notice if the page is active

* Added key 'legalNotice'

* feat translate story

* translate auth

- register
- login

* add remove follow

* Update ApiV1Controller.php

Co-Authored-By: Mathieu <385764+Casmo@users.noreply.github.com>

* New translations web.php (Chinese Simplified)
[ci skip]

* Added current title as value for input so that the current value remains stored by default

* Added parameter 'show_legal_notice_link' => (bool) config_cache('instance.has_legal_notice'),

* Added conditional display of a link to legal notice if the page is active

* Added key 'legalNotice'

* add missing key

* add missing keys

* New translations web.php (Portuguese, Brazilian)
[ci skip]

* New translations web.php (Turkish)
[ci skip]

* New translations web.php (Italian)
[ci skip]

* translate custom  filter

* New translations web.php (Italian)
[ci skip]

* use configured alt text length limit when uploading multiple photos

* in notifications sidebar, show popover on shared posts too, not just liked posts

* use case insensitive search when tagging accounts

* New translations web.php (Portuguese, Brazilian)
[ci skip]

* Generic OIDC Support

* Everything should be configurable by env variables
* Basic request tests

* Fixes for items highlighted by review.ai

* Consider using `hash_equals()` instead of `==` when comparing the state values to prevent timing attacks:
`abort_unless(hash_equals($request->input('state'), $request->session()->pull('oauth2state')), 400, 'invalid
state');`
* For better data integrity, consider adding a foreign key constraint to the user_id column: `$table-
>foreign('user_id')->references('id')->on('users')->onDelete('cascade');`
* Does the OIDC provider guarantee that the username field exists in the userInfo data? Consider adding a
null check or fallback: `$userInfoData[config('remote-auth.oidc.field_username')] ?? null`

* field isnt accessTokenResourceOwnerId but responseResourceOwnerId

* New translations web.php (Dutch)
[ci skip]

* Fix components

* Update LandingService and Config util to properly support the legal_notice setting

* Update footer to use legalNotice i18n

* Update i18n

* Update sidebar with gap padding for footer links

* Update compiled assets

* Update i18n json

* Update OIDC config with comments, and disable tests as we dont have db tests configured

* Update remove_from_followers api endpoint

* Update i18n

* Update compiled assets

* Update changelog

* New supported formats, Preserve ICC Color Profiles, libvips support

Update image pipeline to handle avif, heic and webp and preserve ICC color profiles and added libvips support.

* Fix tests

* Update CHANGELOG.md

---------

Co-authored-by: Samy Elshamy <elshamy@coderbutze.de>
Co-authored-by: Felipe Mateus <eu@felipemateus.com>
Co-authored-by: Mathieu <385764+Casmo@users.noreply.github.com>
Co-authored-by: Mackenzie Morgan <macoafi@gmail.com>
Co-authored-by: Gavin Mogan <git@gavinmogan.com>
pull/5979/head^2
daniel 4 weeks ago committed by GitHub
parent 3d6348225b
commit 3861e7ddfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,6 +5,9 @@
### Added ### Added
- Pinned Posts ([2f655d000](https://github.com/pixelfed/pixelfed/commit/2f655d000)) - Pinned Posts ([2f655d000](https://github.com/pixelfed/pixelfed/commit/2f655d000))
- Custom Filters ([#5928](https://github.com/pixelfed/pixelfed/pull/5928)) ([437d742ac](https://github.com/pixelfed/pixelfed/commit/437d742ac)) - Custom Filters ([#5928](https://github.com/pixelfed/pixelfed/pull/5928)) ([437d742ac](https://github.com/pixelfed/pixelfed/commit/437d742ac))
- Legal Notice page ([#5606](https://github.com/pixelfed/pixelfed/pull/5606)) ([c72fa0529](https://github.com/pixelfed/pixelfed/commit/c72fa0529))
- OIDC Support ([#5608](https://github.com/pixelfed/pixelfed/pull/5608)) ([c72fa0529](https://github.com/pixelfed/pixelfed/commit/c72fa0529))
- Avif, HEIC, webp, libvips support + Preserve ICC color profiles ([ab9c13fe0](https://github.com/pixelfed/pixelfed/commit/ab9c13fe0))
### Updates ### Updates
- Update PublicApiController, use pixelfed entities for /api/pixelfed/v1/accounts/id/statuses with bookmarked state ([5ddb6d842](https://github.com/pixelfed/pixelfed/commit/5ddb6d842)) - Update PublicApiController, use pixelfed entities for /api/pixelfed/v1/accounts/id/statuses with bookmarked state ([5ddb6d842](https://github.com/pixelfed/pixelfed/commit/5ddb6d842))
@ -20,6 +23,10 @@
- Update report views, fix missing forms ([475d1d627](https://github.com/pixelfed/pixelfed/commit/475d1d627)) - Update report views, fix missing forms ([475d1d627](https://github.com/pixelfed/pixelfed/commit/475d1d627))
- Update private settings, change "Private Account" to "Manually Review Follow Requests" ([31dd1ab35](https://github.com/pixelfed/pixelfed/commit/31dd1ab35)) - Update private settings, change "Private Account" to "Manually Review Follow Requests" ([31dd1ab35](https://github.com/pixelfed/pixelfed/commit/31dd1ab35))
- Update ReportController, fix type validation ([ccc7f2fc6](https://github.com/pixelfed/pixelfed/commit/ccc7f2fc6)) - Update ReportController, fix type validation ([ccc7f2fc6](https://github.com/pixelfed/pixelfed/commit/ccc7f2fc6))
- Update footer to use legalNotice i18n ([0e59098da](https://github.com/pixelfed/pixelfed/commit/0e59098da))
- Update sidebar with gap padding for footer links ([dbd8289fe](https://github.com/pixelfed/pixelfed/commit/dbd8289fe))
- Update translations for Stories ([0a4dc7724](https://github.com/pixelfed/pixelfed/commit/0a4dc7724))
- Update translations for Auth ([756102696](https://github.com/pixelfed/pixelfed/commit/756102696))
- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.12.5 (2025-03-23)](https://github.com/pixelfed/pixelfed/compare/v0.12.5...dev) ## [v0.12.5 (2025-03-23)](https://github.com/pixelfed/pixelfed/compare/v0.12.5...dev)

@ -48,6 +48,7 @@ class CatchUnoptimizedMedia extends Command
->whereNotNull('status_id') ->whereNotNull('status_id')
->whereNotNull('media_path') ->whereNotNull('media_path')
->whereIn('mime', [ ->whereIn('mime', [
'image/jpg',
'image/jpeg', 'image/jpeg',
'image/png', 'image/png',
]) ])

@ -11,123 +11,124 @@ use App\Jobs\MediaPipeline\MediaFixLocalFilesystemCleanupPipeline;
class FixMediaDriver extends Command class FixMediaDriver extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'media:fix-nonlocal-driver'; protected $signature = 'media:fix-nonlocal-driver';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Fix filesystem when FILESYSTEM_DRIVER not set to local'; protected $description = 'Fix filesystem when FILESYSTEM_DRIVER not set to local';
/** /**
* Execute the console command. * Execute the console command.
* *
* @return int * @return int
*/ */
public function handle() public function handle()
{ {
if(config('filesystems.default') !== 'local') { if(config('filesystems.default') !== 'local') {
$this->error('Invalid default filesystem, set FILESYSTEM_DRIVER=local to proceed'); $this->error('Invalid default filesystem, set FILESYSTEM_DRIVER=local to proceed');
return Command::SUCCESS; return Command::SUCCESS;
} }
if((bool) config_cache('pixelfed.cloud_storage') == false) { if((bool) config_cache('pixelfed.cloud_storage') == false) {
$this->error('Cloud storage not enabled, exiting...'); $this->error('Cloud storage not enabled, exiting...');
return Command::SUCCESS; return Command::SUCCESS;
} }
$this->info(' ____ _ ______ __ '); $this->info(' ____ _ ______ __ ');
$this->info(' / __ \(_) _____ / / __/__ ____/ / '); $this->info(' / __ \(_) _____ / / __/__ ____/ / ');
$this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / '); $this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ');
$this->info(' / ____/ /> </ __/ / __/ __/ /_/ / '); $this->info(' / ____/ /> </ __/ / __/ __/ /_/ / ');
$this->info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ '); $this->info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ ');
$this->info(' '); $this->info(' ');
$this->info(' Media Filesystem Fix'); $this->info(' Media Filesystem Fix');
$this->info(' ====================='); $this->info(' =====================');
$this->info(' Fix media that was created when FILESYSTEM_DRIVER=local'); $this->info(' Fix media that was created when FILESYSTEM_DRIVER=local');
$this->info(' was not properly set. This command will fix media urls'); $this->info(' was not properly set. This command will fix media urls');
$this->info(' and optionally optimize/generate thumbnails when applicable,'); $this->info(' and optionally optimize/generate thumbnails when applicable,');
$this->info(' clean up temporary local media files and clear the app cache'); $this->info(' clean up temporary local media files and clear the app cache');
$this->info(' to fix media paths/urls.'); $this->info(' to fix media paths/urls.');
$this->info(' '); $this->info(' ');
$this->error(' Remember, FILESYSTEM_DRIVER=local must remain set or you will break things!'); $this->error(' Remember, FILESYSTEM_DRIVER=local must remain set or you will break things!');
if(!$this->confirm('Are you sure you want to perform this command?')) { if(!$this->confirm('Are you sure you want to perform this command?')) {
$this->info('Exiting...'); $this->info('Exiting...');
return Command::SUCCESS; return Command::SUCCESS;
} }
$optimize = $this->choice( $optimize = $this->choice(
'Do you want to optimize media and generate thumbnails? This will store s3 locally and re-upload optimized versions.', 'Do you want to optimize media and generate thumbnails? This will store s3 locally and re-upload optimized versions.',
['no', 'yes'], ['no', 'yes'],
1 1
); );
$cloud = Storage::disk(config('filesystems.cloud')); $cloud = Storage::disk(config('filesystems.cloud'));
$mountManager = new MountManager([ $mountManager = new MountManager([
's3' => $cloud->getDriver(), 's3' => $cloud->getDriver(),
'local' => Storage::disk('local')->getDriver(), 'local' => Storage::disk('local')->getDriver(),
]); ]);
$this->info('Fixing media, this may take a while...'); $this->info('Fixing media, this may take a while...');
$this->line(' '); $this->line(' ');
$bar = $this->output->createProgressBar(Media::whereNotNull('status_id')->whereNull('cdn_url')->count()); $bar = $this->output->createProgressBar(Media::whereNotNull('status_id')->whereNull('cdn_url')->count());
$bar->start(); $bar->start();
foreach(Media::whereNotNull('status_id')->whereNull('cdn_url')->lazyById(20) as $media) { foreach(Media::whereNotNull('status_id')->whereNull('cdn_url')->lazyById(20) as $media) {
if($cloud->exists($media->media_path)) { if($cloud->exists($media->media_path)) {
if($optimize === 'yes') { if($optimize === 'yes') {
$mountManager->copy( $mountManager->copy(
's3://' . $media->media_path, 's3://' . $media->media_path,
'local://' . $media->media_path 'local://' . $media->media_path
); );
sleep(1); sleep(1);
if(empty($media->original_sha256)) { if(empty($media->original_sha256)) {
$hash = \hash_file('sha256', Storage::disk('local')->path($media->media_path)); $hash = \hash_file('sha256', Storage::disk('local')->path($media->media_path));
$media->original_sha256 = $hash; $media->original_sha256 = $hash;
$media->save(); $media->save();
sleep(1); sleep(1);
} }
if( if(
$media->mime && $media->mime &&
in_array($media->mime, [ in_array($media->mime, [
'image/jpeg', 'image/jpg',
'image/png', 'image/jpeg',
'image/webp' 'image/png',
]) 'image/webp'
) { ])
ImageOptimize::dispatch($media); ) {
sleep(3); ImageOptimize::dispatch($media);
} sleep(3);
} else { }
$media->cdn_url = $cloud->url($media->media_path); } else {
$media->save(); $media->cdn_url = $cloud->url($media->media_path);
} $media->save();
} }
$bar->advance(); }
} $bar->advance();
}
$bar->finish();
$this->line(' '); $bar->finish();
$this->line(' '); $this->line(' ');
$this->line(' ');
$this->callSilently('cache:clear');
$this->callSilently('cache:clear');
$this->info('Successfully fixed media paths and cleared cached!');
$this->info('Successfully fixed media paths and cleared cached!');
if($optimize === 'yes') {
MediaFixLocalFilesystemCleanupPipeline::dispatch()->delay(now()->addMinutes(15))->onQueue('default'); if($optimize === 'yes') {
$this->line(' '); MediaFixLocalFilesystemCleanupPipeline::dispatch()->delay(now()->addMinutes(15))->onQueue('default');
$this->info('A cleanup job has been dispatched to delete media stored locally, it may take a few minutes to process!'); $this->line(' ');
} $this->info('A cleanup job has been dispatched to delete media stored locally, it may take a few minutes to process!');
}
$this->line(' ');
return Command::SUCCESS; $this->line(' ');
} return Command::SUCCESS;
}
} }

@ -110,7 +110,7 @@ class ImportEmojis extends Command
private function isEmoji($filename) private function isEmoji($filename)
{ {
$allowedMimeTypes = ['image/png', 'image/jpeg', 'image/webp']; $allowedMimeTypes = ['image/png', 'image/jpeg', 'image/webp', 'image/jpg'];
$mimeType = mime_content_type($filename); $mimeType = mime_content_type($filename);
return in_array($mimeType, $allowedMimeTypes); return in_array($mimeType, $allowedMimeTypes);

@ -40,7 +40,7 @@ class RegenerateThumbnails extends Command
public function handle() public function handle()
{ {
DB::transaction(function() { DB::transaction(function() {
Media::whereIn('mime', ['image/jpeg', 'image/png']) Media::whereIn('mime', ['image/jpeg', 'image/png', 'image/jpg'])
->chunk(50, function($medias) { ->chunk(50, function($medias) {
foreach($medias as $media) { foreach($medias as $media) {
\App\Jobs\ImageOptimizePipeline\ImageThumbnail::dispatch($media); \App\Jobs\ImageOptimizePipeline\ImageThumbnail::dispatch($media);

@ -243,7 +243,7 @@ class ApiV1Controller extends Controller
} }
$this->validate($request, [ $this->validate($request, [
'avatar' => 'sometimes|mimetypes:image/jpeg,image/png|max:'.config('pixelfed.max_avatar_size'), 'avatar' => 'sometimes|mimetypes:image/jpeg,image/jpg,image/png|max:'.config('pixelfed.max_avatar_size'),
'display_name' => 'nullable|string|max:30', 'display_name' => 'nullable|string|max:30',
'note' => 'nullable|string|max:200', 'note' => 'nullable|string|max:200',
'locked' => 'nullable', 'locked' => 'nullable',
@ -1907,6 +1907,7 @@ class ApiV1Controller extends Controller
$media->save(); $media->save();
switch ($media->mime) { switch ($media->mime) {
case 'image/jpg':
case 'image/jpeg': case 'image/jpeg':
case 'image/png': case 'image/png':
case 'image/webp': case 'image/webp':
@ -2137,6 +2138,7 @@ class ApiV1Controller extends Controller
$media->save(); $media->save();
switch ($media->mime) { switch ($media->mime) {
case 'image/jpg':
case 'image/jpeg': case 'image/jpeg':
case 'image/png': case 'image/png':
case 'image/webp': case 'image/webp':
@ -4563,6 +4565,46 @@ class ApiV1Controller extends Controller
); );
} }
public function accountRemoveFollowById(Request $request, $id)
{
abort_if(! $request->user(), 403);
$pid = $request->user()->profile_id;
if ($pid === $id) {
return $this->json(['error' => 'Request invalid! target_id is same user id.'], 500);
}
$exists = Follower::whereProfileId($id)
->whereFollowingId($pid)
->first();
abort_unless($exists, 404);
$exists->delete();
RelationshipService::refresh($pid, $id);
RelationshipService::refresh($pid, $id);
UnfollowPipeline::dispatch($id, $pid)->onQueue('high');
Cache::forget('profile:following:'.$id);
Cache::forget('profile:followers:'.$id);
Cache::forget('profile:following:'.$pid);
Cache::forget('profile:followers:'.$pid);
Cache::forget('api:local:exp:rec:'.$pid);
Cache::forget('user:account:id:'.$id);
Cache::forget('user:account:id:'.$pid);
Cache::forget('profile:follower_count:'.$id);
Cache::forget('profile:follower_count:'.$pid);
Cache::forget('profile:following_count:'.$id);
Cache::forget('profile:following_count:'.$pid);
AccountService::del($pid);
AccountService::del($id);
$res = RelationshipService::get($id, $pid);
return $this->json($res);
}
/** /**
* GET /api/v1/statuses/{id}/pin * GET /api/v1/statuses/{id}/pin
*/ */

@ -1307,6 +1307,7 @@ class ApiV1Dot1Controller extends Controller
$media->save(); $media->save();
switch ($media->mime) { switch ($media->mime) {
case 'image/jpg':
case 'image/jpeg': case 'image/jpeg':
case 'image/png': case 'image/png':
ImageOptimize::dispatch($media)->onQueue('mmo'); ImageOptimize::dispatch($media)->onQueue('mmo');

@ -310,6 +310,7 @@ class ApiV2Controller extends Controller
switch ($media->mime) { switch ($media->mime) {
case 'image/jpeg': case 'image/jpeg':
case 'image/jpg':
case 'image/png': case 'image/png':
ImageOptimize::dispatch($media)->onQueue('mmo'); ImageOptimize::dispatch($media)->onQueue('mmo');
break; break;

@ -268,10 +268,11 @@ class ComposeController extends Controller
$blocked->push($request->user()->profile_id); $blocked->push($request->user()->profile_id);
$operator = config('database.default') === 'pgsql' ? 'ilike' : 'like';
$results = Profile::select('id', 'domain', 'username') $results = Profile::select('id', 'domain', 'username')
->whereNotIn('id', $blocked) ->whereNotIn('id', $blocked)
->whereNull('domain') ->whereNull('domain')
->where('username', 'like', '%'.$q.'%') ->where('username', $operator, '%'.$q.'%')
->limit(15) ->limit(15)
->get() ->get()
->map(function ($r) { ->map(function ($r) {

@ -201,7 +201,7 @@ class ImportPostController extends Controller
$this->checkPermissions($request); $this->checkPermissions($request);
$allowedMimeTypes = ['image/png', 'image/jpeg']; $allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg'];
if (config('import.instagram.allow_image_webp') && str_contains(config_cache('pixelfed.media_types'), 'image/webp')) { if (config('import.instagram.allow_image_webp') && str_contains(config_cache('pixelfed.media_types'), 'image/webp')) {
$allowedMimeTypes[] = 'image/webp'; $allowedMimeTypes[] = 'image/webp';

@ -14,6 +14,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Rules\PixelfedUsername;
use InvalidArgumentException; use InvalidArgumentException;
use Purify; use Purify;
@ -359,37 +360,7 @@ class RemoteAuthController extends Controller
'required', 'required',
'min:2', 'min:2',
'max:30', 'max:30',
function ($attribute, $value, $fail) { new PixelfedUsername(),
$dash = substr_count($value, '-');
$underscore = substr_count($value, '_');
$period = substr_count($value, '.');
if (ends_with($value, ['.php', '.js', '.css'])) {
return $fail('Username is invalid.');
}
if (($dash + $underscore + $period) > 1) {
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
}
if (! ctype_alnum($value[0])) {
return $fail('Username is invalid. Must start with a letter or number.');
}
if (! ctype_alnum($value[strlen($value) - 1])) {
return $fail('Username is invalid. Must end with a letter or number.');
}
$val = str_replace(['_', '.', '-'], '', $value);
if (! ctype_alnum($val)) {
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
}
$restricted = RestrictedNames::get();
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
return $fail('Username cannot be used.');
}
},
], ],
]); ]);
$username = strtolower($request->input('username')); $username = strtolower($request->input('username'));

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers;
use App\Models\UserOidcMapping;
use Purify;
use App\Services\EmailService;
use App\Services\UserOidcService;
use App\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use App\Rules\EmailNotBanned;
use App\Rules\PixelfedUsername;
class RemoteOidcController extends Controller
{
protected $fractal;
public function start(UserOidcService $provider, Request $request)
{
abort_unless((bool) config('remote-auth.oidc.enabled'), 404);
if ($request->user()) {
return redirect('/');
}
$url = $provider->getAuthorizationUrl([
'scope' => $provider->getDefaultScopes(),
]);
$request->session()->put('oauth2state', $provider->getState());
return redirect($url);
}
public function handleCallback(UserOidcService $provider, Request $request)
{
abort_unless((bool) config('remote-auth.oidc.enabled'), 404);
if ($request->user()) {
return redirect('/');
}
abort_unless($request->input("state"), 400);
abort_unless($request->input("code"), 400);
abort_unless(hash_equals($request->session()->pull('oauth2state'), $request->input("state")), 400, "invalid state");
$accessToken = $provider->getAccessToken('authorization_code', [
'code' => $request->get('code')
]);
$userInfo = $provider->getResourceOwner($accessToken);
$userInfoId = $userInfo->getId();
$userInfoData = $userInfo->toArray();
$mappedUser = UserOidcMapping::where('oidc_id', $userInfoId)->first();
if ($mappedUser) {
$this->guarder()->login($mappedUser->user);
return redirect('/');
}
abort_if(EmailService::isBanned($userInfoData["email"]), 400, 'Banned email.');
$user = $this->createUser([
'username' => $userInfoData[config('remote-auth.oidc.field_username')],
'name' => $userInfoData["name"] ?? $userInfoData["display_name"] ?? $userInfoData[config('remote-auth.oidc.field_username')] ?? null,
'email' => $userInfoData["email"],
]);
UserOidcMapping::create([
'user_id' => $user->id,
'oidc_id' => $userInfoId,
]);
return redirect('/');
}
protected function createUser($data)
{
$this->validate(new Request($data), [
'email' => [
'required',
'string',
'email:strict,filter_unicode,dns,spoof',
'max:255',
'unique:users',
new EmailNotBanned(),
],
'username' => [
'required',
'min:2',
'max:30',
'unique:users,username',
new PixelfedUsername(),
],
'name' => 'nullable|max:30',
]);
event(new Registered($user = User::create([
'name' => Purify::clean($data['name']),
'username' => $data['username'],
'email' => $data['email'],
'password' => Hash::make(Str::password()),
'email_verified_at' => now(),
'app_register_ip' => request()->ip(),
'register_source' => 'oidc',
])));
$this->guarder()->login($user);
return $user;
}
protected function guarder()
{
return Auth::guard();
}
}

@ -260,7 +260,7 @@ class StoryApiV1Controller extends Controller
'file' => function () { 'file' => function () {
return [ return [
'required', 'required',
'mimetypes:image/jpeg,image/png,video/mp4', 'mimetypes:image/jpeg,image/jpg,image/png,video/mp4',
'max:'.config_cache('pixelfed.max_photo_size'), 'max:'.config_cache('pixelfed.max_photo_size'),
]; ];
}, },

@ -34,7 +34,7 @@ class StoryComposeController extends Controller
'file' => function () { 'file' => function () {
return [ return [
'required', 'required',
'mimetypes:image/jpeg,image/png,video/mp4', 'mimetypes:image/jpeg,image/png,video/mp4,image/jpg',
'max:'.config_cache('pixelfed.max_photo_size'), 'max:'.config_cache('pixelfed.max_photo_size'),
]; ];
}, },

@ -40,6 +40,9 @@ class ImageOptimize implements ShouldQueue
public function handle() public function handle()
{ {
$media = $this->media; $media = $this->media;
if(!$media) {
return;
}
$path = storage_path('app/'.$media->media_path); $path = storage_path('app/'.$media->media_path);
if (!is_file($path) || $media->skip_optimize) { if (!is_file($path) || $media->skip_optimize) {
return; return;

@ -64,7 +64,7 @@ class Media extends Model
return $this->cdn_url; return $this->cdn_url;
} }
if ($this->media_path && $this->mime && in_array($this->mime, ['image/jpeg', 'image/png'])) { if ($this->media_path && $this->mime && in_array($this->mime, ['image/jpeg', 'image/png', 'image/jpg'])) {
return $this->remote_media || Str::startsWith($this->media_path, 'http') ? return $this->remote_media || Str::startsWith($this->media_path, 'http') ?
$this->media_path : $this->media_path :
url(Storage::url($this->media_path)); url(Storage::url($this->media_path));

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class UserOidcMapping extends Model
{
use HasFactory;
public $timestamps = true;
protected $fillable = [
'user_id',
'oidc_id',
];
public function user()
{
return $this->belongsTo(User::class);
}
}

@ -21,6 +21,7 @@ use App\Observers\UserFilterObserver;
use App\Observers\UserObserver; use App\Observers\UserObserver;
use App\Profile; use App\Profile;
use App\Services\AccountService; use App\Services\AccountService;
use App\Services\UserOidcService;
use App\Status; use App\Status;
use App\StatusHashtag; use App\StatusHashtag;
use App\User; use App\User;
@ -112,6 +113,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register() public function register()
{ {
// $this->app->bind(UserOidcService::class, function() {
return UserOidcService::build();
});
} }
} }

@ -0,0 +1,25 @@
<?php
namespace App\Rules;
use Closure;
use App\Services\EmailService;
use Illuminate\Contracts\Validation\ValidationRule;
class EmailNotBanned implements ValidationRule
{
/**
* Run the validation rule.
*
* @param string $attribute
* @param mixed $value
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
* @return void
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (EmailService::isBanned($value)) {
$fail('Email is invalid.');
}
}
}

@ -0,0 +1,57 @@
<?php
namespace App\Rules;
use Closure;
use App\Util\Lexer\RestrictedNames;
use Illuminate\Contracts\Validation\ValidationRule;
class PixelfedUsername implements ValidationRule
{
/**
* Run the validation rule.
*
* @param string $attribute
* @param mixed $value
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
* @return void
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$dash = substr_count($value, '-');
$underscore = substr_count($value, '_');
$period = substr_count($value, '.');
if (ends_with($value, ['.php', '.js', '.css'])) {
$fail('Username is invalid.');
return;
}
if (($dash + $underscore + $period) > 1) {
$fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
return;
}
if (! ctype_alnum($value[0])) {
$fail('Username is invalid. Must start with a letter or number.');
return;
}
if (! ctype_alnum($value[strlen($value) - 1])) {
$fail('Username is invalid. Must end with a letter or number.');
return;
}
$val = str_replace(['_', '.', '-'], '', $value);
if (! ctype_alnum($val)) {
$fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
return;
}
$restricted = RestrictedNames::get();
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
$fail('Username cannot be used.');
return;
}
}
}

@ -52,6 +52,7 @@ class LandingService
'domain' => config('pixelfed.domain.app'), 'domain' => config('pixelfed.domain.app'),
'show_directory' => (bool) config_cache('instance.landing.show_directory'), 'show_directory' => (bool) config_cache('instance.landing.show_directory'),
'show_explore_feed' => (bool) config_cache('instance.landing.show_explore'), 'show_explore_feed' => (bool) config_cache('instance.landing.show_explore'),
'show_legal_notice_link' => (bool) config('instance.has_legal_notice'),
'open_registration' => (bool) $openReg, 'open_registration' => (bool) $openReg,
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'), 'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
'version' => config('pixelfed.version'), 'version' => config('pixelfed.version'),

@ -138,6 +138,7 @@ class MediaStorageService
} }
$mimes = [ $mimes = [
'image/jpg',
'image/jpeg', 'image/jpeg',
'image/png', 'image/png',
'video/mp4', 'video/mp4',
@ -166,6 +167,7 @@ class MediaStorageService
$ext = '.gif'; $ext = '.gif';
break; break;
case 'image/jpg':
case 'image/jpeg': case 'image/jpeg':
$ext = '.jpg'; $ext = '.jpg';
break; break;
@ -219,6 +221,7 @@ class MediaStorageService
$mimes = [ $mimes = [
'application/octet-stream', 'application/octet-stream',
'image/jpg',
'image/jpeg', 'image/jpeg',
'image/png', 'image/png',
]; ];
@ -249,7 +252,7 @@ class MediaStorageService
} }
$base = ($local ? 'public/cache/' : 'cache/').'avatars/'.$avatar->profile_id; $base = ($local ? 'public/cache/' : 'cache/').'avatars/'.$avatar->profile_id;
$ext = $head['mime'] == 'image/jpeg' ? 'jpg' : 'png'; $ext = ($head['mime'] == 'image/png') ? 'png' : 'jpg';
$path = 'avatar_'.strtolower(Str::random(random_int(3, 6))).'.'.$ext; $path = 'avatar_'.strtolower(Str::random(random_int(3, 6))).'.'.$ext;
$tmpBase = storage_path('app/remcache/'); $tmpBase = storage_path('app/remcache/');
$tmpPath = 'avatar_'.$avatar->profile_id.'-'.$path; $tmpPath = 'avatar_'.$avatar->profile_id.'-'.$path;
@ -262,7 +265,7 @@ class MediaStorageService
$mimeCheck = Storage::mimeType('remcache/'.$tmpPath); $mimeCheck = Storage::mimeType('remcache/'.$tmpPath);
if (! $mimeCheck || ! in_array($mimeCheck, ['image/png', 'image/jpeg'])) { if (! $mimeCheck || ! in_array($mimeCheck, ['image/png', 'image/jpeg', 'image/jpg'])) {
$avatar->last_fetched_at = now(); $avatar->last_fetched_at = now();
$avatar->save(); $avatar->save();
unlink($tmpName); unlink($tmpName);

@ -0,0 +1,21 @@
<?php
namespace App\Services;
use League\OAuth2\Client\Provider\GenericProvider;
class UserOidcService extends GenericProvider {
public static function build()
{
return new UserOidcService([
'clientId' => config('remote-auth.oidc.clientId'),
'clientSecret' => config('remote-auth.oidc.clientSecret'),
'redirectUri' => url('auth/oidc/callback'),
'urlAuthorize' => config('remote-auth.oidc.authorizeURL'),
'urlAccessToken' => config('remote-auth.oidc.tokenURL'),
'urlResourceOwnerDetails' => config('remote-auth.oidc.profileURL'),
'scopes' => config('remote-auth.oidc.scopes'),
'responseResourceOwnerId' => config('remote-auth.oidc.field_id'),
]);
}
}

@ -15,104 +15,104 @@ use Illuminate\Support\Str;
class Status extends Model class Status extends Model
{ {
use HasSnowflakePrimary, SoftDeletes; use HasSnowflakePrimary, SoftDeletes;
/** /**
* Indicates if the IDs are auto-incrementing. * Indicates if the IDs are auto-incrementing.
* *
* @var bool * @var bool
*/ */
public $incrementing = false; public $incrementing = false;
/** /**
* The attributes that should be mutated to dates. * The attributes that should be mutated to dates.
* *
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
'edited_at' => 'datetime' 'edited_at' => 'datetime'
]; ];
protected $guarded = []; protected $guarded = [];
const STATUS_TYPES = [ const STATUS_TYPES = [
'text', 'text',
'photo', 'photo',
'photo:album', 'photo:album',
'video', 'video',
'video:album', 'video:album',
'photo:video:album', 'photo:video:album',
'share', 'share',
'reply', 'reply',
'story', 'story',
'story:reply', 'story:reply',
'story:reaction', 'story:reaction',
'story:live', 'story:live',
'loop' 'loop'
]; ];
const MAX_MENTIONS = 20; const MAX_MENTIONS = 20;
const MAX_HASHTAGS = 60; const MAX_HASHTAGS = 60;
const MAX_LINKS = 5; const MAX_LINKS = 5;
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class); return $this->belongsTo(Profile::class);
} }
public function media() public function media()
{ {
return $this->hasMany(Media::class); return $this->hasMany(Media::class);
} }
public function firstMedia() public function firstMedia()
{ {
return $this->hasMany(Media::class)->orderBy('order', 'asc')->first(); return $this->hasMany(Media::class)->orderBy('order', 'asc')->first();
} }
public function viewType() public function viewType()
{ {
if($this->type) { if($this->type) {
return $this->type; return $this->type;
} }
return $this->setType(); return $this->setType();
} }
public function setType() public function setType()
{ {
if(in_array($this->type, self::STATUS_TYPES)) { if(in_array($this->type, self::STATUS_TYPES)) {
return $this->type; return $this->type;
} }
$mimes = $this->media->pluck('mime')->toArray(); $mimes = $this->media->pluck('mime')->toArray();
$type = StatusController::mimeTypeCheck($mimes); $type = StatusController::mimeTypeCheck($mimes);
if($type) { if($type) {
$this->type = $type; $this->type = $type;
$this->save(); $this->save();
return $type; return $type;
} }
} }
public function thumb($showNsfw = false) public function thumb($showNsfw = false)
{ {
$entity = StatusService::get($this->id, false); $entity = StatusService::get($this->id, false);
if(!$entity || !isset($entity['media_attachments']) || empty($entity['media_attachments'])) { if(!$entity || !isset($entity['media_attachments']) || empty($entity['media_attachments'])) {
return url(Storage::url('public/no-preview.png')); return url(Storage::url('public/no-preview.png'));
} }
if((!isset($entity['sensitive']) || $entity['sensitive']) && !$showNsfw) { if((!isset($entity['sensitive']) || $entity['sensitive']) && !$showNsfw) {
return url(Storage::url('public/no-preview.png')); return url(Storage::url('public/no-preview.png'));
} }
if(!isset($entity['visibility']) || !in_array($entity['visibility'], ['public', 'unlisted'])) { if(!isset($entity['visibility']) || !in_array($entity['visibility'], ['public', 'unlisted'])) {
return url(Storage::url('public/no-preview.png')); return url(Storage::url('public/no-preview.png'));
} }
return collect($entity['media_attachments']) return collect($entity['media_attachments'])
->filter(fn($media) => $media['type'] == 'image' && in_array($media['mime'], ['image/jpeg', 'image/png'])) ->filter(fn($media) => $media['type'] == 'image' && in_array($media['mime'], ['image/jpeg', 'image/png', 'image/jpg']))
->map(function($media) { ->map(function($media) {
if(!Str::endsWith($media['preview_url'], ['no-preview.png', 'no-preview.jpg'])) { if(!Str::endsWith($media['preview_url'], ['no-preview.png', 'no-preview.jpg'])) {
return $media['preview_url']; return $media['preview_url'];
@ -121,259 +121,259 @@ class Status extends Model
return $media['url']; return $media['url'];
}) })
->first() ?? url(Storage::url('public/no-preview.png')); ->first() ?? url(Storage::url('public/no-preview.png'));
} }
public function url($forceLocal = false) public function url($forceLocal = false)
{ {
if($this->uri) { if($this->uri) {
return $forceLocal ? "/i/web/post/_/{$this->profile_id}/{$this->id}" : $this->uri; return $forceLocal ? "/i/web/post/_/{$this->profile_id}/{$this->id}" : $this->uri;
} else { } else {
$id = $this->id; $id = $this->id;
$account = AccountService::get($this->profile_id, true); $account = AccountService::get($this->profile_id, true);
if(!$account || !isset($account['username'])) { if(!$account || !isset($account['username'])) {
return '/404'; return '/404';
} }
$path = url(config('app.url')."/p/{$account['username']}/{$id}"); $path = url(config('app.url')."/p/{$account['username']}/{$id}");
return $path; return $path;
} }
} }
public function permalink($suffix = '/activity') public function permalink($suffix = '/activity')
{ {
$id = $this->id; $id = $this->id;
$username = $this->profile->username; $username = $this->profile->username;
$path = config('app.url')."/p/{$username}/{$id}{$suffix}"; $path = config('app.url')."/p/{$username}/{$id}{$suffix}";
return url($path); return url($path);
} }
public function editUrl() public function editUrl()
{ {
return $this->url().'/edit'; return $this->url().'/edit';
} }
public function mediaUrl() public function mediaUrl()
{ {
$media = $this->firstMedia(); $media = $this->firstMedia();
$path = $media->media_path; $path = $media->media_path;
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at); $hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
$url = $media->cdn_url ? $media->cdn_url . "?v={$hash}" : url(Storage::url($path)."?v={$hash}"); $url = $media->cdn_url ? $media->cdn_url . "?v={$hash}" : url(Storage::url($path)."?v={$hash}");
return $url; return $url;
} }
public function likes() public function likes()
{ {
return $this->hasMany(Like::class); return $this->hasMany(Like::class);
} }
public function liked() : bool public function liked() : bool
{ {
if(!Auth::check()) { if(!Auth::check()) {
return false; return false;
} }
$pid = Auth::user()->profile_id; $pid = Auth::user()->profile_id;
return Like::select('status_id', 'profile_id') return Like::select('status_id', 'profile_id')
->whereStatusId($this->id) ->whereStatusId($this->id)
->whereProfileId($pid) ->whereProfileId($pid)
->exists(); ->exists();
} }
public function likedBy() public function likedBy()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Profile::class, Profile::class,
Like::class, Like::class,
'status_id', 'status_id',
'id', 'id',
'id', 'id',
'profile_id' 'profile_id'
); );
} }
public function comments() public function comments()
{ {
return $this->hasMany(self::class, 'in_reply_to_id'); return $this->hasMany(self::class, 'in_reply_to_id');
} }
public function bookmarked() public function bookmarked()
{ {
if (!Auth::check()) { if (!Auth::check()) {
return false; return false;
} }
$profile = Auth::user()->profile; $profile = Auth::user()->profile;
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count(); return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
} }
public function shares() public function shares()
{ {
return $this->hasMany(self::class, 'reblog_of_id'); return $this->hasMany(self::class, 'reblog_of_id');
} }
public function shared() : bool public function shared() : bool
{ {
if(!Auth::check()) { if(!Auth::check()) {
return false; return false;
} }
$pid = Auth::user()->profile_id; $pid = Auth::user()->profile_id;
return $this->select('profile_id', 'reblog_of_id') return $this->select('profile_id', 'reblog_of_id')
->whereProfileId($pid) ->whereProfileId($pid)
->whereReblogOfId($this->id) ->whereReblogOfId($this->id)
->exists(); ->exists();
} }
public function sharedBy() public function sharedBy()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Profile::class, Profile::class,
Status::class, Status::class,
'reblog_of_id', 'reblog_of_id',
'id', 'id',
'id', 'id',
'profile_id' 'profile_id'
); );
} }
public function parent() public function parent()
{ {
$parent = $this->in_reply_to_id ?? $this->reblog_of_id; $parent = $this->in_reply_to_id ?? $this->reblog_of_id;
if (!empty($parent)) { if (!empty($parent)) {
return $this->findOrFail($parent); return $this->findOrFail($parent);
} else { } else {
return false; return false;
} }
} }
public function conversation() public function conversation()
{ {
return $this->hasOne(Conversation::class); return $this->hasOne(Conversation::class);
} }
public function hashtags() public function hashtags()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Hashtag::class, Hashtag::class,
StatusHashtag::class, StatusHashtag::class,
'status_id', 'status_id',
'id', 'id',
'id', 'id',
'hashtag_id' 'hashtag_id'
); );
} }
public function mentions() public function mentions()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(
Profile::class, Profile::class,
Mention::class, Mention::class,
'status_id', 'status_id',
'id', 'id',
'id', 'id',
'profile_id' 'profile_id'
); );
} }
public function reportUrl() public function reportUrl()
{ {
return route('report.form')."?type=post&id={$this->id}"; return route('report.form')."?type=post&id={$this->id}";
} }
public function toActivityStream() public function toActivityStream()
{ {
$media = $this->media; $media = $this->media;
$mediaCollection = []; $mediaCollection = [];
foreach ($media as $image) { foreach ($media as $image) {
$mediaCollection[] = [ $mediaCollection[] = [
'type' => 'Link', 'type' => 'Link',
'href' => $image->url(), 'href' => $image->url(),
'mediaType' => $image->mime, 'mediaType' => $image->mime,
]; ];
} }
$obj = [ $obj = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Image', 'type' => 'Image',
'name' => null, 'name' => null,
'url' => $mediaCollection, 'url' => $mediaCollection,
]; ];
return $obj; return $obj;
} }
public function recentComments() public function recentComments()
{ {
return $this->comments()->orderBy('created_at', 'desc')->take(3); return $this->comments()->orderBy('created_at', 'desc')->take(3);
} }
public function scopeToAudience($audience) public function scopeToAudience($audience)
{ {
if(!in_array($audience, ['to', 'cc']) || $this->local == false) { if(!in_array($audience, ['to', 'cc']) || $this->local == false) {
return; return;
} }
$res = []; $res = [];
$res['to'] = []; $res['to'] = [];
$res['cc'] = []; $res['cc'] = [];
$scope = $this->scope; $scope = $this->scope;
$mentions = $this->mentions->map(function ($mention) { $mentions = $this->mentions->map(function ($mention) {
return $mention->permalink(); return $mention->permalink();
})->toArray(); })->toArray();
if($this->in_reply_to_id != null) { if($this->in_reply_to_id != null) {
$parent = $this->parent(); $parent = $this->parent();
if($parent) { if($parent) {
$mentions = array_merge([$parent->profile->permalink()], $mentions); $mentions = array_merge([$parent->profile->permalink()], $mentions);
} }
} }
switch ($scope) { switch ($scope) {
case 'public': case 'public':
$res['to'] = [ $res['to'] = [
"https://www.w3.org/ns/activitystreams#Public" "https://www.w3.org/ns/activitystreams#Public"
]; ];
$res['cc'] = array_merge([$this->profile->permalink('/followers')], $mentions); $res['cc'] = array_merge([$this->profile->permalink('/followers')], $mentions);
break; break;
case 'unlisted': case 'unlisted':
$res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions); $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions);
$res['cc'] = [ $res['cc'] = [
"https://www.w3.org/ns/activitystreams#Public" "https://www.w3.org/ns/activitystreams#Public"
]; ];
break; break;
case 'private': case 'private':
$res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions); $res['to'] = array_merge([$this->profile->permalink('/followers')], $mentions);
$res['cc'] = []; $res['cc'] = [];
break; break;
// TODO: Update scope when DMs are supported // TODO: Update scope when DMs are supported
case 'direct': case 'direct':
$res['to'] = []; $res['to'] = [];
$res['cc'] = []; $res['cc'] = [];
break; break;
} }
return $res[$audience]; return $res[$audience];
} }
public function place() public function place()
{ {
return $this->belongsTo(Place::class); return $this->belongsTo(Place::class);
} }
public function directMessage() public function directMessage()
{ {
return $this->hasOne(DirectMessage::class); return $this->hasOne(DirectMessage::class);
} }
public function poll() public function poll()
{ {
return $this->hasOne(Poll::class); return $this->hasOne(Poll::class);
} }
public function edits() public function edits()
{ {
return $this->hasMany(StatusEdit::class); return $this->hasMany(StatusEdit::class);
} }
} }

@ -175,7 +175,7 @@ class Helpers
return false; return false;
} }
if (! self::passesSecurityChecks($host, $disableDNSCheck, $forceBanCheck)) { if (!$disableDNSCheck && ! self::passesSecurityChecks($host, $disableDNSCheck, $forceBanCheck)) {
return false; return false;
} }

@ -7,53 +7,52 @@ use App\Media;
class Blurhash { class Blurhash {
const DEFAULT_HASH = 'U4Rfzst8?bt7ogayj[j[~pfQ9Goe%Mj[WBay'; const DEFAULT_HASH = 'U4Rfzst8?bt7ogayj[j[~pfQ9Goe%Mj[WBay';
public static function generate(Media $media) public static function generate(Media $media)
{ {
if(!in_array($media->mime, ['image/png', 'image/jpeg', 'video/mp4'])) { if(!in_array($media->mime, ['image/png', 'image/jpeg', 'image/jpg', 'video/mp4'])) {
return self::DEFAULT_HASH; return self::DEFAULT_HASH;
} }
if($media->thumbnail_path == null) { if($media->thumbnail_path == null) {
return self::DEFAULT_HASH; return self::DEFAULT_HASH;
} }
$file = storage_path('app/' . $media->thumbnail_path); $file = storage_path('app/' . $media->thumbnail_path);
if(!is_file($file)) { if(!is_file($file)) {
return self::DEFAULT_HASH; return self::DEFAULT_HASH;
} }
$image = imagecreatefromstring(file_get_contents($file)); $image = imagecreatefromstring(file_get_contents($file));
if(!$image) { if(!$image) {
return self::DEFAULT_HASH; return self::DEFAULT_HASH;
} }
$width = imagesx($image); $width = imagesx($image);
$height = imagesy($image); $height = imagesy($image);
$pixels = []; $pixels = [];
for ($y = 0; $y < $height; ++$y) { for ($y = 0; $y < $height; ++$y) {
$row = []; $row = [];
for ($x = 0; $x < $width; ++$x) { for ($x = 0; $x < $width; ++$x) {
$index = imagecolorat($image, $x, $y); $index = imagecolorat($image, $x, $y);
$colors = imagecolorsforindex($image, $index); $colors = imagecolorsforindex($image, $index);
$row[] = [$colors['red'], $colors['green'], $colors['blue']]; $row[] = [$colors['red'], $colors['green'], $colors['blue']];
} }
$pixels[] = $row; $pixels[] = $row;
} }
// Free the allocated GdImage object from memory: imagedestroy($image);
imagedestroy($image);
$components_x = 4;
$components_x = 4; $components_y = 4;
$components_y = 4; $blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y);
$blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y); if(strlen($blurhash) > 191) {
if(strlen($blurhash) > 191) { return self::DEFAULT_HASH;
return self::DEFAULT_HASH; }
} return $blurhash;
return $blurhash; }
}
} }

@ -3,223 +3,281 @@
namespace App\Util\Media; namespace App\Util\Media;
use App\Media; use App\Media;
use Image as Intervention; use Intervention\Image\ImageManager;
use Intervention\Image\Encoders\JpegEncoder;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Encoders\AvifEncoder;
use Intervention\Image\Encoders\PngEncoder;
use Cache, Log, Storage; use Cache, Log, Storage;
use App\Util\Media\Blurhash;
class Image class Image
{ {
public $square; public $square;
public $landscape; public $landscape;
public $portrait; public $portrait;
public $thumbnail; public $thumbnail;
public $orientation; public $orientation;
public $acceptedMimes = [ public $acceptedMimes = [
'image/png', 'image/png',
'image/jpeg', 'image/jpeg',
'image/webp', 'image/jpg',
'image/avif', 'image/webp',
]; 'image/avif',
'image/heic',
public function __construct() ];
{
ini_set('memory_limit', config('pixelfed.memory_limit', '1024M')); protected $imageManager;
$this->square = $this->orientations()['square']; public function __construct()
$this->landscape = $this->orientations()['landscape']; {
$this->portrait = $this->orientations()['portrait']; ini_set('memory_limit', config('pixelfed.memory_limit', '1024M'));
$this->thumbnail = [
'width' => 640, $this->square = $this->orientations()['square'];
'height' => 640, $this->landscape = $this->orientations()['landscape'];
]; $this->portrait = $this->orientations()['portrait'];
$this->orientation = null; $this->thumbnail = [
} 'width' => 640,
'height' => 640,
public function orientations() ];
{ $this->orientation = null;
return [
'square' => [ $driver = match(config('image.driver')) {
'width' => 1080, 'imagick' => new \Intervention\Image\Drivers\Imagick\Driver(),
'height' => 1080, 'vips' => new \Intervention\Image\Drivers\Vips\Driver(),
], default => new \Intervention\Image\Drivers\Gd\Driver()
'landscape' => [ };
'width' => 1920,
'height' => 1080, $this->imageManager = new ImageManager(
], $driver,
'portrait' => [ autoOrientation: true,
'width' => 1080, decodeAnimation: true,
'height' => 1350, blendingColor: 'ffffff',
], strip: true
]; );
} }
public function getAspectRatio($mediaPath, $thumbnail = false) public function orientations()
{ {
if (!is_file($mediaPath)) { return [
throw new \Exception('Invalid Media Path'); 'square' => [
} 'width' => 1080,
if ($thumbnail) { 'height' => 1080,
return [ ],
'dimensions' => $this->thumbnail, 'landscape' => [
'orientation' => 'thumbnail', 'width' => 1920,
]; 'height' => 1080,
} ],
'portrait' => [
list($width, $height) = getimagesize($mediaPath); 'width' => 1080,
$aspect = $width / $height; 'height' => 1350,
$orientation = $aspect === 1 ? 'square' : ],
($aspect > 1 ? 'landscape' : 'portrait'); ];
$this->orientation = $orientation; }
return [ public function getAspectRatio($mediaPath, $thumbnail = false)
'dimensions' => $this->orientations()[$orientation], {
'orientation' => $orientation, if ($thumbnail) {
'width_original' => $width, return [
'height_original' => $height, 'dimensions' => $this->thumbnail,
]; 'orientation' => 'thumbnail',
} ];
}
public function resizeImage(Media $media)
{ if (!is_file($mediaPath)) {
$basePath = storage_path('app/'.$media->media_path); throw new \Exception('Invalid Media Path');
}
$this->handleResizeImage($media);
} list($width, $height) = getimagesize($mediaPath);
$aspect = $width / $height;
public function resizeThumbnail(Media $media) $orientation = $aspect === 1 ? 'square' :
{ ($aspect > 1 ? 'landscape' : 'portrait');
$basePath = storage_path('app/'.$media->media_path); $this->orientation = $orientation;
$this->handleThumbnailImage($media); return [
} 'dimensions' => $this->orientations()[$orientation],
'orientation' => $orientation,
public function handleResizeImage(Media $media) 'width_original' => $width,
{ 'height_original' => $height,
$this->handleImageTransform($media, false); ];
} }
public function handleThumbnailImage(Media $media) public function resizeImage(Media $media)
{ {
$this->handleImageTransform($media, true); $this->handleResizeImage($media);
} }
public function handleImageTransform(Media $media, $thumbnail = false) public function resizeThumbnail(Media $media)
{ {
$path = $media->media_path; $this->handleThumbnailImage($media);
$file = storage_path('app/'.$path); }
if (!in_array($media->mime, $this->acceptedMimes)) {
return; public function handleResizeImage(Media $media)
} {
$ratio = $this->getAspectRatio($file, $thumbnail); $this->handleImageTransform($media, false);
$aspect = $ratio['dimensions']; }
$orientation = $ratio['orientation'];
public function handleThumbnailImage(Media $media)
try { {
$img = Intervention::make($file); $this->handleImageTransform($media, true);
$metadata = $img->exif(); }
$img->orientate();
if($thumbnail) { public function handleImageTransform(Media $media, $thumbnail = false)
$img->resize($aspect['width'], $aspect['height'], function ($constraint) { {
$constraint->aspectRatio(); $path = $media->media_path;
}); $file = storage_path('app/'.$path);
} else { if (!in_array($media->mime, $this->acceptedMimes)) {
if(config('media.exif.database', false) == true && $metadata) { return;
$meta = []; }
$keys = [ $ratio = $this->getAspectRatio($file, $thumbnail);
"COMPUTED", $aspect = $ratio['dimensions'];
"FileName", $orientation = $ratio['orientation'];
"FileSize",
"FileType", try {
"Make", $fileInfo = pathinfo($file);
"Model", $extension = strtolower($fileInfo['extension'] ?? 'jpg');
"MimeType",
"ColorSpace", $metadata = null;
"ExifVersion", if (!$thumbnail && config('media.exif.database', false) == true) {
"Orientation", try {
"UserComment", $exif = @exif_read_data($file);
"XResolution", if ($exif) {
"YResolution", $meta = [];
"FileDateTime", $keys = [
"SectionsFound", "FileName",
"ExifImageWidth", "FileSize",
"ResolutionUnit", "FileType",
"ExifImageLength", "Make",
"FlashPixVersion", "Model",
"Exif_IFD_Pointer", "MimeType",
"YCbCrPositioning", "ColorSpace",
"ComponentsConfiguration", "ExifVersion",
"ExposureTime", "Orientation",
"FNumber", "UserComment",
"ISOSpeedRatings", "XResolution",
"ShutterSpeedValue" "YResolution",
]; "FileDateTime",
foreach ($metadata as $k => $v) { "SectionsFound",
if(in_array($k, $keys)) { "ExifImageWidth",
$meta[$k] = $v; "ResolutionUnit",
} "ExifImageLength",
} "FlashPixVersion",
$media->metadata = json_encode($meta); "Exif_IFD_Pointer",
} "YCbCrPositioning",
"ComponentsConfiguration",
if ( "ExposureTime",
($ratio['width_original'] > $aspect['width']) "FNumber",
|| ($ratio['height_original'] > $aspect['height']) "ISOSpeedRatings",
) { "ShutterSpeedValue"
$img->resize($aspect['width'], $aspect['height'], function ($constraint) { ];
$constraint->aspectRatio(); foreach ($exif as $k => $v) {
}); if (in_array($k, $keys)) {
} $meta[$k] = $v;
} }
$converted = $this->setBaseName($path, $thumbnail, $img->extension); }
$newPath = storage_path('app/'.$converted['path']); $media->metadata = json_encode($meta);
}
$quality = config_cache('pixelfed.image_quality'); } catch (\Exception $e) {
$img->save($newPath, $quality); Log::info('EXIF extraction failed: ' . $e->getMessage());
}
if ($thumbnail == true) { }
$media->thumbnail_path = $converted['path'];
$media->thumbnail_url = url(Storage::url($converted['path'])); $img = $this->imageManager->read($file);
} else {
$media->width = $img->width(); if ($thumbnail) {
$media->height = $img->height(); $img = $img->coverDown(
$media->orientation = $orientation; $aspect['width'],
$media->media_path = $converted['path']; $aspect['height']
$media->mime = $img->mime; );
} } else {
if (
$img->destroy(); ($ratio['width_original'] > $aspect['width'])
$media->save(); || ($ratio['height_original'] > $aspect['height'])
) {
if($thumbnail) { $img = $img->scaleDown(
$this->generateBlurhash($media); $aspect['width'],
} $aspect['height']
);
Cache::forget('status:transformer:media:attachments:'.$media->status_id); }
Cache::forget('status:thumb:'.$media->status_id); }
} catch (Exception $e) { $converted = $this->setBaseName($path, $thumbnail, $extension);
$media->processed_at = now(); $newPath = storage_path('app/'.$converted['path']);
$media->save();
Log::info('MediaResizeException: Could not process media id: ' . $media->id); $quality = config_cache('pixelfed.image_quality');
}
} $encoder = null;
switch ($extension) {
public function setBaseName($basePath, $thumbnail, $extension) case 'jpeg':
{ case 'jpg':
$png = false; $encoder = new JpegEncoder($quality);
$path = explode('.', $basePath); break;
$name = ($thumbnail == true) ? $path[0].'_thumb' : $path[0]; case 'png':
$ext = last($path); $encoder = new PngEncoder();
$basePath = "{$name}.{$ext}"; break;
case 'webp':
return ['path' => $basePath, 'png' => $png]; $encoder = new WebpEncoder($quality);
} break;
case 'avif':
protected function generateBlurhash($media) $encoder = new AvifEncoder($quality);
{ break;
$blurhash = Blurhash::generate($media); case 'heic':
if($blurhash) { $encoder = new JpegEncoder($quality);
$media->blurhash = $blurhash; $extension = 'jpg';
$media->save(); break;
} default:
} $encoder = new JpegEncoder($quality);
$extension = 'jpg';
}
$encoded = $encoder->encode($img);
file_put_contents($newPath, $encoded->toString());
if ($thumbnail == true) {
$media->thumbnail_path = $converted['path'];
$media->thumbnail_url = url(Storage::url($converted['path']));
} else {
$media->width = $img->width();
$media->height = $img->height();
$media->orientation = $orientation;
$media->media_path = $converted['path'];
$media->mime = 'image/' . $extension;
}
$media->save();
if ($thumbnail) {
$this->generateBlurhash($media);
}
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
Cache::forget('status:thumb:'.$media->status_id);
} catch (\Exception $e) {
$media->processed_at = now();
$media->save();
Log::info('MediaResizeException: ' . $e->getMessage() . ' | Could not process media id: ' . $media->id);
}
}
public function setBaseName($basePath, $thumbnail, $extension)
{
$png = false;
$path = explode('.', $basePath);
$name = ($thumbnail == true) ? $path[0].'_thumb' : $path[0];
$ext = last($path);
$basePath = "{$name}.{$ext}";
return ['path' => $basePath, 'png' => $png];
}
protected function generateBlurhash($media)
{
$blurhash = Blurhash::generate($media);
if ($blurhash) {
$media->blurhash = $blurhash;
$media->save();
}
}
} }

@ -29,6 +29,7 @@ class Config
return [ return [
'version' => config('pixelfed.version'), 'version' => config('pixelfed.version'),
'open_registration' => (bool) config_cache('pixelfed.open_registration'), 'open_registration' => (bool) config_cache('pixelfed.open_registration'),
'show_legal_notice_link' => (bool) config('instance.has_legal_notice'),
'uploader' => [ 'uploader' => [
'max_photo_size' => (int) config_cache('pixelfed.max_photo_size'), 'max_photo_size' => (int) config_cache('pixelfed.max_photo_size'),
'max_caption_length' => (int) config_cache('pixelfed.max_caption_length'), 'max_caption_length' => (int) config_cache('pixelfed.max_caption_length'),

@ -18,7 +18,7 @@
"buzz/laravel-h-captcha": "^1.0.4", "buzz/laravel-h-captcha": "^1.0.4",
"doctrine/dbal": "^3.0", "doctrine/dbal": "^3.0",
"endroid/qr-code": "^6.0", "endroid/qr-code": "^6.0",
"intervention/image": "^2.4", "intervention/image": "^3.11.2",
"jenssegers/agent": "^2.6", "jenssegers/agent": "^2.6",
"laravel-notification-channels/expo": "^2.0.0", "laravel-notification-channels/expo": "^2.0.0",
"laravel-notification-channels/webpush": "^10.2", "laravel-notification-channels/webpush": "^10.2",
@ -31,6 +31,7 @@
"laravel/ui": "^4.2", "laravel/ui": "^4.2",
"league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-aws-s3-v3": "^3.0",
"league/iso3166": "^2.1|^4.0", "league/iso3166": "^2.1|^4.0",
"league/oauth2-client": "^2.8",
"league/uri": "^7.4", "league/uri": "^7.4",
"pbmedia/laravel-ffmpeg": "^8.0", "pbmedia/laravel-ffmpeg": "^8.0",
"phpseclib/phpseclib": "~2.0", "phpseclib/phpseclib": "~2.0",

937
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -7,14 +7,12 @@ return [
| Image Driver | Image Driver
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Intervention Image supports "GD Library" and "Imagick" to process images | Intervention Image supports "GD Library", "Imagick" and "libvips" to process
| internally. You may choose one of them according to your PHP | images internally. You may choose one of them according to your PHP
| configuration. By default PHP's "GD Library" implementation is used. | configuration. By default PHP's "GD Library" implementation is used.
| |
| Supported: "gd", "imagick" | Supported: "gd", "imagick", "libvips"
| |
*/ */
'driver' => env('IMAGE_DRIVER', 'gd'), 'driver' => env('IMAGE_DRIVER', 'gd'),
]; ];

@ -54,4 +54,79 @@ return [
'limit' => env('PF_LOGIN_WITH_MASTODON_MAX_USES_LIMIT', 3) 'limit' => env('PF_LOGIN_WITH_MASTODON_MAX_USES_LIMIT', 3)
] ]
], ],
'oidc' => [
/*
* Enable OIDC authentication
*
* Enable Sign-in with OpenID Connect (OIDC) authentication providers
*/
'enabled' => env('PF_OIDC_ENABLED', false),
/*
* Client ID
*
* The client ID provided by your OIDC provider
*/
'clientId' => env('PF_OIDC_CLIENT_ID', false),
/*
* Client Secret
*
* The client secret provided by your OIDC provider
*/
'clientSecret' => env('PF_OIDC_CLIENT_SECRET', false),
/*
* OAuth Scopes
*
* The scopes to request from the OIDC provider, typically including
* 'openid' (required), 'profile', and 'email' for basic user information
*/
'scopes' => env('PF_OIDC_SCOPES', 'openid profile email'),
/*
* Authorization URL
*
* The endpoint used to start the OIDC authentication flow
*/
'authorizeURL' => env('PF_OIDC_AUTHORIZE_URL', ''),
/*
* Token URL
*
* The endpoint used to exchange the authorization code for an access token
*/
'tokenURL' => env('PF_OIDC_TOKEN_URL', ''),
/*
* Profile URL
*
* The endpoint used to retrieve user information with a valid access token
*/
'profileURL' => env('PF_OIDC_PROFILE_URL', ''),
/*
* Logout URL
*
* The endpoint used to log the user out of the OIDC provider
*/
'logoutURL' => env('PF_OIDC_LOGOUT_URL', ''),
/*
* Username Field
*
* The field from the OIDC profile response to use as the username
* Default is 'preferred_username' but can be changed based on your provider
*/
'field_username' => env('PF_OIDC_USERNAME_FIELD', "preferred_username"),
/*
* ID Field
*
* The field from the OIDC profile response to use as the unique identifier
* Default is 'sub' (subject) which is standard in OIDC implementations
*/
'field_id' => env('PF_OIDC_FIELD_ID', 'sub'),
],
]; ];

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_oidc_mappings', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned()->index();
$table->string('oidc_id')->unique()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_oidc_mappings');
}
};

@ -53,6 +53,7 @@
"language": "Sprache", "language": "Sprache",
"privacy": "Privatsph\u00e4re", "privacy": "Privatsph\u00e4re",
"terms": "AGB", "terms": "AGB",
"legalNotice": "Impressum",
"backToPreviousDesign": "Zur\u00fcck zum vorherigen Design" "backToPreviousDesign": "Zur\u00fcck zum vorherigen Design"
}, },
"directMessages": { "directMessages": {

@ -26,7 +26,9 @@
"clickHere": "click here", "clickHere": "click here",
"sensitive": "Sensitive", "sensitive": "Sensitive",
"sensitiveContent": "Sensitive Content", "sensitiveContent": "Sensitive Content",
"sensitiveContentWarning": "This post may contain sensitive content" "sensitiveContentWarning": "This post may contain sensitive content",
"loading": "Loading...",
"continue": "Continue"
}, },
"site": { "site": {
"terms": "Terms of Use", "terms": "Terms of Use",
@ -49,11 +51,13 @@
"appearance": "Appearance", "appearance": "Appearance",
"compose": "Create New", "compose": "Create New",
"logout": "Logout", "logout": "Logout",
"createStory": "Create Story",
"about": "About", "about": "About",
"help": "Help", "help": "Help",
"language": "Language", "language": "Language",
"privacy": "Privacy", "privacy": "Privacy",
"terms": "Terms", "terms": "Terms",
"legalNotice": "Legal Notice",
"backToPreviousDesign": "Go back to previous design" "backToPreviousDesign": "Go back to previous design"
}, },
"directMessages": { "directMessages": {
@ -185,7 +189,34 @@
"unpinPostConfirm": "Are you sure you want to unpin this post?" "unpinPostConfirm": "Are you sure you want to unpin this post?"
}, },
"story": { "story": {
"add": "Add Story" "add": "Add Story",
"myStory": "My Story",
"viewMyStory": "View My Story",
"goBack": "Go Back",
"delete": "Delete",
"crop": "Crop",
"error": "An error occurred, please try again later.",
"cropping": "Cropping",
"storyDuration": "Story Duration",
"seconds": "seconds",
"processing": "Processing",
"shareWithFollowers": "Share moments with followers that last 24 hours",
"cancel": "Cancel",
"viewedBy": "Viewed by",
"next": "Next",
"zoom": "Pan around and pinch to zoom",
"options": "Options",
"allowReplies": "Allow Replies",
"allowReactions": "Allow reactions",
"limit": "You have reached the limit for new stories",
"reactionSent": "Reaction sent",
"replySent": "Reply sent",
"expiresIn": "Expires in",
"viewers": "Viewers",
"report": "Report",
"close": "Close",
"myStories": "My Stories",
"seeAll": "See All"
}, },
"timeline": { "timeline": {
"peopleYouMayKnow": "People you may know", "peopleYouMayKnow": "People you may know",
@ -219,5 +250,79 @@
"grid": "Grid", "grid": "Grid",
"masonry": "Masonry", "masonry": "Masonry",
"feed": "Feed" "feed": "Feed"
},
"settings": {
"filters": {
"title": "Filters",
"manage_your_custom_filters": "Manage your custom filters.",
"customize_your_experience": "Customize your experience with powerful content filters that screen for specific words or phrases throughout your entire account - including home and public timelines, and hashtag feeds.",
"add_new_filter": "Add New Filter",
"limit_message": "You can add up to <strong>:filters_num filters</strong> that can have up to <strong>:keywords_num keywords</strong>.",
"learn_more_help_center": "Learn more in our <a href=\"/site/help\">Help Center</a>.",
"no_filters": "You don't have any content filters yet",
"no_filters_message": "Filters help you hide content containing specific words or phrases from your timelines.",
"create_first_filter": "Create Your First Filter",
"no_matching_filters": "You don't have any content filters that match <strong>:searchQuery</strong>.",
"no_matching_filters_message": "Filters help you hide content containing specific words or phrases from your timelines.",
"create_new_filter": "Create new Filter",
"filter_title": "Filter Title",
"edit_filter": "Edit Filter",
"create_filter": "Create Filter",
"advance_mode": "Advanced Mode",
"simple_mode": "Simple Mode",
"keywords": "Keywords",
"legend": "Legend",
"whole_word": "Whole word",
"partial_word": "Partial word",
"duplicate_not_allowed": "Duplicate keywords are not allowed",
"filter_action": "Filter Action",
"hide_media_blur": "Hide media behind a blurbash",
"show_warning": "Show warning before displaying content",
"hide_content_completely": "Hide content completely",
"apply_filters_to": "Apply filters to",
"home_timeline": "Home Timeline",
"notifications": "Notifications",
"public_timeline": "Public Timeline",
"hashtags": "Hashtag",
"groups": "Groups",
"conversations": "Conversations",
"duration": "Duration",
"forever": "Forever",
"30_minutes": "30 minutes",
"1_hour": "1 hour",
"6_hours": "6 hours",
"12_hours": "12 hours",
"1_day": "1 day",
"1_week": "1 week",
"cutom": "Custom..",
"enter_duration_in_seconds": "Enter duration in seconds",
"save_changes": "Save Changes",
"name_your_filter": "Name your filter",
"give_your_filter_a_name": "Give your filter a name that will help you remember what content it filters.",
"my_filter_name": "My Filter Name",
"filter_duration": "Filter Duration",
"add_filter_keywords": "Add filter keywords",
"add_word_or_phrase": "Add words or phrases you want to filter.<br />Content containing these words will be filtered according to your settings.",
"whole_word_match": "Whole word match - filters exact matches only (e.g. \"book\" won't match \"bookstore\")",
"partial_word_match": "Partial word match - filters any content containing this text (e.g. \"book\" will match \"bookstore\")",
"add_another_keyword": "Add another keyword",
"please_remove_duplicate_keywords": "Please remove duplicate keywords before continuing",
"choose_filter_action": "Choose filter action",
"choose_filter_action_description": "How would you like to handle content that matches your filter?",
"hide_completely": "Completely hide content that matches",
"choose_where_to_apply": "Choose Where to Apply",
"choose_where_to_apply_description": "Select which sections of the application should use this filter.",
"review_your_filter": "Review your filter",
"review_your_filter_description": "Here's a summary of the filter you've created.",
"no_keywords_specified": "No keywords specified",
"action": "Action",
"expires": "Expires",
"never_expires": "Never Expires",
"titleAdvance": "Title",
"context": "Context",
"review": "Review",
"add_keyword": "Add a keyword...",
"enter_filter_title": "Enter filter title"
}
} }
} }

@ -23,7 +23,7 @@
"proceed": "Procedi", "proceed": "Procedi",
"next": "Avanti", "next": "Avanti",
"close": "Chiudi", "close": "Chiudi",
"clickHere": "cclicca qui", "clickHere": "clicca qui",
"sensitive": "Sensibile", "sensitive": "Sensibile",
"sensitiveContent": "Contenuto Sensibile", "sensitiveContent": "Contenuto Sensibile",
"sensitiveContentWarning": "Questo post potrebbe contenere contenuti sensibili" "sensitiveContentWarning": "Questo post potrebbe contenere contenuti sensibili"
@ -63,7 +63,7 @@
"notifications": { "notifications": {
"liked": "ha messo like a", "liked": "ha messo like a",
"commented": "ha commentato il tuo", "commented": "ha commentato il tuo",
"reacted": "ha reagito a", "reacted": "ha reagito al tuo",
"shared": "ha condiviso il tuo", "shared": "ha condiviso il tuo",
"tagged": "ti ha taggato in", "tagged": "ti ha taggato in",
"updatedA": "ha aggiornato un", "updatedA": "ha aggiornato un",

@ -75,16 +75,16 @@
"applicationApproved": "werd goedgekeurd!", "applicationApproved": "werd goedgekeurd!",
"applicationRejected": "werd afgekeurd. Je kunt over 6 maanden opnieuw aanmelden.", "applicationRejected": "werd afgekeurd. Je kunt over 6 maanden opnieuw aanmelden.",
"dm": "dm", "dm": "dm",
"groupPost": "groepspost", "groupPost": "groepsbericht",
"modlog": "modlogboek", "modlog": "modlogboek",
"post": "post", "post": "bericht",
"story": "verhaal", "story": "verhaal",
"noneFound": "Geen notificaties gevonden" "noneFound": "Geen notificaties gevonden"
}, },
"post": { "post": {
"shareToFollowers": "Deel met volgers", "shareToFollowers": "Deel met volgers",
"shareToOther": "Deel met anderen", "shareToOther": "Deel met anderen",
"noLikes": "Nog geen leuks", "noLikes": "Nog geen likes",
"uploading": "Uploaden" "uploading": "Uploaden"
}, },
"profile": { "profile": {
@ -99,10 +99,10 @@
"followRequested": "Volgen verzocht", "followRequested": "Volgen verzocht",
"joined": "Lid geworden", "joined": "Lid geworden",
"emptyCollections": "We kunnen geen collecties vinden", "emptyCollections": "We kunnen geen collecties vinden",
"emptyPosts": "We kunnen geen posts vinden" "emptyPosts": "We kunnen geen berichten vinden"
}, },
"menu": { "menu": {
"viewPost": "Post bekijken", "viewPost": "Berichten bekijken",
"viewProfile": "Profiel bekijken", "viewProfile": "Profiel bekijken",
"moderationTools": "Moderatiegereedschappen", "moderationTools": "Moderatiegereedschappen",
"report": "Rapporteren", "report": "Rapporteren",
@ -123,13 +123,13 @@
"impersonation": "Impersonatie", "impersonation": "Impersonatie",
"scamOrFraud": "Oplichting of fraude", "scamOrFraud": "Oplichting of fraude",
"confirmReport": "Bevestig Rapport", "confirmReport": "Bevestig Rapport",
"confirmReportText": "Weet je zeker dat je deze post wilt rapporteren?", "confirmReportText": "Weet je zeker dat je dit bericht wilt rapporteren?",
"reportSent": "Rapport verzonden!", "reportSent": "Rapport verzonden!",
"reportSentText": "We hebben uw rapport met succes ontvangen.", "reportSentText": "We hebben uw rapport met succes ontvangen.",
"reportSentError": "Er is een probleem opgetreden bij het rapporteren van deze post.", "reportSentError": "Er is een probleem opgetreden bij het rapporteren van dit bericht.",
"modAddCWConfirm": "Weet u zeker dat u een waarschuwing voor inhoud wilt toevoegen aan deze post?", "modAddCWConfirm": "Weet u zeker dat u een waarschuwing voor inhoud wilt toevoegen aan dit bericht?",
"modCWSuccess": "Inhoudswaarschuwing succesvol toegevoegd", "modCWSuccess": "Inhoudswaarschuwing succesvol toegevoegd",
"modRemoveCWConfirm": "Weet u zeker dat u de waarschuwing voor inhoud wilt verwijderen van deze post?", "modRemoveCWConfirm": "Weet u zeker dat u de inhoudswaarschuwing wilt verwijderen van dit bericht?",
"modRemoveCWSuccess": "Succesvol de inhoudswaarschuwing verwijderd", "modRemoveCWSuccess": "Succesvol de inhoudswaarschuwing verwijderd",
"modUnlistConfirm": "Weet je zeker dat je deze post uit de lijst wilt halen?", "modUnlistConfirm": "Weet je zeker dat je deze post uit de lijst wilt halen?",
"modUnlistSuccess": "Post met succes uit de lijst gehaald", "modUnlistSuccess": "Post met succes uit de lijst gehaald",

@ -3,17 +3,17 @@
"comment": "Comentar", "comment": "Comentar",
"commented": "Comentado", "commented": "Comentado",
"comments": "Coment\u00e1rios", "comments": "Coment\u00e1rios",
"like": "Gosto", "like": "Curtir",
"liked": "Gostei", "liked": "Curtiu",
"likes": "Gostos", "likes": "Curtidas",
"share": "Compartilhar", "share": "Compartilhar",
"shared": "Compartilhado", "shared": "Compartilhado",
"shares": "Partilhas", "shares": "Compartilhamentos",
"unshare": "Despartilhar", "unshare": "Remover compartilhamento",
"bookmark": "Favorito", "bookmark": "Favorito",
"cancel": "Cancelar", "cancel": "Cancelar",
"copyLink": "Copiar link", "copyLink": "Copiar link",
"delete": "Eliminar", "delete": "Excluir",
"error": "Erro", "error": "Erro",
"errorMsg": "Algo correu mal. Por favor, tente novamente mais tarde.", "errorMsg": "Algo correu mal. Por favor, tente novamente mais tarde.",
"oops": "Opa!", "oops": "Opa!",
@ -26,7 +26,9 @@
"clickHere": "clique aqui", "clickHere": "clique aqui",
"sensitive": "Sens\u00edvel", "sensitive": "Sens\u00edvel",
"sensitiveContent": "Conte\u00fado sens\u00edvel", "sensitiveContent": "Conte\u00fado sens\u00edvel",
"sensitiveContentWarning": "Este post pode conter conte\u00fado sens\u00edvel" "sensitiveContentWarning": "Este post pode conter conte\u00fado sens\u00edvel",
"loading": "Carregando...",
"continue": "Continuar"
}, },
"site": { "site": {
"terms": "Termos de Uso", "terms": "Termos de Uso",
@ -34,11 +36,11 @@
}, },
"navmenu": { "navmenu": {
"search": "Pesquisa", "search": "Pesquisa",
"admin": "Painel de Administra\u00e7\u00e3o", "admin": "Painel Administrativo",
"homeFeed": "Inicio", "homeFeed": "Inicio",
"localFeed": "Feed local", "localFeed": "Feed local",
"globalFeed": "Feed global", "globalFeed": "Feed global",
"discover": "Descobrir", "discover": "Explorar",
"directMessages": "Mensagens Diretas", "directMessages": "Mensagens Diretas",
"notifications": "Notifica\u00e7\u00f5es", "notifications": "Notifica\u00e7\u00f5es",
"groups": "Grupos", "groups": "Grupos",
@ -49,6 +51,7 @@
"appearance": "Apar\u00eancia", "appearance": "Apar\u00eancia",
"compose": "Criar novo", "compose": "Criar novo",
"logout": "Terminar Sess\u00e3o", "logout": "Terminar Sess\u00e3o",
"createStory": "Criar Story",
"about": "Sobre", "about": "Sobre",
"help": "Ajuda", "help": "Ajuda",
"language": "Idioma", "language": "Idioma",
@ -146,11 +149,11 @@
"unarchive": "Retirar do arquivo", "unarchive": "Retirar do arquivo",
"embed": "Incorporar", "embed": "Incorporar",
"selectOneOption": "Selecione uma das seguintes op\u00e7\u00f5es", "selectOneOption": "Selecione uma das seguintes op\u00e7\u00f5es",
"unlistFromTimelines": "Remover das cronologias", "unlistFromTimelines": "Remover das Timelines",
"addCW": "Adicionar aviso de conte\u00fado", "addCW": "Adicionar aviso de conte\u00fado",
"removeCW": "Remover aviso de conte\u00fado", "removeCW": "Remover aviso de conte\u00fado",
"markAsSpammer": "Marcar como Spammer", "markAsSpammer": "Marcar como Spammer",
"markAsSpammerText": "Remover das cronologias e adicionar um aviso de conte\u00fado \u00e0s publica\u00e7\u00f5es existentes e futuras", "markAsSpammerText": "Remover das timelines e adicionar um aviso de conte\u00fado \u00e0s publica\u00e7\u00f5es existentes e futuras",
"spam": "Lixo Eletr\u00f4nico", "spam": "Lixo Eletr\u00f4nico",
"sensitive": "Conte\u00fado Sens\u00edvel", "sensitive": "Conte\u00fado Sens\u00edvel",
"abusive": "Abusivo ou prejudicial", "abusive": "Abusivo ou prejudicial",
@ -185,7 +188,34 @@
"unpinPostConfirm": "Tem certeza de que deseja desafixar esta publica\u00e7\u00e3o?" "unpinPostConfirm": "Tem certeza de que deseja desafixar esta publica\u00e7\u00e3o?"
}, },
"story": { "story": {
"add": "Adicionar Story" "add": "Adicionar Story",
"myStory": "Minha Story",
"viewMyStory": "Ver Minha Story",
"goBack": "Voltar",
"delete": "Excluir",
"crop": "Cortar",
"error": "Ocorreu um erro, por favor tente novamente mais tarde.",
"cropping": "Cortando",
"storyDuration": "Dura\u00e7\u00e3o da Story",
"seconds": "segundos",
"processing": "Processando",
"shareWithFollowers": "Compartilhe momentos com seguidores que duram 24 horas",
"cancel": "Cancelar",
"viewdBy": "Visualizado por",
"next": "Pr\u00f3ximo",
"zoom": "Mova e pin\u00e7a para dar zoom",
"options": "Op\u00e7\u00f5es",
"allowReplies": "Permitir Respostas",
"allowReactions": "Permitir Rea\u00e7\u00f5es",
"limit": "Voc\u00ea atingiu o limite para novas stories",
"reactionSent": "Rea\u00e7\u00e3o enviada",
"replySent": "Resposta enviada",
"expiresIn": "Expira em",
"viewers": "Visualizadores",
"report": "Denunciar",
"close": "Fechar",
"myStories": "My Stories",
"seeAll": "Ver todas as stories"
}, },
"timeline": { "timeline": {
"peopleYouMayKnow": "Pessoas que talvez conhe\u00e7a", "peopleYouMayKnow": "Pessoas que talvez conhe\u00e7a",
@ -219,5 +249,79 @@
"grid": "Grade", "grid": "Grade",
"masonry": "Mansory", "masonry": "Mansory",
"feed": "Feed" "feed": "Feed"
},
"settings": {
"filters": {
"title": "Filtros",
"manage_your_custom_filters": "Gerencie seus filtros personalizados.",
"customize_your_experience": "Personalize sua experi\u00eancia com filtros de conte\u00fado poderosos que verificam palavras ou frases espec\u00edficas em toda a sua conta - incluindo linhas do tempo pessoais e p\u00fablicas, e feeds de hashtags.",
"add_new_filter": "Adicionar Novo Filtro",
"limit_message": "Voc\u00ea pode adicionar at\u00e9 <strong>:filters_num filtros</strong> que podem ter at\u00e9 <strong>:keywords_num palavras-chave</strong>.",
"learn_more_help_center": "Saiba mais no nosso <a href=\"/site/help\">Centro de Ajuda</a>.",
"no_filters": "Voc\u00ea ainda n\u00e3o tem nenhum filtro de conte\u00fado",
"no_filters_message": "Os filtros ajudam voc\u00ea a ocultar conte\u00fado que cont\u00e9m palavras ou frases espec\u00edficas das suas linhas do tempo.",
"create_first_filter": "Crie Seu Primeiro Filtro",
"no_matching_filters": "Voc\u00ea n\u00e3o tem nenhum filtro de conte\u00fado que corresponda a <strong>:searchQuery</strong>.",
"no_matching_filters_message": "Os filtros ajudam voc\u00ea a ocultar conte\u00fado que cont\u00e9m palavras ou frases espec\u00edficas das suas linhas do tempo.",
"create_new_filter": "Criar Novo Filtro",
"filter_title": "T\u00edtulo do Filtro",
"edit_filter": "Editar Filtro",
"create_filter": "Criar Filtro",
"advance_mode": "Modo Avan\u00e7ado",
"simple_mode": "Modo Simples",
"keywords": "Palavras-chave",
"legend": "Legenda",
"whole_word": "Palavra inteira",
"partial_word": "Palavra parcial",
"duplicate_not_allowed": "Palavras-chave duplicadas n\u00e3o s\u00e3o permitidas",
"filter_action": "A\u00e7\u00e3o do Filtro",
"hide_media_blur": "Ocultar m\u00eddia atr\u00e1s de um desfoque",
"show_warning": "Mostrar aviso antes de exibir o conte\u00fado",
"hide_content_completely": "Ocultar conte\u00fado completamente",
"apply_filters_to": "Aplicar filtros a",
"home_timeline": "Linha do Tempo Pessoal",
"notifications": "Notifica\u00e7\u00f5es",
"public_timeline": "Linha do Tempo P\u00fablica",
"hashtags": "Hashtags",
"groups": "Grupos",
"conversations": "Conversas",
"duration": "Dura\u00e7\u00e3o",
"forever": "Para Sempre",
"30_minutes": "30 minutos",
"1_hour": "1 hora",
"6_hours": "6 horas",
"12_hours": "12 horas",
"1_day": "1 dia",
"1_week": "1 semana",
"cutom": "Personalizado...",
"enter_duration_in_seconds": "Insira a dura\u00e7\u00e3o em segundos",
"save_changes": "Salvar Altera\u00e7\u00f5es",
"name_your_filter": "Nomeie seu filtro",
"give_your_filter_a_name": "D\u00ea ao seu filtro um nome que o ajude a lembrar o que ele filtra.",
"my_filter_name": "Nome do Meu Filtro",
"filter_duration": "Dura\u00e7\u00e3o do Filtro",
"add_filter_keywords": "Adicionar palavras-chave ao filtro",
"add_word_or_phrase": "Adicione palavras ou frases que voc\u00ea deseja filtrar.<br />Conte\u00fado contendo essas palavras ser\u00e1 filtrado de acordo com suas configura\u00e7\u00f5es.",
"whole_word_match": "Correspond\u00eancia de palavra inteira - filtra apenas correspond\u00eancias exatas (ex.: \"livro\" n\u00e3o corresponder\u00e1 a \"livraria\")",
"partial_word_match": "Correspond\u00eancia de palavra parcial - filtra qualquer conte\u00fado que contenha este texto (ex.: \"livro\" corresponder\u00e1 a \"livraria\")",
"add_another_keyword": "Adicionar outra palavra-chave",
"please_remove_duplicate_keywords": "Por favor, remova palavras-chave duplicadas antes de continuar",
"choose_filter_action": "Escolha a a\u00e7\u00e3o do filtro",
"choose_filter_action_description": "Como voc\u00ea gostaria de lidar com o conte\u00fado que corresponde ao seu filtro?",
"hide_completely": "Ocultar completamente o conte\u00fado que corresponde",
"choose_where_to_apply": "Escolha Onde Aplicar",
"choose_where_to_apply_description": "Selecione quais se\u00e7\u00f5es do aplicativo devem usar este filtro.",
"review_your_filter": "Revise seu filtro",
"review_your_filter_description": "Aqui est\u00e1 um resumo do filtro que voc\u00ea criou.",
"no_keywords_specified": "Nenhuma palavra-chave especificada",
"action": "A\u00e7\u00e3o",
"expires": "Expira",
"never_expires": "Nunca Expira",
"titleAdvance": "T\u00edtulo",
"context": "Contexto",
"review": "Revis\u00e3o",
"add_keyword": "Adicionar uma palavra-chave...",
"enter_filter_title": "Insira o t\u00edtulo do filtro"
}
} }
} }

@ -98,7 +98,7 @@
"editProfile": "Profili D\u00fczenle", "editProfile": "Profili D\u00fczenle",
"followRequested": "Takip \u0130ste\u011fi", "followRequested": "Takip \u0130ste\u011fi",
"joined": "Kat\u0131ld\u0131", "joined": "Kat\u0131ld\u0131",
"emptyCollections": "Herhangi bir koleksiyon bulunmuyor", "emptyCollections": "Herhangi bir derleme bulunmuyor",
"emptyPosts": "Herhangi bir g\u00f6nderi bulunmuyor" "emptyPosts": "Herhangi bir g\u00f6nderi bulunmuyor"
}, },
"menu": { "menu": {

@ -1,170 +1,170 @@
{ {
"common": { "common": {
"comment": "\u8a55\u8ad6", "comment": "\u8bc4\u8bba",
"commented": "\u5df2\u8a55\u8ad6", "commented": "\u5df2\u8bc4\u8bba",
"comments": "\u689d\u610f\u898b\u8a55\u8ad6", "comments": "\u8bc4\u8bba",
"like": "\u6309\u8b9a", "like": "\u8d5e",
"liked": "\u5df2\u6309\u8b9a", "liked": "\u5df2\u8d5e",
"likes": "\u500b\u4eba\u5df2\u6309\u8b9a", "likes": "\u70b9\u8d5e",
"share": "\u5206\u4eab", "share": "\u5206\u4eab",
"shared": "\u5df2\u5206\u4eab", "shared": "\u5df2\u5206\u4eab\u7684",
"shares": "\u6b21\u5206\u4eab", "shares": "\u5206\u4eab",
"unshare": "\u53d6\u6d88\u5206\u4eab", "unshare": "\u53d6\u6d88\u5206\u4eab",
"bookmark": "\u66f8\u7c64", "bookmark": "\u6536\u85cf",
"cancel": "\u53d6\u6d88", "cancel": "\u53d6\u6d88",
"copyLink": "\u8907\u88fd\u9023\u7d50", "copyLink": "\u590d\u5236\u94fe\u63a5",
"delete": "\u5220\u9664", "delete": "\u5220\u9664",
"error": "\u932f\u8aa4", "error": "\u9519\u8bef",
"errorMsg": "\u51fa\u4e86\u9ede\u5c0f\u554f\u984c\uff0c\u8acb\u7a0d\u5f8c\u91cd\u8a66\u3002", "errorMsg": "\u51fa\u9519\u4e86\u3002\u8bf7\u7a0d\u540e\u518d\u8bd5\u3002",
"oops": "\u5443\u2026", "oops": "\u54ce\u5440\uff01",
"other": "\u5176\u4ed6", "other": "\u5176\u5b83",
"readMore": "\u7e7c\u7e8c\u8b80\u4e0b\u53bb", "readMore": "\u9605\u8bfb\u66f4\u591a",
"success": "\u6210\u529f", "success": "\u6210\u529f",
"proceed": "\u7e7c\u7e8c", "proceed": "\u7ee7\u7eed",
"next": "\u4e0b\u4e00\u500b", "next": "\u4e0b\u4e00\u4e2a",
"close": "\u95dc\u9589", "close": "\u5173\u95ed",
"clickHere": "\u9ede\u64ca\u6b64\u8655", "clickHere": "\u70b9\u51fb\u6b64\u5904",
"sensitive": "\u654f\u611f\u5167\u5bb9", "sensitive": "\u654f\u611f",
"sensitiveContent": "\u654f\u611f\u5167\u5bb9", "sensitiveContent": "\u654f\u611f\u5185\u5bb9",
"sensitiveContentWarning": "\u9019\u7bc7\u6587\u53ef\u80fd\u5305\u542b\u654f\u611f\u5167\u5bb9" "sensitiveContentWarning": "\u6b64\u5e16\u6587\u53ef\u80fd\u5305\u542b\u654f\u611f\u5185\u5bb9"
}, },
"site": { "site": {
"terms": "\u4f7f\u7528\u689d\u6b3e", "terms": "\u4f7f\u7528\u6761\u6b3e",
"privacy": "\u96b1\u79c1\u6b0a\u653f\u7b56" "privacy": "\u9690\u79c1\u653f\u7b56"
}, },
"navmenu": { "navmenu": {
"search": "\u641c\u5c0b", "search": "\u641c\u7d22",
"admin": "\u7ba1\u7406\u5100\u8868\u677f", "admin": "\u7ba1\u7406\u9762\u677f",
"homeFeed": "\u9996\u9801\u52d5\u614b", "homeFeed": "\u4e3b\u9875",
"localFeed": "\u7ad9\u5167\u52d5\u614b", "localFeed": "\u672c\u7ad9\u52a8\u6001",
"globalFeed": "\u806f\u90a6\u52d5\u614b", "globalFeed": "\u8de8\u7ad9\u52a8\u6001",
"discover": "\u63a2\u7d22", "discover": "\u63a2\u7d22",
"directMessages": "\u79c1\u4eba\u8a0a\u606f", "directMessages": "\u79c1\u4fe1",
"notifications": "\u901a\u77e5", "notifications": "\u901a\u77e5",
"groups": "\u7fa4\u7d44", "groups": "\u7fa4\u7ec4",
"stories": "\u9650\u6642\u52d5\u614b", "stories": "\u6545\u4e8b",
"profile": "\u500b\u4eba\u6a94\u6848", "profile": "\u4e2a\u4eba\u8d44\u6599",
"drive": "Drive", "drive": "\u7f51\u76d8",
"settings": "\u8a2d\u5b9a", "settings": "\u8bbe\u7f6e",
"compose": "\u65b0\u589e", "compose": "\u521b\u5efa",
"logout": "\u767b\u51fa", "logout": "\u767b\u51fa",
"about": "\u95dc\u65bc", "about": "\u5173\u4e8e",
"help": "\u8aac\u660e", "help": "\u5e2e\u52a9",
"language": "\u8a9e\u8a00", "language": "\u8bed\u8a00",
"privacy": "\u96b1\u79c1\u6b0a", "privacy": "\u9690\u79c1",
"terms": "\u689d\u6b3e", "terms": "\u4f7f\u7528\u6761\u6b3e",
"backToPreviousDesign": "\u56de\u5230\u5148\u524d\u7684\u8a2d\u8a08" "backToPreviousDesign": "\u8fd4\u56de\u4e0a\u4e00\u4e2a\u8bbe\u8ba1"
}, },
"directMessages": { "directMessages": {
"inbox": "\u6536\u4ef6\u593e", "inbox": "\u6536\u4ef6\u7bb1",
"sent": "\u5bc4\u4ef6\u593e", "sent": "\u5df2\u53d1\u9001",
"requests": "\u8acb\u6c42" "requests": "\u8bf7\u6c42"
}, },
"notifications": { "notifications": {
"liked": "\u559c\u6b61\u4f60\u7684", "liked": "\u70b9\u8d5e\u4e86\u4f60\u7684",
"commented": "\u8a55\u8ad6\u4e86\u4f60\u7684", "commented": "\u8bc4\u8bba\u4e86\u4f60\u7684",
"reacted": "\u53cd\u61c9\u4e86\u4f60\u7684", "reacted": "\u56de\u5e94\u4e86\u4f60\u7684",
"shared": "\u5206\u4eab\u4e86\u4f60\u7684", "shared": "\u8f6c\u53d1\u4e86\u4f60\u7684",
"tagged": "tagged you in a", "tagged": "\u5728\u5e16\u5b50\u4e2d\u6807\u8bb0\u4e86\u4f60",
"updatedA": "updated a", "updatedA": "\u66f4\u65b0\u4e86\u4e00\u4e2a",
"sentA": "sent a", "sentA": "\u53d1\u9001\u4e86\u4e00\u4e2a",
"followed": "\u5df2\u95dc\u6ce8", "followed": "\u5df2\u5173\u6ce8",
"mentioned": "\u88ab\u63d0\u53ca", "mentioned": "\u63d0\u53ca\u4e86",
"you": "\u4f60", "you": "\u4f60",
"yourApplication": "\u60a8\u7684\u52a0\u5165\u7533\u8acb", "yourApplication": "\u60a8\u60f3\u8981\u52a0\u5165",
"applicationApproved": "\u88ab\u6279\u51c6\u4e86\uff01", "applicationApproved": "\u7684\u7533\u8bf7\u88ab\u6279\u51c6\u4e86\uff01",
"applicationRejected": "\u88ab\u62d2\u7d55\u3002\u60a8\u53ef\u4ee5\u5728 6 \u500b\u6708\u5f8c\u518d\u6b21\u7533\u8acb\u52a0\u5165\u3002", "applicationRejected": "\u7684\u7533\u8bf7\u88ab\u62d2\u7edd\u3002\u60a8\u53ef\u4ee5\u5728 6 \u4e2a\u6708\u540e\u91cd\u65b0\u7533\u8bf7\u52a0\u5165\u3002",
"dm": "\u76f4\u63a5\u8a0a\u606f", "dm": "\u79c1\u4fe1",
"groupPost": "group post", "groupPost": "\u7fa4\u7ec4\u8d34\u6587",
"modlog": "modlog", "modlog": "\u7ba1\u7406\u65e5\u5fd7",
"post": "post", "post": "\u5e16\u6587",
"story": "story", "story": "\u6545\u4e8b",
"noneFound": "No notifications found" "noneFound": "\u6682\u65e0\u901a\u77e5"
}, },
"post": { "post": {
"shareToFollowers": "Share to followers", "shareToFollowers": "\u5206\u4eab\u7ed9\u5173\u6ce8\u8005",
"shareToOther": "Share to other", "shareToOther": "\u4e0e\u4ed6\u4eba\u5206\u4eab",
"noLikes": "No likes yet", "noLikes": "\u5c1a\u65e0\u70b9\u8d5e",
"uploading": "\u4e0a\u50b3\u4e2d" "uploading": "\u4e0a\u4f20\u4e2d"
}, },
"profile": { "profile": {
"posts": "Posts", "posts": "\u5e16\u5b50",
"followers": "\u8ddf\u96a8\u8005", "followers": "\u5173\u6ce8\u8005",
"following": "\u8ffd\u8e64\u4e2d", "following": "\u6b63\u5728\u5173\u6ce8",
"admin": "\u7ba1\u7406\u54e1", "admin": "\u7ba1\u7406\u5458",
"collections": "Collections", "collections": "\u5f71\u96c6",
"follow": "\u8ffd\u8e64", "follow": "\u5173\u6ce8",
"unfollow": "\u53d6\u6d88\u8ffd\u8e64", "unfollow": "\u53d6\u6d88\u5173\u6ce8",
"editProfile": "\u7de8\u8f2f\u500b\u4eba\u6a94\u6848", "editProfile": "\u7f16\u8f91\u4e2a\u4eba\u8d44\u6599",
"followRequested": "\u8ffd\u8e64\u8acb\u6c42", "followRequested": "\u5df2\u53d1\u9001\u5173\u6ce8\u8bf7\u6c42",
"joined": "Joined", "joined": "\u5df2\u52a0\u5165",
"emptyCollections": "We can't seem to find any collections", "emptyCollections": "\u6211\u4eec\u4f3c\u4e4e\u627e\u4e0d\u5230\u4efb\u4f55\u5f71\u96c6",
"emptyPosts": "We can't seem to find any posts" "emptyPosts": "\u6211\u4eec\u4f3c\u4e4e\u672a\u80fd\u627e\u5230\u4efb\u4f55\u5e16\u6587"
}, },
"menu": { "menu": {
"viewPost": "View Post", "viewPost": "\u67e5\u770b\u5e16\u6587",
"viewProfile": "View Profile", "viewProfile": "\u67e5\u770b\u4e2a\u4eba\u8d44\u6599",
"moderationTools": "Moderation Tools", "moderationTools": "\u7ba1\u7406\u5de5\u5177",
"report": "\u6aa2\u8209", "report": "\u4e3e\u62a5",
"archive": "\u5c01\u5b58", "archive": "\u5c01\u5b58",
"unarchive": "\u53d6\u6d88\u5c01\u5b58", "unarchive": "\u53d6\u6d88\u5c01\u5b58",
"embed": "\u5d4c\u5165", "embed": "\u5d4c\u5165",
"selectOneOption": "\u9078\u64c7\u4ee5\u4e0b\u9078\u9805\u4e4b\u4e00", "selectOneOption": "\u8bf7\u4ece\u4e0b\u5217\u9009\u9879\u4e2d\u9009\u62e9\u4e00\u4e2a",
"unlistFromTimelines": "\u4e0d\u5728\u6642\u9593\u8ef8\u4e2d\u4e0a\u986f\u793a", "unlistFromTimelines": "\u4ece\u65f6\u95f4\u7ebf\u4e2d\u9690\u85cf",
"addCW": "\u589e\u52a0\u5167\u5bb9\u8b66\u544a", "addCW": "\u6dfb\u52a0\u5185\u5bb9\u8b66\u544a",
"removeCW": "\u79fb\u9664\u5167\u5bb9\u8b66\u544a", "removeCW": "\u79fb\u9664\u5185\u5bb9\u8b66\u544a",
"markAsSpammer": "\u6a19\u8a18\u70ba\u5783\u573e\u8a0a\u606f\u4f86\u6e90\u8005", "markAsSpammer": "\u6807\u8bb0\u4e3a\u9a9a\u6270\u4fe1\u606f\u53d1\u9001\u8005",
"markAsSpammerText": "\u5c0d\u8a08\u6709\u53ca\u672a\u4f86\u8cbc\u6587\u8a2d\u5b9a\u6210\u4e0d\u5217\u51fa\u548c\u589e\u52a0\u5167\u5bb9\u8b66\u544a", "markAsSpammerText": "\u5c06\u6240\u6709\u76ee\u524d\u53ca\u672a\u6765\u7684\u5e16\u5b50\u9690\u85cf\u5e76\u6dfb\u52a0\u5185\u5bb9\u8b66\u544a",
"spam": "\u5783\u573e\u8a0a\u606f", "spam": "\u9a9a\u6270\u4fe1\u606f",
"sensitive": "\u654f\u611f\u5167\u5bb9", "sensitive": "\u654f\u611f\u5185\u5bb9",
"abusive": "\u8fb1\u7f75\u6216\u6709\u5bb3", "abusive": "\u6076\u610f\u6216\u6709\u5bb3\u7684",
"underageAccount": "\u672a\u6210\u5e74\u5e33\u865f", "underageAccount": "\u672a\u6210\u5e74\u8d26\u6237",
"copyrightInfringement": "\u4fb5\u72af\u7248\u6b0a", "copyrightInfringement": "\u7248\u6743\u4fb5\u72af",
"impersonation": "\u5047\u5192\u5e33\u865f", "impersonation": "\u5192\u5145\u4ed6\u4eba",
"scamOrFraud": "\u8a50\u9a19\u5167\u5bb9", "scamOrFraud": "\u9a9a\u6270\u6216\u6b3a\u8bc8\u884c\u4e3a",
"confirmReport": "\u78ba\u8a8d\u6aa2\u8209", "confirmReport": "\u786e\u8ba4\u4e3e\u62a5",
"confirmReportText": "\u4f60\u78ba\u5b9a\u8981\u6aa2\u8209\u9019\u7bc7\u8cbc\u6587\uff1f", "confirmReportText": "\u60a8\u786e\u5b9a\u8981\u4e3e\u62a5\u8fd9\u7bc7\u5e16\u6587\u5417\uff1f",
"reportSent": "\u6aa2\u8209\u5df2\u9001\u51fa\uff01", "reportSent": "\u4e3e\u62a5\u5df2\u53d1\u9001\uff01",
"reportSentText": "\u6211\u5011\u5df2\u7d93\u6536\u5230\u4f60\u7684\u6aa2\u8209\u3002", "reportSentText": "\u6211\u4eec\u5df2\u6210\u529f\u6536\u5230\u60a8\u7684\u4e3e\u62a5\u3002",
"reportSentError": "\u6aa2\u8209\u6b64\u8cbc\u6587\u6642\u51fa\u73fe\u554f\u984c\u3002", "reportSentError": "\u4e3e\u62a5\u8fd9\u4e2a\u5e16\u5b50\u65f6\u51fa\u73b0\u95ee\u9898\u3002",
"modAddCWConfirm": "\u60a8\u78ba\u5b9a\u8981\u70ba\u6b64\u8cbc\u6587\u6dfb\u52a0\u5167\u5bb9\u8b66\u544a\u55ce\uff1f", "modAddCWConfirm": "\u60a8\u786e\u5b9a\u8981\u4e3a\u8fd9\u4e2a\u5e16\u6587\u6dfb\u52a0\u5185\u5bb9\u8b66\u544a\uff1f",
"modCWSuccess": "\u6210\u529f\u6dfb\u52a0\u5167\u5bb9\u8b66\u544a", "modCWSuccess": "\u5185\u5bb9\u8b66\u544a\u5df2\u6210\u529f\u6dfb\u52a0",
"modRemoveCWConfirm": "\u60a8\u78ba\u5b9a\u8981\u522a\u9664\u6b64\u8cbc\u6587\u4e0a\u7684\u5167\u5bb9\u8b66\u544a\u55ce\uff1f", "modRemoveCWConfirm": "\u60a8\u786e\u5b9a\u8981\u79fb\u9664\u8fd9\u4e2a\u5e16\u6587\u4e0a\u7684\u5185\u5bb9\u8b66\u544a\u5417\uff1f",
"modRemoveCWSuccess": "\u5df2\u6210\u529f\u522a\u9664\u5167\u5bb9\u8b66\u544a", "modRemoveCWSuccess": "\u5185\u5bb9\u8b66\u544a\u5df2\u88ab\u6210\u529f\u79fb\u9664",
"modUnlistConfirm": "Are you sure you want to unlist this post?", "modUnlistConfirm": "\u60a8\u786e\u5b9a\u8981\u9690\u85cf\u8fd9\u4e2a\u5e16\u6587\u5417\uff1f",
"modUnlistSuccess": "Successfully unlisted post", "modUnlistSuccess": "\u5e16\u6587\u5df2\u88ab\u6210\u529f\u9690\u85cf",
"modMarkAsSpammerConfirm": "\u60a8\u78ba\u5b9a\u8981\u5c07\u6b64\u4f7f\u7528\u8005\u6a19\u8a18\u70ba\u5783\u573e\u8a0a\u606f\u767c\u9001\u8005\u55ce\uff1f\u6240\u6709\u73fe\u6709\u548c\u672a\u4f86\u7684\u8cbc\u6587\u5c07\u6539\u70ba\u975e\u516c\u958b\uff0c\u4e26\u5c07\u52a0\u4e0a\u5167\u5bb9\u8b66\u544a\u3002", "modMarkAsSpammerConfirm": "\u60a8\u786e\u5b9a\u8981\u5c06\u6b64\u7528\u6237\u6807\u8bb0\u4e3a\u5783\u573e\u4fe1\u606f\u53d1\u9001\u8005\uff1f\u6240\u6709\u6b64\u7528\u6237\u7684\u73b0\u6709\u548c\u672a\u6765\u7684\u5e16\u5b50\u90fd\u5c06\u88ab\u9690\u85cf\uff0c\u5e76\u6dfb\u52a0\u5185\u5bb9\u8b66\u544a\u3002",
"modMarkAsSpammerSuccess": "\u5df2\u6210\u529f\u5c07\u5e33\u6236\u6a19\u8a18\u70ba\u5783\u573e\u8a0a\u606f\u767c\u9001\u8005", "modMarkAsSpammerSuccess": "\u6210\u529f\u5730\u5c06\u5e10\u6237\u6807\u8bb0\u4e3a\u5783\u573e\u4fe1\u606f\u53d1\u9001\u8005",
"toFollowers": "\u7d66\u95dc\u6ce8\u8005", "toFollowers": "\u81f3\u5173\u6ce8\u8005",
"showCaption": "\u986f\u793a\u6587\u5b57\u8aaa\u660e", "showCaption": "\u663e\u793a\u6807\u9898",
"showLikes": "\u986f\u793a\u559c\u6b61", "showLikes": "\u663e\u793a\u70b9\u8d5e",
"compactMode": "\u7dca\u6e4a\u6a21\u5f0f", "compactMode": "\u7d27\u51d1\u6a21\u5f0f",
"embedConfirmText": "\u4f7f\u7528\u6b64\u5d4c\u5165\u5f0f\u5167\u5bb9\u5373\u8868\u793a\u60a8\u540c\u610f\u6211\u5011\u7684", "embedConfirmText": "\u4f7f\u7528\u8fd9\u6bb5\u5d4c\u5165\u5185\u5bb9\u4ee3\u8868\u60a8\u5c06\u540c\u610f\u6211\u4eec\u7684",
"deletePostConfirm": "\u4f60\u78ba\u5b9a\u8981\u522a\u9664\u6b64\u8cbc\u6587\uff1f", "deletePostConfirm": "\u60a8\u786e\u5b9a\u8981\u5220\u9664\u8be5\u8d34\u5417\uff1f",
"archivePostConfirm": "\u60a8\u78ba\u5b9a\u8981\u5c01\u5b58\u6b64\u8cbc\u6587\u55ce\uff1f", "archivePostConfirm": "\u60a8\u786e\u5b9a\u8981\u5c01\u5b58\u6b64\u8d34\u6587\u5417\uff1f",
"unarchivePostConfirm": "\u60a8\u78ba\u5b9a\u8981\u89e3\u9664\u5c01\u5b58\u6b64\u8cbc\u6587\u55ce\uff1f" "unarchivePostConfirm": "\u60a8\u786e\u5b9a\u8981\u53d6\u6d88\u5c01\u5b58\u6b64\u8d34\u6587\u5417\uff1f"
}, },
"story": { "story": {
"add": "\u65b0\u589e\u6545\u4e8b" "add": "\u6dfb\u52a0\u6545\u4e8b"
}, },
"timeline": { "timeline": {
"peopleYouMayKnow": "\u4f60\u53ef\u80fd\u8a8d\u8b58", "peopleYouMayKnow": "\u60a8\u53ef\u80fd\u8ba4\u8bc6\u7684\u4eba",
"onboarding": { "onboarding": {
"welcome": "\u6b61\u8fce", "welcome": "\u6b22\u8fce",
"thisIsYourHomeFeed": "This is your home feed, a chronological feed of posts from accounts you follow.", "thisIsYourHomeFeed": "\u8fd9\u662f\u4f60\u7684\u4e3b\u9875\u65f6\u95f4\u7ebf\uff0c\u5b83\u4f1a\u6309\u65f6\u95f4\u987a\u5e8f\u6392\u5217\u4f60\u6240\u5173\u6ce8\u7684\u8d26\u6237\u7684\u5e16\u5b50\u3002",
"letUsHelpYouFind": "Let us help you find some interesting people to follow", "letUsHelpYouFind": "\u8ba9\u6211\u4eec\u5e2e\u4f60\u627e\u4e00\u4e9b\u6709\u8da3\u7684\u4eba\u6765\u5173\u6ce8",
"refreshFeed": "\u66f4\u65b0\u6211\u7684\u6e90" "refreshFeed": "\u5237\u65b0\u52a8\u6001\u5217\u8868"
} }
}, },
"hashtags": { "hashtags": {
"emptyFeed": "\u6211\u5011\u4f3c\u4e4e\u627e\u4e0d\u5230\u6b64\u4e3b\u984c\u6a19\u7c64\u7684\u4efb\u4f55\u8cbc\u6587" "emptyFeed": "\u6211\u4eec\u4f3c\u4e4e\u627e\u4e0d\u5230\u8fd9\u4e2a\u6807\u7b7e\u7684\u4efb\u4f55\u5e16\u5b50"
}, },
"report": { "report": {
"report": "\u6aa2\u8209", "report": "\u4e3e\u62a5",
"selectReason": "\u9078\u64c7\u4e00\u500b\u7406\u7531", "selectReason": "\u9009\u62e9\u7406\u7531",
"reported": "\u5df2\u6aa2\u8209", "reported": "\u5df2\u4e3e\u62a5",
"sendingReport": "\u9001\u51fa\u6aa2\u8209", "sendingReport": "\u6b63\u5728\u63d0\u4ea4\u4e3e\u62a5",
"thanksMsg": "\u611f\u8b1d\u4f60\u7684\u6aa2\u8209\u8b93\u4e16\u754c\u66f4\u7f8e\u597d\uff01", "thanksMsg": "\u611f\u8c22\u4f60\u7684\u4e3e\u62a5\uff0c\u8fd9\u6709\u52a9\u4e8e\u7ef4\u62a4\u6211\u4eec\u793e\u533a\u7684\u5b89\u5168\uff01",
"contactAdminMsg": "\u5982\u679c\u4f60\u60f3\u8981\u63d0\u4f9b\u66f4\u591a\u6709\u95dc\u672c\u6b21\u6aa2\u8209\u7684\u5167\u5bb9\u7d66\u7ba1\u7406\u54e1" "contactAdminMsg": "\u5982\u679c\u4f60\u60f3\u5c31\u8fd9\u4e2a\u5e16\u5b50\u8054\u7cfb\u7ba1\u7406\u5458\u6216\u7ba1\u7406\u5458\u4e3e\u62a5"
} }
} }

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[2822],{7685:(t,e,a)=>{a.r(e),a.d(e,{default:()=>f});var o=a(85072),n=a.n(o),r=a(12712),d={insert:"head",singleton:!1};n()(r.default,d);const f=r.default.locals||{}},12712:(t,e,a)=>{a.r(e),a.d(e,{default:()=>r});var o=a(76798),n=a.n(o)()((function(t){return t[1]}));n.push([t.id,".group-notifications-component[data-v-a4ffe9ee]{font-family:var(--font-family-sans-serif)}.group-notifications-component .jumbotron[data-v-a4ffe9ee]{background-color:#fff;border-radius:0}",""]);const r=n},22500:(t,e,a)=>{a.r(e),a.d(e,{default:()=>d});var o=a(47393),n=a(67988),r={};for(const t in n)"default"!==t&&(r[t]=()=>n[t]);a.d(e,r);a(61784);const d=(0,a(14486).default)(n.default,o.render,o.staticRenderFns,!1,null,"a4ffe9ee",null).exports},42360:(t,e,a)=>{a.r(e),a.d(e,{render:()=>o,staticRenderFns:()=>n});var o=function(){var t=this._self._c;return t("div",{staticClass:"group-notifications-component"},[t("div",{staticClass:"row border-bottom m-0 p-0"},[t("sidebar"),this._v(" "),t("create-group")],1)])},n=[]},47393:(t,e,a)=>{a.r(e);var o=a(42360),n={};for(const t in o)"default"!==t&&(n[t]=()=>o[t]);a.d(e,n)},61784:(t,e,a)=>{a.r(e);var o=a(7685),n={};for(const t in o)"default"!==t&&(n[t]=()=>o[t]);a.d(e,n)},67988:(t,e,a)=>{a.r(e),a.d(e,{default:()=>r});var o=a(96140),n={};for(const t in o)"default"!==t&&(n[t]=()=>o[t]);a.d(e,n);const r=o.default},96140:(t,e,a)=>{a.r(e),a.d(e,{default:()=>d});var o=a(26679),n=a(16080),r=a(49139);const d={components:{sidebar:o.default,loader:n.default,"create-group":r.default},data:function(){return{loaded:!1,loadTimeout:void 0}},created:function(){var t=this;this.loadTimeout=setTimeout((function(){t.loaded=!0}),1e3)},beforeUnmount:function(){clearTimeout(this.loadTimeout)}}}}]); "use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[2822],{7685:(t,e,a)=>{a.r(e),a.d(e,{default:()=>f});var o=a(85072),n=a.n(o),r=a(12712),d={insert:"head",singleton:!1};n()(r.default,d);const f=r.default.locals||{}},12712:(t,e,a)=>{a.r(e),a.d(e,{default:()=>r});var o=a(76798),n=a.n(o)()((function(t){return t[1]}));n.push([t.id,".group-notifications-component[data-v-a4ffe9ee]{font-family:var(--font-family-sans-serif)}.group-notifications-component .jumbotron[data-v-a4ffe9ee]{background-color:#fff;border-radius:0}",""]);const r=n},22500:(t,e,a)=>{a.r(e),a.d(e,{default:()=>d});var o=a(47393),n=a(45607),r={};for(const t in n)"default"!==t&&(r[t]=()=>n[t]);a.d(e,r);a(61784);const d=(0,a(14486).default)(n.default,o.render,o.staticRenderFns,!1,null,"a4ffe9ee",null).exports},42360:(t,e,a)=>{a.r(e),a.d(e,{render:()=>o,staticRenderFns:()=>n});var o=function(){var t=this._self._c;return t("div",{staticClass:"group-notifications-component"},[t("div",{staticClass:"row border-bottom m-0 p-0"},[t("sidebar"),this._v(" "),t("create-group")],1)])},n=[]},45607:(t,e,a)=>{a.r(e),a.d(e,{default:()=>r});var o=a(96140),n={};for(const t in o)"default"!==t&&(n[t]=()=>o[t]);a.d(e,n);const r=o.default},47393:(t,e,a)=>{a.r(e);var o=a(42360),n={};for(const t in o)"default"!==t&&(n[t]=()=>o[t]);a.d(e,n)},61784:(t,e,a)=>{a.r(e);var o=a(7685),n={};for(const t in o)"default"!==t&&(n[t]=()=>o[t]);a.d(e,n)},96140:(t,e,a)=>{a.r(e),a.d(e,{default:()=>d});var o=a(26679),n=a(16080),r=a(49139);const d={components:{sidebar:o.default,loader:n.default,"create-group":r.default},data:function(){return{loaded:!1,loadTimeout:void 0}},created:function(){var t=this;this.loadTimeout=setTimeout((function(){t.loaded=!0}),1e3)},beforeUnmount:function(){clearTimeout(this.loadTimeout)}}}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
(()=>{"use strict";var e,r,a,o={},t={};function n(e){var r=t[e];if(void 0!==r)return r.exports;var a=t[e]={id:e,loaded:!1,exports:{}};return o[e].call(a.exports,a,a.exports,n),a.loaded=!0,a.exports}n.m=o,e=[],n.O=(r,a,o,t)=>{if(!a){var c=1/0;for(f=0;f<e.length;f++){for(var[a,o,t]=e[f],d=!0,s=0;s<a.length;s++)(!1&t||c>=t)&&Object.keys(n.O).every((e=>n.O[e](a[s])))?a.splice(s--,1):(d=!1,t<c&&(c=t));if(d){e.splice(f--,1);var i=o();void 0!==i&&(r=i)}}return r}t=t||0;for(var f=e.length;f>0&&e[f-1][2]>t;f--)e[f]=e[f-1];e[f]=[a,o,t]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var a in r)n.o(r,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:r[a]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,a)=>(n.f[a](e,r),r)),[])),n.u=e=>"js/"+{529:"groups-page",1179:"daci.chunk",1240:"discover~myhashtags.chunk",1645:"profile~following.bundle",2156:"dms.chunk",2822:"group.create",2966:"discover~hashtag.bundle",3688:"discover~serverfeed.chunk",4951:"home.chunk",6250:"discover~settings.chunk",6438:"groups-page-media",6535:"discover.chunk",6740:"discover~memories.chunk",6791:"groups-page-members",7206:"groups-page-topics",7342:"groups-post",7399:"dms~message.chunk",7413:"error404.bundle",7521:"discover~findfriends.chunk",7744:"notifications.chunk",8087:"profile.chunk",8119:"i18n.bundle",8257:"groups-page-about",8408:"post.chunk",8977:"profile~followers.bundle",9124:"compose.chunk",9231:"groups-profile",9919:"changelog.bundle"}[e]+"."+{529:"4a77f2a4e0024224",1179:"4eaae509ed4a084c",1240:"57eeb9257cb300fd",1645:"8ebe39a19638db1b",2156:"13449036a5b769e6",2822:"38102523ebf4cde9",2966:"fffb7ab6f02db6fe",3688:"b7e1082a3be6ef4c",4951:"7b3c50ff0f7828a4",6250:"edeee5803151d4eb",6438:"526b66b27a0bd091",6535:"0ca404627af971f2",6740:"8ea5b8e37111f15f",6791:"c59de89c3b8e3a02",7206:"d279a2438ee20311",7342:"e160e406bdb4a1b0",7399:"f0d6ccb6f2f1cbf7",7413:"f5958c1713b4ab7c",7521:"2ccaf3c586ba03fc",7744:"a8193668255b2c9a",8087:"5d560ecb7d4a57ce",8119:"85976a3b9d6b922a",8257:"16d96a32748daa93",8408:"d0c8b400a930b92a",8977:"9d2008cfa13a6f17",9124:"80e32f21442c8a91",9231:"58b5bf1af4d0722e",9919:"efd3d17aee17020e"}[e]+".js",n.miniCssF=e=>({2305:"css/portfolio",2540:"css/landing",3364:"css/admin",4370:"css/profile",6952:"css/appdark",8252:"css/app",8759:"css/spa"}[e]+".css"),n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},a="pixelfed:",n.l=(e,o,t,c)=>{if(r[e])r[e].push(o);else{var d,s;if(void 0!==t)for(var i=document.getElementsByTagName("script"),f=0;f<i.length;f++){var u=i[f];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==a+t){d=u;break}}d||(s=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,n.nc&&d.setAttribute("nonce",n.nc),d.setAttribute("data-webpack",a+t),d.src=e),r[e]=[o];var l=(a,o)=>{d.onerror=d.onload=null,clearTimeout(b);var t=r[e];if(delete r[e],d.parentNode&&d.parentNode.removeChild(d),t&&t.forEach((e=>e(o))),a)return a(o)},b=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),s&&document.head.appendChild(d)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.p="/",(()=>{var e={461:0,6952:0,8252:0,2305:0,3364:0,2540:0,4370:0,8759:0};n.f.j=(r,a)=>{var o=n.o(e,r)?e[r]:void 0;if(0!==o)if(o)a.push(o[2]);else if(/^((69|82)52|2305|2540|3364|4370|461|8759)$/.test(r))e[r]=0;else{var t=new Promise(((a,t)=>o=e[r]=[a,t]));a.push(o[2]=t);var c=n.p+n.u(r),d=new Error;n.l(c,(a=>{if(n.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var t=a&&("load"===a.type?"missing":a.type),c=a&&a.target&&a.target.src;d.message="Loading chunk "+r+" failed.\n("+t+": "+c+")",d.name="ChunkLoadError",d.type=t,d.request=c,o[1](d)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,a)=>{var o,t,[c,d,s]=a,i=0;if(c.some((r=>0!==e[r]))){for(o in d)n.o(d,o)&&(n.m[o]=d[o]);if(s)var f=s(n)}for(r&&r(a);i<c.length;i++)t=c[i],n.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return n.O(f)},a=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];a.forEach(r.bind(null,0)),a.push=r.bind(null,a.push.bind(a))})(),n.nc=void 0})(); (()=>{"use strict";var e,r,o,a={},t={};function n(e){var r=t[e];if(void 0!==r)return r.exports;var o=t[e]={id:e,loaded:!1,exports:{}};return a[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}n.m=a,e=[],n.O=(r,o,a,t)=>{if(!o){var d=1/0;for(f=0;f<e.length;f++){for(var[o,a,t]=e[f],s=!0,c=0;c<o.length;c++)(!1&t||d>=t)&&Object.keys(n.O).every((e=>n.O[e](o[c])))?o.splice(c--,1):(s=!1,t<d&&(d=t));if(s){e.splice(f--,1);var i=a();void 0!==i&&(r=i)}}return r}t=t||0;for(var f=e.length;f>0&&e[f-1][2]>t;f--)e[f]=e[f-1];e[f]=[o,a,t]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var o in r)n.o(r,o)&&!n.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,o)=>(n.f[o](e,r),r)),[])),n.u=e=>"js/"+{529:"groups-page",1179:"daci.chunk",1240:"discover~myhashtags.chunk",1645:"profile~following.bundle",2156:"dms.chunk",2822:"group.create",2966:"discover~hashtag.bundle",3688:"discover~serverfeed.chunk",4951:"home.chunk",6250:"discover~settings.chunk",6438:"groups-page-media",6535:"discover.chunk",6740:"discover~memories.chunk",6791:"groups-page-members",7206:"groups-page-topics",7342:"groups-post",7399:"dms~message.chunk",7413:"error404.bundle",7521:"discover~findfriends.chunk",7744:"notifications.chunk",8087:"profile.chunk",8119:"i18n.bundle",8257:"groups-page-about",8408:"post.chunk",8977:"profile~followers.bundle",9124:"compose.chunk",9231:"groups-profile",9919:"changelog.bundle"}[e]+"."+{529:"4a77f2a4e0024224",1179:"0903327306251770",1240:"9b2cd210943ec613",1645:"8ebe39a19638db1b",2156:"746342b9470dc71f",2822:"e34ad5621d07870d",2966:"3f6d5e3bb2865a61",3688:"7eeef300c5b29e82",4951:"cf3e6ccd3b76689d",6250:"80c4e5afc970254e",6438:"526b66b27a0bd091",6535:"8698471944aa4417",6740:"8601596a52c06bfc",6791:"c59de89c3b8e3a02",7206:"d279a2438ee20311",7342:"e160e406bdb4a1b0",7399:"8cdd27784f95ee11",7413:"f5958c1713b4ab7c",7521:"c3db8f429e763088",7744:"eb78183fd97a9f0f",8087:"5b03b78ed621f690",8119:"ff6f2af48fd2e3d5",8257:"16d96a32748daa93",8408:"cdef3ec51a723c2f",8977:"9d2008cfa13a6f17",9124:"8292176da8a20099",9231:"58b5bf1af4d0722e",9919:"8ee4f1174f52ec8b"}[e]+".js",n.miniCssF=e=>({2305:"css/portfolio",2540:"css/landing",3364:"css/admin",4370:"css/profile",6952:"css/appdark",8252:"css/app",8759:"css/spa"}[e]+".css"),n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},o="pixelfed:",n.l=(e,a,t,d)=>{if(r[e])r[e].push(a);else{var s,c;if(void 0!==t)for(var i=document.getElementsByTagName("script"),f=0;f<i.length;f++){var u=i[f];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==o+t){s=u;break}}s||(c=!0,(s=document.createElement("script")).charset="utf-8",s.timeout=120,n.nc&&s.setAttribute("nonce",n.nc),s.setAttribute("data-webpack",o+t),s.src=e),r[e]=[a];var l=(o,a)=>{s.onerror=s.onload=null,clearTimeout(p);var t=r[e];if(delete r[e],s.parentNode&&s.parentNode.removeChild(s),t&&t.forEach((e=>e(a))),o)return o(a)},p=setTimeout(l.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=l.bind(null,s.onerror),s.onload=l.bind(null,s.onload),c&&document.head.appendChild(s)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.p="/",(()=>{var e={461:0,6952:0,8252:0,2305:0,3364:0,2540:0,4370:0,8759:0};n.f.j=(r,o)=>{var a=n.o(e,r)?e[r]:void 0;if(0!==a)if(a)o.push(a[2]);else if(/^((69|82)52|2305|2540|3364|4370|461|8759)$/.test(r))e[r]=0;else{var t=new Promise(((o,t)=>a=e[r]=[o,t]));o.push(a[2]=t);var d=n.p+n.u(r),s=new Error;n.l(d,(o=>{if(n.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var t=o&&("load"===o.type?"missing":o.type),d=o&&o.target&&o.target.src;s.message="Loading chunk "+r+" failed.\n("+t+": "+d+")",s.name="ChunkLoadError",s.type=t,s.request=d,a[1](s)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,o)=>{var a,t,[d,s,c]=o,i=0;if(d.some((r=>0!==e[r]))){for(a in s)n.o(s,a)&&(n.m[a]=s[a]);if(c)var f=c(n)}for(r&&r(o);i<d.length;i++)t=d[i],n.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return n.O(f)},o=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];o.forEach(r.bind(null,0)),o.push=r.bind(null,o.push.bind(o))})(),n.nc=void 0})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/spa.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,12 +1,12 @@
{ {
"/js/app.js": "/js/app.js?id=32db2813fed31fece75a036e7c1e10f5", "/js/app.js": "/js/app.js?id=3af56403652514c06dd3641d925e554c",
"/js/activity.js": "/js/activity.js?id=a7e66fc4edffd7ac88264ec77ecc897f", "/js/activity.js": "/js/activity.js?id=a7e66fc4edffd7ac88264ec77ecc897f",
"/js/components.js": "/js/components.js?id=9b6f094bb7d0e43a737ed1d1756f8653", "/js/components.js": "/js/components.js?id=9b6f094bb7d0e43a737ed1d1756f8653",
"/js/discover.js": "/js/discover.js?id=0a7264152a6fcef7a5b2a2fc5775c86c", "/js/discover.js": "/js/discover.js?id=0a7264152a6fcef7a5b2a2fc5775c86c",
"/js/profile.js": "/js/profile.js?id=b96eaa039d2457b0be6dc5b94d3376cc", "/js/profile.js": "/js/profile.js?id=b96eaa039d2457b0be6dc5b94d3376cc",
"/js/status.js": "/js/status.js?id=509d9f7a92fb4cbbe3e404128c31ba42", "/js/status.js": "/js/status.js?id=509d9f7a92fb4cbbe3e404128c31ba42",
"/js/timeline.js": "/js/timeline.js?id=b7987bb7009319115cc59f71747c725a", "/js/timeline.js": "/js/timeline.js?id=719eefc202c228333e983d5b1b6c78a6",
"/js/compose.js": "/js/compose.js?id=85045be7b0a762894ea4d1fff00fd809", "/js/compose.js": "/js/compose.js?id=736397b50b183759fe47d87d5a99963b",
"/js/compose-classic.js": "/js/compose-classic.js?id=2c94b7641c2c5be0bcce17b8a5a5d359", "/js/compose-classic.js": "/js/compose-classic.js?id=2c94b7641c2c5be0bcce17b8a5a5d359",
"/js/search.js": "/js/search.js?id=df0c7f946dc70cac3292736a872864af", "/js/search.js": "/js/search.js?id=df0c7f946dc70cac3292736a872864af",
"/js/developers.js": "/js/developers.js?id=773bbb88ee8d8009f319caf35439aa46", "/js/developers.js": "/js/developers.js?id=773bbb88ee8d8009f319caf35439aa46",
@ -14,42 +14,43 @@
"/js/collectioncompose.js": "/js/collectioncompose.js?id=b0eabee67199311da4020b8731681e3c", "/js/collectioncompose.js": "/js/collectioncompose.js?id=b0eabee67199311da4020b8731681e3c",
"/js/collections.js": "/js/collections.js?id=18008521a6dc37b73b441e634635e020", "/js/collections.js": "/js/collections.js?id=18008521a6dc37b73b441e634635e020",
"/js/profile-directory.js": "/js/profile-directory.js?id=12062dff1852e33308b71f239f79d0fe", "/js/profile-directory.js": "/js/profile-directory.js?id=12062dff1852e33308b71f239f79d0fe",
"/js/story-compose.js": "/js/story-compose.js?id=a25351b1487264fd49458d47cd8c121f", "/js/story-compose.js": "/js/story-compose.js?id=4221207ff7667c3e3015df08285cd434",
"/js/direct.js": "/js/direct.js?id=3e9c970e8ee5cc4e744a262b6b58339a", "/js/direct.js": "/js/direct.js?id=3e9c970e8ee5cc4e744a262b6b58339a",
"/js/admin.js": "/js/admin.js?id=4f07b4fac37aa56cf7db83f76a20d0d6", "/js/admin.js": "/js/admin.js?id=4f07b4fac37aa56cf7db83f76a20d0d6",
"/js/spa.js": "/js/spa.js?id=26f7ff3340fb7b2e6b90e26a7c5d8c85", "/js/spa.js": "/js/spa.js?id=79ff958a13a061359d7197cad27f0784",
"/js/stories.js": "/js/stories.js?id=f5637cea14c47edfa96df7346b724236", "/js/stories.js": "/js/stories.js?id=709b0efa81f72dd5817cf1fec5ae9ae6",
"/js/portfolio.js": "/js/portfolio.js?id=5f64242a8cccdeb9d0642c9216396192", "/js/portfolio.js": "/js/portfolio.js?id=5f64242a8cccdeb9d0642c9216396192",
"/js/account-import.js": "/js/account-import.js?id=910fa8ccd6563f4711fa4214b00e898e", "/js/account-import.js": "/js/account-import.js?id=910fa8ccd6563f4711fa4214b00e898e",
"/js/admin_invite.js": "/js/admin_invite.js?id=883bf2f76521fc6b31eb1a3d4fb915ff", "/js/admin_invite.js": "/js/admin_invite.js?id=883bf2f76521fc6b31eb1a3d4fb915ff",
"/js/landing.js": "/js/landing.js?id=ff9ad6c41257eed13f3a275aab4e7faf", "/js/landing.js": "/js/landing.js?id=9bbb11ca7e1125f64fe950995a312b27",
"/js/remote_auth.js": "/js/remote_auth.js?id=1a952303a4e5c6651960a3b92b9f5134", "/js/remote_auth.js": "/js/remote_auth.js?id=1a952303a4e5c6651960a3b92b9f5134",
"/js/groups.js": "/js/groups.js?id=328b41b473d4890abed8b24d3c482cef", "/js/groups.js": "/js/groups.js?id=328b41b473d4890abed8b24d3c482cef",
"/js/group-status.js": "/js/group-status.js?id=6dfd59071c48823ed2ca6665e2784f01", "/js/group-status.js": "/js/group-status.js?id=6dfd59071c48823ed2ca6665e2784f01",
"/js/group-topic-feed.js": "/js/group-topic-feed.js?id=ad7231d9f18463043725102768672aee", "/js/group-topic-feed.js": "/js/group-topic-feed.js?id=ad7231d9f18463043725102768672aee",
"/js/custom_filters.js": "/js/custom_filters.js?id=8502df28c2e32bcc1e0b4bd32eb29193", "/js/custom_filters.js": "/js/custom_filters.js?id=3499c9be39421801e078e328f450c368",
"/js/manifest.js": "/js/manifest.js?id=f83beefb303abd604a2f44e0ab4f6f3b", "/js/settings.js": "/js/settings.js?id=9981b613df142bdade392653b72fd115",
"/js/home.chunk.7b3c50ff0f7828a4.js": "/js/home.chunk.7b3c50ff0f7828a4.js?id=01c02c2bd0ac9550ccb1f806cdccb398", "/js/manifest.js": "/js/manifest.js?id=c5929e29892f262c247f7235be876fb7",
"/js/compose.chunk.80e32f21442c8a91.js": "/js/compose.chunk.80e32f21442c8a91.js?id=c27c7ab6f212ffbdbf58f532133ef610", "/js/home.chunk.cf3e6ccd3b76689d.js": "/js/home.chunk.cf3e6ccd3b76689d.js?id=85e86bff5ccc23ca16b7295b5c7eb87e",
"/js/post.chunk.d0c8b400a930b92a.js": "/js/post.chunk.d0c8b400a930b92a.js?id=951d91fc992d6830148c399f6c538fe3", "/js/compose.chunk.8292176da8a20099.js": "/js/compose.chunk.8292176da8a20099.js?id=01a6029734e6500d811c6f81676498d9",
"/js/profile.chunk.5d560ecb7d4a57ce.js": "/js/profile.chunk.5d560ecb7d4a57ce.js?id=bab41ffcc0a7ac25f550abadddaa9d7d", "/js/post.chunk.cdef3ec51a723c2f.js": "/js/post.chunk.cdef3ec51a723c2f.js?id=c084f8d15aa02d57d1b79d1786cbc294",
"/js/discover~memories.chunk.8ea5b8e37111f15f.js": "/js/discover~memories.chunk.8ea5b8e37111f15f.js?id=12d2cb68321989ad4ae58cab7eaa8a1a", "/js/profile.chunk.5b03b78ed621f690.js": "/js/profile.chunk.5b03b78ed621f690.js?id=624ddb405d62a0045a9d45db6ef30202",
"/js/discover~myhashtags.chunk.57eeb9257cb300fd.js": "/js/discover~myhashtags.chunk.57eeb9257cb300fd.js?id=80310e89a9766bc8c00e0291c425e462", "/js/discover~memories.chunk.8601596a52c06bfc.js": "/js/discover~memories.chunk.8601596a52c06bfc.js?id=6f1a6685b5d3002982649a3164dee27a",
"/js/daci.chunk.4eaae509ed4a084c.js": "/js/daci.chunk.4eaae509ed4a084c.js?id=3dd9fbed9b40b2565e26aa2865e22796", "/js/discover~myhashtags.chunk.9b2cd210943ec613.js": "/js/discover~myhashtags.chunk.9b2cd210943ec613.js?id=5bdc452ffd26211af604cc863f6eb841",
"/js/discover~findfriends.chunk.2ccaf3c586ba03fc.js": "/js/discover~findfriends.chunk.2ccaf3c586ba03fc.js?id=f1133bf060c6741275a5c7765882e183", "/js/daci.chunk.0903327306251770.js": "/js/daci.chunk.0903327306251770.js?id=e7a63b616c633790837997728c8d1e3e",
"/js/discover~serverfeed.chunk.b7e1082a3be6ef4c.js": "/js/discover~serverfeed.chunk.b7e1082a3be6ef4c.js?id=f69e3eadcb0337334e4e71cee612370b", "/js/discover~findfriends.chunk.c3db8f429e763088.js": "/js/discover~findfriends.chunk.c3db8f429e763088.js?id=8b6cb82544dc61bb34189d745f1ee7b0",
"/js/discover~settings.chunk.edeee5803151d4eb.js": "/js/discover~settings.chunk.edeee5803151d4eb.js?id=46a0f1965f37a7d539a65ebe76d1310c", "/js/discover~serverfeed.chunk.7eeef300c5b29e82.js": "/js/discover~serverfeed.chunk.7eeef300c5b29e82.js?id=7ec546b57d8fa61809d16a93b796c74c",
"/js/discover.chunk.0ca404627af971f2.js": "/js/discover.chunk.0ca404627af971f2.js?id=fb662f204f0a3d50ce8e7ee65f5499d1", "/js/discover~settings.chunk.80c4e5afc970254e.js": "/js/discover~settings.chunk.80c4e5afc970254e.js?id=95ea824466054b10d989e104f94e5146",
"/js/notifications.chunk.a8193668255b2c9a.js": "/js/notifications.chunk.a8193668255b2c9a.js?id=00edadf32d620edca819d5308873a4e7", "/js/discover.chunk.8698471944aa4417.js": "/js/discover.chunk.8698471944aa4417.js?id=3115608f5d422952b4566ce2c2af6e88",
"/js/dms.chunk.13449036a5b769e6.js": "/js/dms.chunk.13449036a5b769e6.js?id=e78688a49ad274ca3bc4cc7bc54a20c4", "/js/notifications.chunk.eb78183fd97a9f0f.js": "/js/notifications.chunk.eb78183fd97a9f0f.js?id=8e2106f74f704acbc8622679bbb5c043",
"/js/dms~message.chunk.f0d6ccb6f2f1cbf7.js": "/js/dms~message.chunk.f0d6ccb6f2f1cbf7.js?id=e130002bd287f084ffca6de9dd758e9d", "/js/dms.chunk.746342b9470dc71f.js": "/js/dms.chunk.746342b9470dc71f.js?id=577554dcdcd9f209df03a51fa0a17a64",
"/js/dms~message.chunk.8cdd27784f95ee11.js": "/js/dms~message.chunk.8cdd27784f95ee11.js?id=3a2b367bfb98fc30720536e79c6b7f11",
"/js/profile~followers.bundle.9d2008cfa13a6f17.js": "/js/profile~followers.bundle.9d2008cfa13a6f17.js?id=6e9c0c2c42d55c4c3db48aacda336e69", "/js/profile~followers.bundle.9d2008cfa13a6f17.js": "/js/profile~followers.bundle.9d2008cfa13a6f17.js?id=6e9c0c2c42d55c4c3db48aacda336e69",
"/js/profile~following.bundle.8ebe39a19638db1b.js": "/js/profile~following.bundle.8ebe39a19638db1b.js?id=239a879240723ec8cef74958f10167e9", "/js/profile~following.bundle.8ebe39a19638db1b.js": "/js/profile~following.bundle.8ebe39a19638db1b.js?id=239a879240723ec8cef74958f10167e9",
"/js/discover~hashtag.bundle.fffb7ab6f02db6fe.js": "/js/discover~hashtag.bundle.fffb7ab6f02db6fe.js?id=f646e0ab831c0f468fcdc22767b269c8", "/js/discover~hashtag.bundle.3f6d5e3bb2865a61.js": "/js/discover~hashtag.bundle.3f6d5e3bb2865a61.js?id=5ee2e237101166c26614eb2e9cb6ce10",
"/js/error404.bundle.f5958c1713b4ab7c.js": "/js/error404.bundle.f5958c1713b4ab7c.js?id=0dc878fd60f73c85280b293b6d6c091a", "/js/error404.bundle.f5958c1713b4ab7c.js": "/js/error404.bundle.f5958c1713b4ab7c.js?id=0dc878fd60f73c85280b293b6d6c091a",
"/js/i18n.bundle.85976a3b9d6b922a.js": "/js/i18n.bundle.85976a3b9d6b922a.js?id=62e1a930a6b89be0b6a72613ec578fb4", "/js/i18n.bundle.ff6f2af48fd2e3d5.js": "/js/i18n.bundle.ff6f2af48fd2e3d5.js?id=53d79a3c26cf157ed59666bd34fb50e1",
"/js/changelog.bundle.efd3d17aee17020e.js": "/js/changelog.bundle.efd3d17aee17020e.js?id=777875be1b3bf4d1520aafc55e71c4c4", "/js/changelog.bundle.8ee4f1174f52ec8b.js": "/js/changelog.bundle.8ee4f1174f52ec8b.js?id=67ed69c96b0ba421bd2650398198f6a1",
"/js/group.create.38102523ebf4cde9.js": "/js/group.create.38102523ebf4cde9.js?id=cf5c3c1448073dc9eaef3b5735dcec9d", "/js/group.create.e34ad5621d07870d.js": "/js/group.create.e34ad5621d07870d.js?id=7c8f2d6ec452eba7b96ad40e53170ce2",
"/js/groups-post.e160e406bdb4a1b0.js": "/js/groups-post.e160e406bdb4a1b0.js?id=db88fbac6ebb84e1586876b86e39409f", "/js/groups-post.e160e406bdb4a1b0.js": "/js/groups-post.e160e406bdb4a1b0.js?id=db88fbac6ebb84e1586876b86e39409f",
"/js/groups-profile.58b5bf1af4d0722e.js": "/js/groups-profile.58b5bf1af4d0722e.js?id=4c99d316874401c5b64d37bf99e1c645", "/js/groups-profile.58b5bf1af4d0722e.js": "/js/groups-profile.58b5bf1af4d0722e.js?id=4c99d316874401c5b64d37bf99e1c645",
"/js/groups-page-about.16d96a32748daa93.js": "/js/groups-page-about.16d96a32748daa93.js?id=758b8b197bb4c2e5ded6ef4179dbf55f", "/js/groups-page-about.16d96a32748daa93.js": "/js/groups-page-about.16d96a32748daa93.js?id=758b8b197bb4c2e5ded6ef4179dbf55f",

@ -1,37 +1,39 @@
<template> <template>
<div class="footer-component"> <div class="footer-component">
<div class="footer-component-links"> <div class="footer-component-links">
<a href="/site/help">Help</a> <a href="/site/help">Help</a>
<div class="spacer">·</div> <div class="spacer">·</div>
<a href="/site/terms">Terms</a> <a href="/site/terms">Terms</a>
<div class="spacer">·</div> <div class="spacer">·</div>
<a href="/site/privacy">Privacy</a> <a href="/site/privacy">Privacy</a>
<div class="spacer">·</div> <div class="spacer">·</div>
<a href="https://pixelfed.org/mobile-apps" target="_blank">Mobile Apps</a> <a v-if="config.show_legal_notice_link" href="/site/legal-notice">Legal Notice</a>
</div> <div v-if="config.show_legal_notice_link" class="spacer">·</div>
<a href="https://pixelfed.org/mobile-apps" target="_blank">Mobile Apps</a>
</div>
<div class="footer-component-attribution"> <div class="footer-component-attribution">
<div><span>© {{ getYear() }} {{config.domain}}</span></div> <div><span>© {{ getYear() }} {{ config.domain }}</span></div>
<div class="spacer">·</div> <div class="spacer">·</div>
<div><a href="https://pixelfed.org" class="text-bluegray-500 font-weight-bold">Powered by Pixelfed</a></div> <div><a href="https://pixelfed.org" class="text-bluegray-500 font-weight-bold">Powered by Pixelfed</a></div>
<div class="spacer">·</div> <div class="spacer">·</div>
<div><span>v{{config.version}}</span></div> <div><span>v{{ config.version }}</span></div>
</div> </div>
</div> </div>
</template> </template>
<script type="text/javascript"> <script type="text/javascript">
export default { export default {
data() { data() {
return { return {
config: window.pfl config: window.pfl
} }
}, },
methods: { methods: {
getYear() { getYear() {
return (new Date().getFullYear()); return (new Date().getFullYear());
} }
} }
} }
</script> </script>

File diff suppressed because it is too large Load Diff

@ -10,7 +10,7 @@
<div class="story-wrapper-blur d-flex flex-column align-items-center justify-content-between" style="display: block;width: 100%;height:100%;"> <div class="story-wrapper-blur d-flex flex-column align-items-center justify-content-between" style="display: block;width: 100%;height:100%;">
<p class="mb-4"></p> <p class="mb-4"></p>
<p class="mb-0"><i class="fal fa-plus-circle fa-2x"></i></p> <p class="mb-0"><i class="fal fa-plus-circle fa-2x"></i></p>
<p class="font-weight-bold">My Story</p> <p class="font-weight-bold">{{ $t("story.myStory")}}</p>
</div> </div>
</div> </div>
</template> </template>

@ -108,7 +108,13 @@
</div> </div>
<div v-else-if="n.type == 'share'"> <div v-else-if="n.type == 'share'">
<p class="my-0"> <p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.acct">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> {{ $t("notifications.shared")}} <a class="font-weight-bold" :href="getPostUrl(n.status)" @click.prevent="goToPost(n.status)">{{ $t("notifications.post")}}</a>. <a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.acct">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> {{ $t("notifications.shared")}}
<span v-if="n.status && n.status.hasOwnProperty('media_attachments')">
<a class="font-weight-bold" v-bind:href="getPostUrl(n.status)" :id="'fvn-' + n.id" @click.prevent="goToPost(n.status)">{{ $t("notifications.post")}}</a>.
<b-popover :target="'fvn-' + n.id" title="" triggers="hover" placement="top" boundary="window">
<img :src="notificationPreview(n)" width="100px" height="100px" style="object-fit: cover;">
</b-popover>
</span>
</p> </p>
</div> </div>
<div v-else-if="n.type == 'modlog'"> <div v-else-if="n.type == 'modlog'">

File diff suppressed because it is too large Load Diff

@ -4,12 +4,12 @@
<div class="card-header bg-white"> <div class="card-header bg-white">
<p class="mb-0 d-flex align-items-center justify-content-between"> <p class="mb-0 d-flex align-items-center justify-content-between">
<span class="text-muted font-weight-bold">Stories</span> <span class="text-muted font-weight-bold">Stories</span>
<a class="text-dark small" href="/account/activity">See All</a> <a class="text-dark small" href="/account/activity">{{ $t("story.seeAll") }}</a>
</p> </p>
</div> </div>
<div class="card-body loader text-center" style="height: 120px;"> <div class="card-body loader text-center" style="height: 120px;">
<div class="spinner-border" role="status"> <div class="spinner-border" role="status">
<span class="sr-only">Loading</span> <span class="sr-only">{{ $t('common.loading') }}</span>
</div> </div>
</div> </div>
<div class="card-body pt-2 contents" style="max-height: 120px; overflow-y: scroll;"> <div class="card-body pt-2 contents" style="max-height: 120px; overflow-y: scroll;">

@ -14,11 +14,11 @@
<p class="lead text-lighter font-weight-light mb-0">Stories</p> <p class="lead text-lighter font-weight-light mb-0">Stories</p>
</div> </div>
<div class="flex-fill py-4"> <div class="flex-fill py-4">
<p class="text-center lead font-weight-light text-lighter mb-4">Share moments with followers that last 24 hours</p> <p class="text-center lead font-weight-light text-lighter mb-4">{{ $t("story.shareWithFollowers")}}</p>
<div class="card w-100 shadow-none bg-transparent"> <div class="card w-100 shadow-none bg-transparent">
<div class="d-flex"> <div class="d-flex">
<button type="button" class="btn btn-outline-light btn-lg font-weight-bold btn-block rounded-pill my-1" :disabled="stories.length >= 20" @click="upload()"> <button type="button" class="btn btn-outline-light btn-lg font-weight-bold btn-block rounded-pill my-1" :disabled="stories.length >= 20" @click="upload()">
Add to Story {{ $t("story.add")}}
</button> </button>
<!-- <button :disabled="stories.length >= 20" type="button" class="btn btn-outline-light btn-lg font-weight-bold btn-block rounded-pill my-1 ml-2" @click="newPoll"> <!-- <button :disabled="stories.length >= 20" type="button" class="btn btn-outline-light btn-lg font-weight-bold btn-block rounded-pill my-1 ml-2" @click="newPoll">
Create Poll Create Poll
@ -27,7 +27,7 @@
<p <p
v-if="stories.length >= 20" v-if="stories.length >= 20"
class="font-weight-bold text-muted text-center"> class="font-weight-bold text-muted text-center">
You have reached the limit for new stories {{ $t("story.limit") }}
</p> </p>
<button <button
@ -35,7 +35,7 @@
class="btn btn-outline-light btn-lg font-weight-bold btn-block rounded-pill my-3" class="btn btn-outline-light btn-lg font-weight-bold btn-block rounded-pill my-3"
@click="viewMyStory" @click="viewMyStory"
:disabled="stories.length == 0"> :disabled="stories.length == 0">
<span>My Story</span> <span>{{ $t("story.myStory") }}</span>
<sup v-if="stories.length" class="ml-2 px-2 text-light bg-danger rounded-pill" style="font-size: 12px;padding-top:2px;padding-bottom:3px;">{{ stories.length }}</sup> <sup v-if="stories.length" class="ml-2 px-2 text-light bg-danger rounded-pill" style="font-size: 12px;padding-top:2px;padding-bottom:3px;">{{ stories.length }}</sup>
</button> </button>
@ -45,7 +45,7 @@
<p class="text-uppercase mb-0"> <p class="text-uppercase mb-0">
<a href="/" class="text-lighter font-weight-bold">Home</a> <a href="/" class="text-lighter font-weight-bold">Home</a>
<span class="px-2 text-lighter">|</span> <span class="px-2 text-lighter">|</span>
<a href="/site/help" class="text-lighter font-weight-bold">Help</a> <a href="/site/help" class="text-lighter font-weight-bold">{{ $t("navmenu.help")}}</a>
</p> </p>
<p class="small text-muted mb-0">v 1.0.0</p> <p class="small text-muted mb-0">v 1.0.0</p>
</div> </div>
@ -73,19 +73,19 @@
type="button" type="button"
class="btn btn-outline-muted rounded-pill font-weight-bold px-4" class="btn btn-outline-muted rounded-pill font-weight-bold px-4"
@click="deleteCurrentStory()"> @click="deleteCurrentStory()">
Cancel {{ $t("story.cancel")}}
</button> </button>
<div class="text-center"> <div class="text-center">
<h4 class="font-weight-light text-light mb-n1">Crop</h4> <h4 class="font-weight-light text-light mb-n1">{{ $t("story.crop")}}</h4>
<span class="small text-light">Pan around and pinch to zoom</span> <span class="small text-light">{{ $t("story.zoom") }}</span>
</div> </div>
<button <button
type="button" type="button"
class="btn btn-outline-light rounded-pill font-weight-bold px-4" class="btn btn-outline-light rounded-pill font-weight-bold px-4"
@click="performCrop()"> @click="performCrop()">
Next {{ $t("story.next") }}
</button> </button>
</div> </div>
</div> </div>
@ -97,23 +97,23 @@
<p class="lead text-lighter font-weight-light mb-0">Stories</p> <p class="lead text-lighter font-weight-light mb-0">Stories</p>
</div> </div>
<div class="flex-fill text-center"> <div class="flex-fill text-center">
<p class="h3 mb-0 text-light">Oops!</p> <p class="h3 mb-0 text-light">{{ $t("common.oops") }}</p>
<p class="text-muted lead">An error occurred, please try again later.</p> <p class="text-muted lead">{{ $t("common.errorMsg")}}</p>
<p class="text-muted mb-0"> <p class="text-muted mb-0">
<a class="btn btn-outline-muted py-0 px-5 rounded-pill font-weight-bold" href="/">Go back</a> <a class="btn btn-outline-muted py-0 px-5 rounded-pill font-weight-bold" href="/">{{ $t("story.goBack") }}</a>
</p> </p>
</div> </div>
</div> </div>
<div v-else-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-else-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 class="spinner-border text-lighter" role="status"> <div class="spinner-border text-lighter" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">{{ $t('common.loading') }}</span>
</div> </div>
</div> </div>
<div v-else-if="page == 'cropping'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;"> <div v-else-if="page == 'cropping'" class="card card-body bg-transparent border-0 shadow-none d-flex justify-content-center align-items-center" style="height: 90vh;">
<div class="spinner-border text-lighter" role="status"> <div class="spinner-border text-lighter" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">{{ $t('common.loading') }}</span>
</div> </div>
</div> </div>
@ -124,31 +124,31 @@
</div> </div>
<div class="flex-fill"> <div class="flex-fill">
<div class="form-group pb-3"> <div class="form-group pb-3">
<label for="durationSlider" class="text-light lead font-weight-bold">Options</label> <label for="durationSlider" class="text-light lead font-weight-bold">{{ $t("story.options") }}</label>
<div class="custom-control custom-checkbox mb-2"> <div class="custom-control custom-checkbox mb-2">
<input type="checkbox" class="custom-control-input" id="optionReplies" v-model="canReply"> <input type="checkbox" class="custom-control-input" id="optionReplies" v-model="canReply">
<label class="custom-control-label text-light font-weight-lighter" for="optionReplies">Allow replies</label> <label class="custom-control-label text-light font-weight-lighter" for="optionReplies">{{ $t("story.allowReplies") }}</label>
</div> </div>
<div class="custom-control custom-checkbox mb-2"> <div class="custom-control custom-checkbox mb-2">
<input type="checkbox" class="custom-control-input" id="formReactions" v-model="canReact"> <input type="checkbox" class="custom-control-input" id="formReactions" v-model="canReact">
<label class="custom-control-label text-light font-weight-lighter" for="formReactions">Allow reactions</label> <label class="custom-control-label text-light font-weight-lighter" for="formReactions">{{ $t("story.allowReactions") }}</label>
</div> </div>
</div> </div>
<div v-if="!canPostPoll" class="form-group"> <div v-if="!canPostPoll" class="form-group">
<video ref="previewVideo" v-if="mediaType == 'video'" class="mb-4 w-100" style="max-height:200px;object-fit:contain;"> <video ref="previewVideo" v-if="mediaType == 'video'" class="mb-4 w-100" style="max-height:200px;object-fit:contain;">
<source :src="mediaUrl" type="video/mp4"> <source :src="mediaUrl" type="video/mp4">
</video> </video>
<label for="durationSlider" class="text-light lead font-weight-bold">Story Duration</label> <label for="durationSlider" class="text-light lead font-weight-bold">{{ $t("story.storyDuration") }}</label>
<input type="range" class="custom-range" min="3" :max="max_duration" step="1" id="durationSlider" v-model="duration"> <input type="range" class="custom-range" min="3" :max="max_duration" step="1" id="durationSlider" v-model="duration">
<p class="help-text text-center"> <p class="help-text text-center">
<span class="text-light">{{duration}} seconds</span> <span class="text-light">{{duration}} {{ $t("story.seconds") }}</span>
</p> </p>
</div> </div>
</div> </div>
<div class="flex-fill w-100 px-md-5"> <div class="flex-fill w-100 px-md-5">
<div class="d-flex"> <div class="d-flex">
<a class="btn btn-outline-muted btn-block font-weight-bold my-3 mr-3 rounded-pill" href="/" @click.prevent="deleteCurrentStory()"> <a class="btn btn-outline-muted btn-block font-weight-bold my-3 mr-3 rounded-pill" href="/" @click.prevent="deleteCurrentStory()">
Cancel {{ $t("story.cancel") }}
</a> </a>
<a class="btn btn-primary btn-block font-weight-bold my-3 rounded-pill" href="#" @click.prevent="shareStoryToFollowers()"> <a class="btn btn-primary btn-block font-weight-bold my-3 rounded-pill" href="#" @click.prevent="shareStoryToFollowers()">
@ -166,7 +166,7 @@
<p class="text-muted font-weight-bold mb-0">STORIES</p> <p class="text-muted font-weight-bold mb-0">STORIES</p>
</div> </div>
<div class="flex-fill py-4"> <div class="flex-fill py-4">
<p class="lead font-weight-bold text-lighter">My Stories</p> <p class="lead font-weight-bold text-lighter">{{ $t('story.myStories') }}</p>
<div class="card w-100 shadow-none bg-transparent" style="max-height: 50vh; overflow-y: scroll"> <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 bg-transparent text-center border-muted text-lighter" href="#"> <div v-for="(story, index) in stories" class="list-group-item bg-transparent text-center border-muted text-lighter" href="#">
@ -187,7 +187,7 @@
</div> </div>
</div> </div>
<div v-if="story.showViewers && story.viewers.length" class="m-2 text-left"> <div v-if="story.showViewers && story.viewers.length" class="m-2 text-left">
<p class="font-weight-bold mb-2">Viewed By</p> <p class="font-weight-bold mb-2">{{ $t("story.viewdBy") }}</p>
<div v-for="viewer in story.viewers" class="d-flex"> <div v-for="viewer in story.viewers" class="d-flex">
<img src="/storage/avatars/default.png" width="24" height="24" class="rounded-circle mr-2"> <img src="/storage/avatars/default.png" width="24" height="24" class="rounded-circle mr-2">
<p class="mb-0 font-weight-bold">viewer.username</p> <p class="mb-0 font-weight-bold">viewer.username</p>
@ -198,7 +198,7 @@
</div> </div>
</div> </div>
<div class="flex-fill text-center"> <div class="flex-fill text-center">
<a class="btn btn-outline-secondary btn-block px-5 font-weight-bold" href="/i/stories/new" @click.prevent="goBack()">Go back</a> <a class="btn btn-outline-secondary btn-block px-5 font-weight-bold" href="/i/stories/new" @click.prevent="goBack()">{{ $t("story.goBack") }}</a>
</div> </div>
</div> </div>
@ -237,8 +237,8 @@
</div> </div>
</div> </div>
<div class="flex-fill text-center"> <div class="flex-fill text-center">
<a v-if="canPostPoll" class="btn btn-outline-light btn-block px-5 font-weight-bold rounded-pill" href="/i/stories/new" @click.prevent="pollPreview">Next</a> <a v-if="canPostPoll" class="btn btn-outline-light btn-block px-5 font-weight-bold rounded-pill" href="/i/stories/new" @click.prevent="pollPreview">{{ $t("story.next")}}</a>
<a class="btn btn-outline-secondary btn-block px-5 font-weight-bold rounded-pill" href="/i/stories/new" @click.prevent="goBack()">Go back</a> <a class="btn btn-outline-secondary btn-block px-5 font-weight-bold rounded-pill" href="/i/stories/new" @click.prevent="goBack()">{{ $t('story.goBack')}}</a>
</div> </div>
</div> </div>
</div> </div>
@ -247,7 +247,7 @@
<div class="col-12 col-md-6 offset-md-3 bg-dark rounded-lg px-0" style="height: 90vh;"> <div class="col-12 col-md-6 offset-md-3 bg-dark rounded-lg px-0" style="height: 90vh;">
<div class="w-100 h-100 d-flex justify-content-center align-items-center"> <div class="w-100 h-100 d-flex justify-content-center align-items-center">
<div class="spinner-border text-lighter" role="status"> <div class="spinner-border text-lighter" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">{{ $t('common.loading') }}</span>
</div> </div>
</div> </div>
</div> </div>

@ -3,7 +3,7 @@
<div v-if="show" class="card card-body p-0 border mt-md-4 mb-md-3 shadow-none"> <div v-if="show" class="card card-body p-0 border mt-md-4 mb-md-3 shadow-none">
<div v-if="loading" class="w-100 h-100 d-flex align-items-center justify-content-center"> <div v-if="loading" class="w-100 h-100 d-flex align-items-center justify-content-center">
<div class="spinner-border spinner-border-sm text-lighter" role="status"> <div class="spinner-border spinner-border-sm text-lighter" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">{{ $t('common.loading') }}</span>
</div> </div>
</div> </div>
<div v-else class="d-flex align-items-center justify-content-start scrolly"> <div v-else class="d-flex align-items-center justify-content-start scrolly">

@ -15,13 +15,13 @@
<div v-if="activeReactionEmoji" style="position: absolute;z-index: 999;" class="w-100 h-100 d-flex justify-content-center align-items-center"> <div v-if="activeReactionEmoji" style="position: absolute;z-index: 999;" class="w-100 h-100 d-flex justify-content-center align-items-center">
<div class="d-flex justify-content-center align-items-center rounded-pill shadow-lg" style="width: 120px;height: 30px;font-size:13px;background-color: rgba(0, 0, 0, 0.6);"> <div class="d-flex justify-content-center align-items-center rounded-pill shadow-lg" style="width: 120px;height: 30px;font-size:13px;background-color: rgba(0, 0, 0, 0.6);">
<span class="text-lighter">Reaction sent</span> <span class="text-lighter">{{ $t("story.reactionSent") }}</span>
</div> </div>
</div> </div>
<div v-if="activeReply" style="position: absolute;z-index: 999;" class="w-100 h-100 d-flex justify-content-center align-items-center"> <div v-if="activeReply" style="position: absolute;z-index: 999;" class="w-100 h-100 d-flex justify-content-center align-items-center">
<div class="d-flex justify-content-center align-items-center rounded-pill shadow-lg" style="width: 120px;height: 30px;font-size:13px;background-color: rgba(0, 0, 0, 0.6);"> <div class="d-flex justify-content-center align-items-center rounded-pill shadow-lg" style="width: 120px;height: 30px;font-size:13px;background-color: rgba(0, 0, 0, 0.6);">
<span class="text-lighter">Reply sent</span> <span class="text-lighter">{{ $t("story.replySent") }}</span>
</div> </div>
</div> </div>
@ -217,7 +217,7 @@
<div class="list-group text-center"> <div class="list-group text-center">
<div v-if="owner" class="list-group-item rounded py-3"> <div v-if="owner" class="list-group-item rounded py-3">
<div class="d-flex justify-content-between align-items-center font-weight-light"> <div class="d-flex justify-content-between align-items-center font-weight-light">
<span>Expires in {{timeahead(stories[storyIndex].expires_at)}}</span> <span>{{ $t("story.expiresIn")}} {{timeahead(stories[storyIndex].expires_at)}}</span>
<span> <span>
<span class="btn btn-light btn-sm font-weight-bold"> <span class="btn btn-light btn-sm font-weight-bold">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
@ -235,10 +235,10 @@
{{ e }} {{ e }}
</button> </button>
</div> </div>
<div v-if="owner" class="list-group-item rounded cursor-pointer" @click="fetchViewers">Viewers</div> <div v-if="owner" class="list-group-item rounded cursor-pointer" @click="fetchViewers">{{ $t("story.viewers")}}</div>
<div v-if="!owner" class="list-group-item rounded cursor-pointer" @click="ctxMenuReport">Report</div> <div v-if="!owner" class="list-group-item rounded cursor-pointer" @click="ctxMenuReport">>{{ $t("story.report")}}</div>
<div v-if="owner" class="list-group-item rounded cursor-pointer" @click="deleteStory">Delete</div> <div v-if="owner" class="list-group-item rounded cursor-pointer" @click="deleteStory">{{ $t("story.delete")}}</div>
<div class="list-group-item rounded cursor-pointer text-muted" @click="closeCtxMenu">Close</div> <div class="list-group-item rounded cursor-pointer text-muted" @click="closeCtxMenu">{{ $t("story.close")}}</div>
</div> </div>
</b-modal> </b-modal>
@ -272,7 +272,7 @@
<div v-if="viewersHasMore" class="list-group-item text-center border-bottom-0"> <div v-if="viewersHasMore" class="list-group-item text-center border-bottom-0">
<button class="btn btn-light font-weight-bold border rounded-pill" @click="viewersLoadMore">Load More</button> <button class="btn btn-light font-weight-bold border rounded-pill" @click="viewersLoadMore">Load More</button>
</div> </div>
<div class="list-group-item text-center rounded cursor-pointer text-muted" @click="closeViewersModal">Close</div> <div class="list-group-item text-center rounded cursor-pointer text-muted" @click="closeViewersModal">{{ $t("story.close")}}</div>
</div> </div>
</b-modal> </b-modal>

@ -9,10 +9,10 @@
</div> </div>
<div class="text-muted">·</div> <div class="text-muted">·</div>
<div v-if="filter.expires_at" class="small text-muted"> <div v-if="filter.expires_at" class="small text-muted">
Expires: {{ formatExpiry(filter.expires_at) }} {{ $t('settings.filters.expires') }}: {{ formatExpiry(filter.expires_at) }}
</div> </div>
<div v-else class="small text-muted"> <div v-else class="small text-muted">
Never expires {{ $t('settings.filters.never_expires') }}
</div> </div>
</div> </div>
<div> <div>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save