Bläddra i källkod

Merge branch 'pixelfed:dev' into dev

Norbert Tretkowski 1 år sedan
förälder
incheckning
8f3ba930ce

+ 1 - 1
.env.docker

@@ -67,7 +67,7 @@ ADMIN_DOMAIN="${APP_DOMAIN}"
 # @default "false"
 # @see https://docs.pixelfed.org/technical-documentation/config/#config_cache
 # @dottie/validate required,boolean
-ENABLE_CONFIG_CACHE="false"
+ENABLE_CONFIG_CACHE="true"
 
 # Enable/disable new local account registrations.
 #

+ 1 - 0
.env.example

@@ -8,6 +8,7 @@ OPEN_REGISTRATION="false"
 ENFORCE_EMAIL_VERIFICATION="false"
 PF_MAX_USERS="1000"
 OAUTH_ENABLED="true"
+ENABLE_CONFIG_CACHE=true
 
 # Media Configuration
 PF_OPTIMIZE_IMAGES="true"

+ 19 - 2
CHANGELOG.md

@@ -1,6 +1,19 @@
 # Release Notes
 
-## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.13...dev)
+## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.1...dev)
+
+### Updates
+- Update DirectMessageController, add 72 hour delay for new accounts before they can send a DM ([61d105fd](https://github.com/pixelfed/pixelfed/commit/61d105fd))
+- Update AdminCuratedRegisterController, increase message length from 1000 to 3000 ([9a5e3471](https://github.com/pixelfed/pixelfed/commit/))
+-  ([](https://github.com/pixelfed/pixelfed/commit/9a5e3471))
+
+## [v0.12.1 (2024-05-07)](https://github.com/pixelfed/pixelfed/compare/v0.12.0...v0.12.1)
+
+### Updates
+- Update ApiV1Dot1Controller, fix in app registration bug that prevents proper auth flow due to missing oauth scopes ([cbf996c9](https://github.com/pixelfed/pixelfed/commit/cbf996c9))
+- Update ConfigCacheService, fix database race condition and fallback to file config and enable by default ([60a62b59](https://github.com/pixelfed/pixelfed/commit/60a62b59))
+
+## [v0.12.0 (2024-04-29)](https://github.com/pixelfed/pixelfed/compare/v0.11.13...v0.12.0)
 
 ### Updates
 
@@ -66,7 +79,11 @@
 - Update UnfollowPipeline, fix follower count cache bug ([6bdf73de](https://github.com/pixelfed/pixelfed/commit/6bdf73de))
 - Update VideoPresenter component, add webkit-playsinline attribute to video element to prevent the full screen video player ([ad032916](https://github.com/pixelfed/pixelfed/commit/ad032916))
 - Update VideoPlayer component, add playsinline attribute to video element ([8af23607](https://github.com/pixelfed/pixelfed/commit/8af23607))
--  ([](https://github.com/pixelfed/pixelfed/commit/))
+- Update StatusController, refactor status embeds ([9a7acc12](https://github.com/pixelfed/pixelfed/commit/9a7acc12))
+- Update ProfileController, refactor profile embeds ([8b8b1ffc](https://github.com/pixelfed/pixelfed/commit/8b8b1ffc))
+- Update profile embed view, fix height bug ([65166570](https://github.com/pixelfed/pixelfed/commit/65166570))
+- Update CustomEmojiService, only return local emoji ([7f8bba44](https://github.com/pixelfed/pixelfed/commit/7f8bba44))
+- Update Like model, increase max likes per day from 500 to 1500 ([4223119f](https://github.com/pixelfed/pixelfed/commit/4223119f))
 
 ## [v0.11.13 (2024-03-05)](https://github.com/pixelfed/pixelfed/compare/v0.11.12...v0.11.13)
 

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

@@ -174,7 +174,7 @@ class AdminCuratedRegisterController extends Controller
     public function apiMessageSendStore(Request $request, $id)
     {
         $this->validate($request, [
-            'message' => 'required|string|min:5|max:1000',
+            'message' => 'required|string|min:5|max:3000',
         ]);
         $record = CuratedRegister::findOrFail($id);
         abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 881 - 876
app/Http/Controllers/Api/ApiV1Dot1Controller.php


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

@@ -309,6 +309,7 @@ class DirectMessageController extends Controller
 
         $user = $request->user();
         abort_if($user->has_roles && !UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
+        abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account');
         $profile = $user->profile;
         $recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
 

+ 4 - 4
app/Http/Controllers/ProfileController.php

@@ -172,7 +172,7 @@ class ProfileController extends Controller
 
         $user = $this->getCachedUser($username);
 
-        abort_if(!$user, 404);
+        abort_if(! $user, 404);
 
         return redirect($user->url());
     }
@@ -254,7 +254,7 @@ class ProfileController extends Controller
 
         abort_if(! $profile || $profile['locked'] || ! $profile['local'], 404);
 
-        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 86400, function () use ($profile) {
+        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 3600, function () use ($profile) {
             $uid = User::whereProfileId($profile['id'])->first();
             if (! $uid) {
                 return true;
@@ -348,7 +348,7 @@ class ProfileController extends Controller
             return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
         }
 
-        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
+        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 3600, function () use ($profile) {
             $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
             if ($exists) {
                 return true;
@@ -373,7 +373,7 @@ class ProfileController extends Controller
 
     public function stories(Request $request, $username)
     {
-        abort_if(!(bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
+        abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
         $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
         $pid = $profile->id;
         $authed = Auth::user()->profile_id;

+ 34 - 14
app/Http/Controllers/StatusController.php

@@ -8,6 +8,7 @@ use App\Jobs\SharePipeline\UndoSharePipeline;
 use App\Jobs\StatusPipeline\RemoteStatusDelete;
 use App\Jobs\StatusPipeline\StatusDelete;
 use App\Profile;
+use App\Services\AccountService;
 use App\Services\HashidService;
 use App\Services\ReblogService;
 use App\Services\StatusService;
@@ -113,19 +114,33 @@ class StatusController extends Controller
             return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
         }
 
-        $profile = Profile::whereNull(['domain', 'status'])
-            ->whereIsPrivate(false)
-            ->whereUsername($username)
-            ->first();
+        $status = StatusService::get($id);
 
-        if (! $profile) {
+        if (
+            ! $status ||
+            ! isset($status['account'], $status['account']['id'], $status['local']) ||
+            ! $status['local'] ||
+            strtolower($status['account']['username']) !== strtolower($username)
+        ) {
+            $content = view('status.embed-removed');
+
+            return response($content, 404)->header('X-Frame-Options', 'ALLOWALL');
+        }
+
+        $profile = AccountService::get($status['account']['id'], true);
+
+        if (! $profile || $profile['locked'] || ! $profile['local']) {
             $content = view('status.embed-removed');
 
             return response($content)->header('X-Frame-Options', 'ALLOWALL');
         }
 
-        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
-            $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
+        $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 3600, function () use ($profile) {
+            $user = Profile::find($profile['id']);
+            if (! $user) {
+                return true;
+            }
+            $exists = AccountInterstitial::whereUserId($user->user_id)->where('is_spam', 1)->count();
             if ($exists) {
                 return true;
             }
@@ -138,17 +153,22 @@ class StatusController extends Controller
 
             return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
         }
-        $status = Status::whereProfileId($profile->id)
-            ->whereNull('uri')
-            ->whereScope('public')
-            ->whereIsNsfw(false)
-            ->whereIn('type', ['photo', 'video', 'photo:album'])
-            ->find($id);
-        if (! $status) {
+
+        $status = StatusService::get($id);
+
+        if (
+            ! $status ||
+            ! isset($status['account'], $status['account']['id']) ||
+            intval($status['account']['id']) !== intval($profile['id']) ||
+            $status['sensitive'] ||
+            $status['visibility'] !== 'public' ||
+            $status['pf_type'] !== 'photo'
+        ) {
             $content = view('status.embed-removed');
 
             return response($content)->header('X-Frame-Options', 'ALLOWALL');
         }
+
         $showLikes = $request->filled('likes') && $request->likes == true;
         $showCaption = $request->filled('caption') && $request->caption !== false;
         $layout = $request->filled('layout') && $request->layout == 'compact' ? 'compact' : 'full';

+ 1 - 1
app/Like.php

@@ -9,7 +9,7 @@ class Like extends Model
 {
     use SoftDeletes;
 
-    const MAX_PER_DAY = 500;
+    const MAX_PER_DAY = 1500;
 
     /**
      * The attributes that should be mutated to dates.

+ 149 - 145
app/Services/ConfigCacheService.php

@@ -4,6 +4,7 @@ namespace App\Services;
 
 use App\Models\ConfigCache as ConfigCacheModel;
 use Cache;
+use Illuminate\Database\QueryException;
 
 class ConfigCacheService
 {
@@ -25,156 +26,159 @@ class ConfigCacheService
             return config($key);
         }
 
-        return Cache::remember($cacheKey, $ttl, function () use ($key) {
-
-            $allowed = [
-                'app.name',
-                'app.short_description',
-                'app.description',
-                'app.rules',
-
-                'pixelfed.max_photo_size',
-                'pixelfed.max_album_length',
-                'pixelfed.image_quality',
-                'pixelfed.media_types',
-
-                'pixelfed.open_registration',
-                'federation.activitypub.enabled',
-                'instance.stories.enabled',
-                'pixelfed.oauth_enabled',
-                'pixelfed.import.instagram.enabled',
-                'pixelfed.bouncer.enabled',
-
-                'pixelfed.enforce_email_verification',
-                'pixelfed.max_account_size',
-                'pixelfed.enforce_account_limit',
-
-                'uikit.custom.css',
-                'uikit.custom.js',
-                'uikit.show_custom.css',
-                'uikit.show_custom.js',
-                'about.title',
-
-                'pixelfed.cloud_storage',
-
-                'account.autofollow',
-                'account.autofollow_usernames',
-                'config.discover.features',
-
-                'instance.has_legal_notice',
-                'instance.avatar.local_to_cloud',
-
-                'pixelfed.directory',
-                'app.banner_image',
-                'pixelfed.directory.submission-key',
-                'pixelfed.directory.submission-ts',
-                'pixelfed.directory.has_submitted',
-                'pixelfed.directory.latest_response',
-                'pixelfed.directory.is_synced',
-                'pixelfed.directory.testimonials',
-
-                'instance.landing.show_directory',
-                'instance.landing.show_explore',
-                'instance.admin.pid',
-                'instance.banner.blurhash',
-
-                'autospam.nlp.enabled',
-
-                'instance.curated_registration.enabled',
-
-                'federation.migration',
-
-                'pixelfed.max_caption_length',
-                'pixelfed.max_bio_length',
-                'pixelfed.max_name_length',
-                'pixelfed.min_password_length',
-                'pixelfed.max_avatar_size',
-                'pixelfed.max_altext_length',
-                'pixelfed.allow_app_registration',
-                'pixelfed.app_registration_rate_limit_attempts',
-                'pixelfed.app_registration_rate_limit_decay',
-                'pixelfed.app_registration_confirm_rate_limit_attempts',
-                'pixelfed.app_registration_confirm_rate_limit_decay',
-                'instance.embed.profile',
-                'instance.embed.post',
-
-                'captcha.enabled',
-                'captcha.secret',
-                'captcha.sitekey',
-                'captcha.active.login',
-                'captcha.active.register',
-                'captcha.triggers.login.enabled',
-                'captcha.triggers.login.attempts',
-                'federation.custom_emoji.enabled',
-
-                'pixelfed.optimize_image',
-                'pixelfed.optimize_video',
-                'pixelfed.max_collection_length',
-                'media.delete_local_after_cloud',
-                'instance.user_filters.max_user_blocks',
-                'instance.user_filters.max_user_mutes',
-                'instance.user_filters.max_domain_blocks',
-
-                'filesystems.disks.s3.key',
-                'filesystems.disks.s3.secret',
-                'filesystems.disks.s3.region',
-                'filesystems.disks.s3.bucket',
-                'filesystems.disks.s3.visibility',
-                'filesystems.disks.s3.url',
-                'filesystems.disks.s3.endpoint',
-                'filesystems.disks.s3.use_path_style_endpoint',
-
-                'filesystems.disks.spaces.key',
-                'filesystems.disks.spaces.secret',
-                'filesystems.disks.spaces.region',
-                'filesystems.disks.spaces.bucket',
-                'filesystems.disks.spaces.visibility',
-                'filesystems.disks.spaces.url',
-                'filesystems.disks.spaces.endpoint',
-                'filesystems.disks.spaces.use_path_style_endpoint',
-                // 'system.user_mode'
-            ];
-
-            if (! config('instance.enable_cc')) {
-                return config($key);
-            }
-
-            if (! in_array($key, $allowed)) {
-                return config($key);
-            }
-
-            $protect = false;
-            $protected = null;
-            if(in_array($key, self::PROTECTED_KEYS)) {
-                $protect = true;
-            }
-
-            $v = config($key);
-            $c = ConfigCacheModel::where('k', $key)->first();
-
-            if ($c) {
-                if($protect) {
-                    return decrypt($c->v) ?? config($key);
-                } else {
-                    return $c->v ?? config($key);
+        try {
+            return Cache::remember($cacheKey, $ttl, function () use ($key) {
+                $allowed = [
+                    'app.name',
+                    'app.short_description',
+                    'app.description',
+                    'app.rules',
+
+                    'pixelfed.max_photo_size',
+                    'pixelfed.max_album_length',
+                    'pixelfed.image_quality',
+                    'pixelfed.media_types',
+
+                    'pixelfed.open_registration',
+                    'federation.activitypub.enabled',
+                    'instance.stories.enabled',
+                    'pixelfed.oauth_enabled',
+                    'pixelfed.import.instagram.enabled',
+                    'pixelfed.bouncer.enabled',
+
+                    'pixelfed.enforce_email_verification',
+                    'pixelfed.max_account_size',
+                    'pixelfed.enforce_account_limit',
+
+                    'uikit.custom.css',
+                    'uikit.custom.js',
+                    'uikit.show_custom.css',
+                    'uikit.show_custom.js',
+                    'about.title',
+
+                    'pixelfed.cloud_storage',
+
+                    'account.autofollow',
+                    'account.autofollow_usernames',
+                    'config.discover.features',
+
+                    'instance.has_legal_notice',
+                    'instance.avatar.local_to_cloud',
+
+                    'pixelfed.directory',
+                    'app.banner_image',
+                    'pixelfed.directory.submission-key',
+                    'pixelfed.directory.submission-ts',
+                    'pixelfed.directory.has_submitted',
+                    'pixelfed.directory.latest_response',
+                    'pixelfed.directory.is_synced',
+                    'pixelfed.directory.testimonials',
+
+                    'instance.landing.show_directory',
+                    'instance.landing.show_explore',
+                    'instance.admin.pid',
+                    'instance.banner.blurhash',
+
+                    'autospam.nlp.enabled',
+
+                    'instance.curated_registration.enabled',
+
+                    'federation.migration',
+
+                    'pixelfed.max_caption_length',
+                    'pixelfed.max_bio_length',
+                    'pixelfed.max_name_length',
+                    'pixelfed.min_password_length',
+                    'pixelfed.max_avatar_size',
+                    'pixelfed.max_altext_length',
+                    'pixelfed.allow_app_registration',
+                    'pixelfed.app_registration_rate_limit_attempts',
+                    'pixelfed.app_registration_rate_limit_decay',
+                    'pixelfed.app_registration_confirm_rate_limit_attempts',
+                    'pixelfed.app_registration_confirm_rate_limit_decay',
+                    'instance.embed.profile',
+                    'instance.embed.post',
+
+                    'captcha.enabled',
+                    'captcha.secret',
+                    'captcha.sitekey',
+                    'captcha.active.login',
+                    'captcha.active.register',
+                    'captcha.triggers.login.enabled',
+                    'captcha.triggers.login.attempts',
+                    'federation.custom_emoji.enabled',
+
+                    'pixelfed.optimize_image',
+                    'pixelfed.optimize_video',
+                    'pixelfed.max_collection_length',
+                    'media.delete_local_after_cloud',
+                    'instance.user_filters.max_user_blocks',
+                    'instance.user_filters.max_user_mutes',
+                    'instance.user_filters.max_domain_blocks',
+
+                    'filesystems.disks.s3.key',
+                    'filesystems.disks.s3.secret',
+                    'filesystems.disks.s3.region',
+                    'filesystems.disks.s3.bucket',
+                    'filesystems.disks.s3.visibility',
+                    'filesystems.disks.s3.url',
+                    'filesystems.disks.s3.endpoint',
+                    'filesystems.disks.s3.use_path_style_endpoint',
+
+                    'filesystems.disks.spaces.key',
+                    'filesystems.disks.spaces.secret',
+                    'filesystems.disks.spaces.region',
+                    'filesystems.disks.spaces.bucket',
+                    'filesystems.disks.spaces.visibility',
+                    'filesystems.disks.spaces.url',
+                    'filesystems.disks.spaces.endpoint',
+                    'filesystems.disks.spaces.use_path_style_endpoint',
+                    // 'system.user_mode'
+                ];
+
+                if (! config('instance.enable_cc')) {
+                    return config($key);
                 }
-            }
 
-            if (! $v) {
-                return;
-            }
+                if (! in_array($key, $allowed)) {
+                    return config($key);
+                }
+
+                $protect = false;
+                $protected = null;
+                if(in_array($key, self::PROTECTED_KEYS)) {
+                    $protect = true;
+                }
 
-            if($protect && $v) {
-                $protected = encrypt($v);
-            }
+                $v = config($key);
+                $c = ConfigCacheModel::where('k', $key)->first();
 
-            $cc = new ConfigCacheModel;
-            $cc->k = $key;
-            $cc->v = $protect ? $protected : $v;
-            $cc->save();
+                if ($c) {
+                    if($protect) {
+                        return decrypt($c->v) ?? config($key);
+                    } else {
+                        return $c->v ?? config($key);
+                    }
+                }
+
+                if (! $v) {
+                    return;
+                }
 
-            return $v;
-        });
+                if($protect && $v) {
+                    $protected = encrypt($v);
+                }
+
+                $cc = new ConfigCacheModel;
+                $cc->k = $key;
+                $cc->v = $protect ? $protected : $v;
+                $cc->save();
+
+                return $v;
+            });
+        } catch (Exception | QueryException $e) {
+            return config($key);
+        }
     }
 
     public static function put($key, $val)

+ 1 - 0
app/Services/CustomEmojiService.php

@@ -133,6 +133,7 @@ class CustomEmojiService
 			return CustomEmoji::when(!$pgsql, function($q, $pgsql) {
 					return $q->groupBy('shortcode');
 				})
+                ->whereNull('uri')
 				->get()
 				->map(function($emojo) {
 					$url = url('storage/' . $emojo->media_path);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 184 - 183
composer.lock


+ 1 - 1
config/pixelfed.php

@@ -23,7 +23,7 @@ return [
 	| This value is the version of your Pixelfed instance.
 	|
 	*/
-	'version' => '0.11.13',
+	'version' => '0.12.1',
 
 	/*
 	|--------------------------------------------------------------------------

BIN
public/embed.js


+ 1 - 1
resources/assets/components/partials/post/PostContent.vue

@@ -169,7 +169,7 @@
 <script type="text/javascript">
 	import BigPicture from 'bigpicture';
 	import ReadMore from './ReadMore.vue';
-    import VideoPlayer from './../../presenter/VideoPlayer.vue';
+    import VideoPlayer from '@/presenter/VideoPlayer.vue';
 
 	export default {
 		props: ['status'],

+ 75 - 0
resources/assets/components/presenter/MixedAlbumPresenter.vue

@@ -0,0 +1,75 @@
+<template>
+	<div v-if="status.sensitive == true">
+		<details class="details-animated">
+			<summary>
+				<p class="mb-0 lead font-weight-bold">{{ status.spoiler_text ? status.spoiler_text : 'CW / NSFW / Hidden Media'}}</p>
+				<p class="font-weight-light">(click to show)</p>
+			</summary>
+			<b-carousel :id="status.id + '-carousel'"
+				style="text-shadow: 1px 1px 2px #333; background-color: #000;"
+				controls
+				img-blank
+				background="#ffffff"
+				:interval="0"
+			>
+				<b-carousel-slide v-for="(media, index) in status.media_attachments" :key="media.id + '-media'">
+
+					<video v-if="media.type == 'video'" slot="img" class="embed-responsive-item" preload="none" controls playsinline loop :alt="media.description" width="100%" height="100%">
+						<source :src="media.url" :type="media.mime">
+					</video>
+
+					<div v-else-if="media.type == 'image'" slot="img" :title="media.description">
+						<img :class="media.filter_class + ' d-block img-fluid w-100'" :src="media.url" :alt="media.description" loading="lazy" onerror="this.onerror=null;this.src='/storage/no-preview.png'">
+					</div>
+
+					<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
+
+				</b-carousel-slide>
+			</b-carousel>
+		</details>
+	</div>
+	<div v-else class="w-100 h-100 p-0">
+		<!-- <b-carousel :id="status.id + '-carousel'"
+					style="text-shadow: 1px 1px 2px #333; background-color: #000;"
+					controls
+					img-blank
+					background="#ffffff"
+					:interval="0"
+				>
+			<b-carousel-slide v-for="(media, index) in status.media_attachments" :key="media.id + '-media'">
+
+				<video v-if="media.type == 'Video'" slot="img" class="embed-responsive-item" preload="none" controls loop :title="media.description" width="100%" height="100%" :poster="media.preview_url">
+					<source :src="media.url" :type="media.mime">
+				</video>
+
+				<div v-else-if="media.type == 'Image'" slot="img" :title="media.description">
+					<img :class="media.filter_class + ' d-block img-fluid w-100'" :src="media.url" :alt="media.description" loading="lazy">
+				</div>
+
+				<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
+
+			</b-carousel-slide>
+		</b-carousel> -->
+		<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb" class="p-0 m-0">
+			<slide v-for="(media, index) in status.media_attachments" :key="'px-carousel-'+media.id + '-' + index" class="w-100 h-100 d-block mx-auto text-center" style="background: #000; display: flex;align-items: center;">
+
+				<video v-if="media.type == 'video'" class="embed-responsive-item" preload="none" controls loop :title="media.description" width="100%" height="100%">
+					<source :src="media.url" :type="media.mime">
+				</video>
+
+				<div v-else-if="media.type == 'image'" :title="media.description">
+					<img :class="media.filter_class + ' img-fluid w-100'" :src="media.url" :alt="media.description" loading="lazy"  onerror="this.onerror=null;this.src='/storage/no-preview.png'">
+				</div>
+
+				<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
+
+			</slide>
+		</carousel>
+	</div>
+</template>
+
+<script type="text/javascript">
+	export default {
+		props: ['status']
+	}
+</script>

+ 188 - 0
resources/assets/components/presenter/PhotoAlbumPresenter.vue

@@ -0,0 +1,188 @@
+<template>
+	<div v-if="status.sensitive == true" class="content-label-wrapper">
+		<div class="text-light content-label">
+			<p class="text-center">
+				<i class="far fa-eye-slash fa-2x"></i>
+			</p>
+			<p class="h4 font-weight-bold text-center">
+				Sensitive Content
+			</p>
+			<p class="text-center py-2 content-label-text">
+				{{ status.spoiler_text ? status.spoiler_text : 'This album may contain sensitive content.'}}
+			</p>
+			<p class="mb-0">
+				<button @click="toggleContentWarning()" class="btn btn-outline-light btn-block btn-sm font-weight-bold">See Post</button>
+			</p>
+		</div>
+		<blur-hash-image
+			width="32"
+			height="32"
+			:punch="1"
+			:hash="status.media_attachments[0].blurhash"
+			:alt="altText(status)"/>
+	</div>
+	<div v-else class="w-100 h-100 p-0 album-wrapper">
+		<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb" class="p-0 m-0" :id="'carousel-' + status.id">
+			<slide v-for="(img, index) in status.media_attachments" :key="'px-carousel-'+img.id + '-' + index" class="" style="background: #000; display: flex;align-items: center;" :title="img.description">
+
+				<img
+					class="img-fluid w-100 p-0"
+					:src="img.url"
+					:alt="altText(img)"
+					loading="lazy"
+					:data-bp="img.url"
+					onerror="this.onerror=null;this.src='/storage/no-preview.png'">
+
+			</slide>
+		</carousel>
+		<div class="album-overlay">
+			<p v-if="!status.sensitive && sensitive"
+				@click="status.sensitive = true"
+				style="
+				margin-top: 0;
+				padding: 10px;
+				color: #fff;
+				font-size: 10px;
+				text-align: right;
+				position: absolute;
+				top: 0;
+				right: 0;
+				border-top-left-radius: 5px;
+				cursor: pointer;
+				background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+			">
+				<i class="fas fa-eye-slash fa-lg"></i>
+			</p>
+
+			<p @click.prevent="toggleLightbox"
+				style="
+				margin-top: 0;
+				padding: 10px;
+				color: #fff;
+				font-size: 10px;
+				text-align: right;
+				position: absolute;
+				left: 0;
+				top: 0;
+				border-bottom-right-radius: 5px;
+				cursor: pointer;
+				background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+			">
+				<i class="fas fa-expand fa-lg"></i>
+			</p>
+
+			<p
+				v-if="status.media_attachments[0].license"
+				style="
+				margin-bottom: 0;
+				padding: 0 5px;
+				color: #fff;
+				font-size: 10px;
+				text-align: right;
+				position: absolute;
+				bottom: 0;
+				right: 0;
+				border-top-left-radius: 5px;
+				background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+			">
+				<a :href="status.url" class="font-weight-bold text-light">Photo</a> by <a :href="status.account.url" class="font-weight-bold text-light">&commat;{{status.account.username}}</a> licensed under <a :href="status.media_attachments[0].license.url" class="font-weight-bold text-light">{{status.media_attachments[0].license.title}}</a>
+			</p>
+		</div>
+	</div>
+</template>
+
+
+<script type="text/javascript">
+	import BigPicture from 'bigpicture';
+
+	export default {
+		props: ['status'],
+
+		data() {
+			return {
+				sensitive: this.status.sensitive,
+				cursor: 0
+			}
+		},
+
+		created() {
+			// window.addEventListener("keydown", this.keypressNavigation);
+		},
+
+		beforeDestroy() {
+			// window.removeEventListener("keydown", this.keypressNavigation);
+		},
+
+		methods: {
+			toggleContentWarning(status) {
+				this.$emit('togglecw');
+			},
+
+			toggleLightbox(e) {
+				BigPicture({
+					el: e.target,
+					gallery: '#carousel-' + this.status.id,
+					position: this.$refs.carousel.currentPage
+				})
+			},
+
+			altText(img) {
+				let desc = img.description;
+				if(desc) {
+					return desc;
+				}
+
+				return 'Photo was not tagged with any alt text.';
+			},
+
+			keypressNavigation(e) {
+				let ref = this.$refs.carousel;
+				if (e.keyCode == "37") {
+					e.preventDefault();
+
+					let direction = "backward";
+
+					ref.advancePage(direction);
+					ref.$emit("navigation-click", direction);
+				}
+
+				if (e.keyCode == "39") {
+					e.preventDefault();
+
+					let direction = "forward";
+
+					ref.advancePage(direction);
+					ref.$emit("navigation-click", direction);
+				}
+			}
+		}
+	}
+</script>
+
+<style type="text/css" scoped>
+  .card-img-top {
+	border-top-left-radius: 0 !important;
+	border-top-right-radius: 0 !important;
+  }
+  .content-label-wrapper {
+	position: relative;
+  }
+  .content-label {
+	margin: 0;
+	position: absolute;
+	top:50%;
+	left:50%;
+	transform: translate(-50%, -50%);
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+	height: 100%;
+	z-index: 2;
+	background: rgba(0, 0, 0, 0.2)
+  }
+  .album-wrapper {
+	position: relative;
+  }
+</style>

+ 160 - 0
resources/assets/components/presenter/PhotoPresenter.vue

@@ -0,0 +1,160 @@
+<template>
+	<div v-if="status.sensitive == true" class="content-label-wrapper">
+		<div class="text-light content-label">
+			<p class="text-center">
+				<i class="far fa-eye-slash fa-2x"></i>
+			</p>
+			<p class="h4 font-weight-bold text-center">
+				Sensitive Content
+			</p>
+			<p class="text-center py-2 content-label-text">
+				{{ status.spoiler_text ? status.spoiler_text : 'This post may contain sensitive content.'}}
+			</p>
+			<p class="mb-0">
+				<button @click="toggleContentWarning()" class="btn btn-outline-light btn-block btn-sm font-weight-bold">See Post</button>
+			</p>
+		</div>
+		<blur-hash-image
+			width="32"
+			height="32"
+			:punch="1"
+			:hash="status.media_attachments[0].blurhash"
+			:alt="altText(status)"/>
+	</div>
+	<div v-else>
+		<div :title="status.media_attachments[0].description" style="position: relative;">
+			<img class="card-img-top"
+				:src="status.media_attachments[0].url"
+				loading="lazy"
+				:alt="altText(status)"
+				:width="width()"
+				:height="height()"
+				onerror="this.onerror=null;this.src='/storage/no-preview.png'"
+				@click.prevent="toggleLightbox">
+
+				<!-- <blur-hash-image
+					class="card-img-top"
+					width="32"
+					height="32"
+					:punch="1"
+					:hash="status.media_attachments[0].blurhash"
+					:src="status.media_attachments[0].url"
+					:alt="altText(status)"
+					@click.prevent="toggleLightbox"/> -->
+
+				<p v-if="!status.sensitive && sensitive"
+					@click="status.sensitive = true"
+					style="
+					margin-top: 0;
+					padding: 10px;
+					color: #fff;
+					font-size: 10px;
+					text-align: right;
+					position: absolute;
+					top: 0;
+					right: 0;
+					border-top-left-radius: 5px;
+					cursor: pointer;
+					background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+				">
+					<i class="fas fa-eye-slash fa-lg"></i>
+				</p>
+
+				<p
+					v-if="status.media_attachments[0].license"
+					style="
+					margin-bottom: 0;
+					padding: 0 5px;
+					color: #fff;
+					font-size: 10px;
+					text-align: right;
+					position: absolute;
+					bottom: 0;
+					right: 0;
+					border-top-left-radius: 5px;
+					background: linear-gradient(0deg, rgba(0,0,0,0.5), rgba(0,0,0,0.5));
+				"><a :href="status.url" class="font-weight-bold text-light">Photo</a> by <a :href="status.account.url" class="font-weight-bold text-light">&commat;{{status.account.username}}</a> licensed under <a :href="status.media_attachments[0].license.url" class="font-weight-bold text-light">{{status.media_attachments[0].license.title}}</a></p>
+		</div>
+	</div>
+</template>
+
+<style type="text/css" scoped>
+  .card-img-top {
+    border-top-left-radius: 0 !important;
+    border-top-right-radius: 0 !important;
+  }
+  .content-label-wrapper {
+  	position: relative;
+  }
+  .content-label {
+  	margin: 0;
+  	position: absolute;
+  	top:50%;
+  	left:50%;
+  	transform: translate(-50%, -50%);
+  	display: flex;
+  	flex-direction: column;
+  	align-items: center;
+  	justify-content: center;
+  	width: 100%;
+  	height: 100%;
+  	z-index: 2;
+  	background: rgba(0, 0, 0, 0.2)
+  }
+</style>
+
+<script type="text/javascript">
+	import BigPicture from 'bigpicture';
+
+	export default {
+		props: ['status'],
+
+		data() {
+			return {
+				sensitive: this.status.sensitive
+			}
+		},
+
+		mounted() {
+		},
+
+		methods: {
+			altText(status) {
+				let desc = status.media_attachments[0].description;
+				if(desc) {
+					return desc;
+				}
+
+				return 'Photo was not tagged with any alt text.';
+			},
+
+			toggleContentWarning(status) {
+				this.$emit('togglecw');
+			},
+
+			toggleLightbox(e) {
+				BigPicture({
+					el: e.target
+				})
+			},
+
+			width() {
+				if( !this.status.media_attachments[0].meta ||
+					!this.status.media_attachments[0].meta.original ||
+					!this.status.media_attachments[0].meta.original.width ) {
+					return;
+				}
+				return this.status.media_attachments[0].meta.original.width;
+			},
+
+			height() {
+				if( !this.status.media_attachments[0].meta ||
+					!this.status.media_attachments[0].meta.original ||
+					!this.status.media_attachments[0].meta.original.height ) {
+					return;
+				}
+				return this.status.media_attachments[0].meta.original.height;
+			}
+		}
+	}
+</script>

+ 44 - 0
resources/assets/components/presenter/VideoAlbumPresenter.vue

@@ -0,0 +1,44 @@
+<template>
+	<div v-if="status.sensitive == true">
+		<details class="details-animated">
+			<summary>
+				<p class="mb-0 lead font-weight-bold">{{ status.spoiler_text ? status.spoiler_text : 'CW / NSFW / Hidden Media'}}</p>
+				<p class="font-weight-light">(click to show)</p>
+			</summary>
+			<b-carousel :id="status.id + '-carousel'"
+				style="text-shadow: 1px 1px 2px #333; background-color: #000;"
+				controls
+				img-blank
+				background="#ffffff"
+				:interval="0"
+			>
+				<b-carousel-slide v-for="(vid, index) in status.media_attachments" :key="vid.id + '-media'">
+					<video slot="img" class="embed-responsive-item" preload="none" controls playsinline loop :alt="vid.description" width="100%" height="100%">
+						<source :src="vid.url" :type="vid.mime">
+					</video>
+				</b-carousel-slide>
+			</b-carousel>
+		</details>
+	</div>
+	<div v-else>
+		<b-carousel :id="status.id + '-carousel'"
+			style="text-shadow: 1px 1px 2px #333; background-color: #000;"
+			controls
+			img-blank
+			background="#ffffff"
+			:interval="0"
+		>
+			<b-carousel-slide v-for="(vid, index) in status.media_attachments" :key="vid.id + '-media'">
+				<video slot="img" class="embed-responsive-item" preload="none" controls playsinline loop :alt="vid.description" width="100%" height="100%">
+					<source :src="vid.url" :type="vid.mime">
+				</video>
+			</b-carousel-slide>
+		</b-carousel>
+	</div>
+</template>
+
+<script type="text/javascript">
+	export default {
+		props: ['status']
+	}
+</script>

+ 90 - 0
resources/assets/components/presenter/VideoPresenter.vue

@@ -0,0 +1,90 @@
+<template>
+	<div v-if="status.sensitive == true" class="content-label-wrapper">
+		<div class="text-light content-label">
+			<p class="text-center">
+				<i class="far fa-eye-slash fa-2x"></i>
+			</p>
+			<p class="h4 font-weight-bold text-center">
+				Sensitive Content
+			</p>
+			<p class="text-center py-2 content-label-text">
+				{{ status.spoiler_text ? status.spoiler_text : 'This post may contain sensitive content.'}}
+			</p>
+			<p class="mb-0">
+				<button @click="toggleContentWarning()" class="btn btn-outline-light btn-block btn-sm font-weight-bold">See Post</button>
+			</p>
+		</div>
+		<blur-hash-image
+			width="32"
+			height="32"
+			:punch="1"
+			:hash="status.media_attachments[0].blurhash"
+			:alt="altText(status)"/>
+	</div>
+	<div v-else class="embed-responsive embed-responsive-16by9">
+		<video class="video" controls playsinline webkit-playsinline preload="metadata" loop :data-id="status.id" :poster="poster()">
+			<source :src="status.media_attachments[0].url" :type="status.media_attachments[0].mime">
+		</video>
+	</div>
+</template>
+
+<style type="text/css" scoped>
+  .content-label-wrapper {
+  	position: relative;
+  }
+  .content-label {
+  	margin: 0;
+  	position: absolute;
+  	top:50%;
+  	left:50%;
+  	transform: translate(-50%, -50%);
+  	display: flex;
+  	flex-direction: column;
+  	align-items: center;
+  	justify-content: center;
+  	width: 100%;
+  	height: 100%;
+  	z-index: 2;
+  	background: rgba(0, 0, 0, 0.2)
+  }
+</style>
+
+<script type="text/javascript">
+	export default {
+		props: ['status'],
+
+		methods: {
+			altText(status) {
+				let desc = status.media_attachments[0].description;
+				if(desc) {
+					return desc;
+				}
+
+				return 'Video was not tagged with any alt text.';
+			},
+
+			playOrPause(e) {
+				let el = e.target;
+				if(el.getAttribute('playing') == 1) {
+					el.removeAttribute('playing');
+					el.pause();
+				} else {
+					el.setAttribute('playing', 1);
+					el.play();
+				}
+			},
+
+			toggleContentWarning(status) {
+				this.$emit('togglecw');
+			},
+
+            poster() {
+                let url = this.status.media_attachments[0].preview_url;
+                if(url.endsWith('no-preview.jpg') || url.endsWith('no-preview.png')) {
+                    return;
+                }
+                return url;
+            }
+		}
+	}
+</script>

+ 5 - 5
resources/assets/js/landing.js

@@ -48,27 +48,27 @@ Vue.use(VueTimeago, {
 
 Vue.component(
 	'photo-presenter',
-	require('./components/presenter/PhotoPresenter.vue').default
+	require('./../components/presenter/PhotoPresenter.vue').default
 );
 
 Vue.component(
 	'video-presenter',
-	require('./components/presenter/VideoPresenter.vue').default
+	require('./../components/presenter/VideoPresenter.vue').default
 );
 
 Vue.component(
 	'photo-album-presenter',
-	require('./components/presenter/PhotoAlbumPresenter.vue').default
+	require('./../components/presenter/PhotoAlbumPresenter.vue').default
 );
 
 Vue.component(
 	'video-album-presenter',
-	require('./components/presenter/VideoAlbumPresenter.vue').default
+	require('./../components/presenter/VideoAlbumPresenter.vue').default
 );
 
 Vue.component(
 	'mixed-album-presenter',
-	require('./components/presenter/MixedAlbumPresenter.vue').default
+	require('./../components/presenter/MixedAlbumPresenter.vue').default
 );
 
 Vue.component(

+ 5 - 5
resources/assets/js/profile.js

@@ -1,26 +1,26 @@
 Vue.component(
     'photo-presenter',
-    require('./components/presenter/PhotoPresenter.vue').default
+    require('./../components/presenter/PhotoPresenter.vue').default
 );
 
 Vue.component(
     'video-presenter',
-    require('./components/presenter/VideoPresenter.vue').default
+    require('./../components/presenter/VideoPresenter.vue').default
 );
 
 Vue.component(
     'photo-album-presenter',
-    require('./components/presenter/PhotoAlbumPresenter.vue').default
+    require('./../components/presenter/PhotoAlbumPresenter.vue').default
 );
 
 Vue.component(
     'video-album-presenter',
-    require('./components/presenter/VideoAlbumPresenter.vue').default
+    require('./../components/presenter/VideoAlbumPresenter.vue').default
 );
 
 Vue.component(
     'mixed-album-presenter',
-    require('./components/presenter/MixedAlbumPresenter.vue').default
+    require('./../components/presenter/MixedAlbumPresenter.vue').default
 );
 
 Vue.component(

+ 5 - 5
resources/assets/js/spa.js

@@ -60,27 +60,27 @@ Vue.component(
 
 Vue.component(
 	'photo-presenter',
-	require('./components/presenter/PhotoPresenter.vue').default
+	require('./../components/presenter/PhotoPresenter.vue').default
 );
 
 Vue.component(
 	'video-presenter',
-	require('./components/presenter/VideoPresenter.vue').default
+	require('./../components/presenter/VideoPresenter.vue').default
 );
 
 Vue.component(
 	'photo-album-presenter',
-	require('./components/presenter/PhotoAlbumPresenter.vue').default
+	require('./../components/presenter/PhotoAlbumPresenter.vue').default
 );
 
 Vue.component(
 	'video-album-presenter',
-	require('./components/presenter/VideoAlbumPresenter.vue').default
+	require('./../components/presenter/VideoAlbumPresenter.vue').default
 );
 
 Vue.component(
 	'mixed-album-presenter',
-	require('./components/presenter/MixedAlbumPresenter.vue').default
+	require('./../components/presenter/MixedAlbumPresenter.vue').default
 );
 
 Vue.component(

+ 15 - 5
resources/assets/js/status.js

@@ -1,26 +1,26 @@
 Vue.component(
 	'photo-presenter',
-	require('./components/presenter/PhotoPresenter.vue').default
+	require('./../components/presenter/PhotoPresenter.vue').default
 );
 
 Vue.component(
 	'video-presenter',
-	require('./components/presenter/VideoPresenter.vue').default
+	require('./../components/presenter/VideoPresenter.vue').default
 );
 
 Vue.component(
 	'photo-album-presenter',
-	require('./components/presenter/PhotoAlbumPresenter.vue').default
+	require('./../components/presenter/PhotoAlbumPresenter.vue').default
 );
 
 Vue.component(
 	'video-album-presenter',
-	require('./components/presenter/VideoAlbumPresenter.vue').default
+	require('./../components/presenter/VideoAlbumPresenter.vue').default
 );
 
 Vue.component(
 	'mixed-album-presenter',
-	require('./components/presenter/MixedAlbumPresenter.vue').default
+	require('./../components/presenter/MixedAlbumPresenter.vue').default
 );
 
 Vue.component(
@@ -32,3 +32,13 @@ Vue.component(
 	'post-component',
 	require('./components/PostComponent.vue').default
 );
+
+// Vue.component(
+// 	'post-next',
+// 	require('./components/PostNext.vue').default
+// );
+
+// Vue.component(
+// 	'video-component',
+// 	require('./components/VideoComponent.vue').default
+// );

+ 6 - 6
resources/assets/js/timeline.js

@@ -5,27 +5,27 @@ Vue.component(
 
 Vue.component(
     'photo-presenter',
-    require('./components/presenter/PhotoPresenter.vue').default
+    require('./../components/presenter/PhotoPresenter.vue').default
 );
 
 Vue.component(
     'video-presenter',
-    require('./components/presenter/VideoPresenter.vue').default
+    require('./../components/presenter/VideoPresenter.vue').default
 );
 
 Vue.component(
     'photo-album-presenter',
-    require('./components/presenter/PhotoAlbumPresenter.vue').default
+    require('./../components/presenter/PhotoAlbumPresenter.vue').default
 );
 
 Vue.component(
     'video-album-presenter',
-    require('./components/presenter/VideoAlbumPresenter.vue').default
+    require('./../components/presenter/VideoAlbumPresenter.vue').default
 );
 
 Vue.component(
     'mixed-album-presenter',
-    require('./components/presenter/MixedAlbumPresenter.vue').default
+    require('./../components/presenter/MixedAlbumPresenter.vue').default
 );
 
 Vue.component(
@@ -46,4 +46,4 @@ Vue.component(
 Vue.component(
     'story-component',
     require('./components/StoryTimelineComponent.vue').default
-);
+);

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 4 - 20
resources/views/profile/embed.blade.php


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 15 - 31
resources/views/status/embed.blade.php


+ 30 - 24
webpack.mix.js

@@ -1,8 +1,10 @@
 let mix = require('laravel-mix');
 const fs = require("fs");
+const path = require("path");
 
 mix.before(() => {
-	fs.rmSync('public/js', { recursive: true, force: true });
+    fs.rmSync('public/css', { recursive: true, force: true });
+    fs.rmSync('public/js', { recursive: true, force: true });
 });
 
 
@@ -46,31 +48,35 @@ mix.version();
 const TerserPlugin = require('terser-webpack-plugin');
 
 mix.options({
-	processCssUrls: false,
-	terser: {
-		parallel: true,
-		terserOptions: {
-			compress: true,
-			output: {
-				comments: false
-			}
-		}
-	}
+    processCssUrls: false,
+    terser: {
+        parallel: true,
+        terserOptions: {
+            compress: true,
+            output: {
+                comments: false
+            }
+        }
+    }
 })
+mix.alias({
+    '@': path.join(__dirname, 'resources/assets/components'),
+    '~': path.join(__dirname, 'resources/assets/js/components'),
+});
 mix.webpackConfig({
-	optimization: {
-		providedExports: false,
-		sideEffects: false,
-		usedExports: false,
-		minimize: true,
-		minimizer: [ new TerserPlugin({
-			extractComments: false,
-		})]
-	},
-	output: {
-		chunkFilename: 'js/[name].[chunkhash].js',
-	}
+    optimization: {
+        providedExports: false,
+        sideEffects: false,
+        usedExports: false,
+        minimize: true,
+        minimizer: [ new TerserPlugin({
+            extractComments: false,
+        })]
+    },
+    output: {
+        chunkFilename: 'js/[name].[chunkhash].js',
+    }
 });
 mix.autoload({
-	jquery: ['$', 'jQuery', 'window.jQuery']
+    jquery: ['$', 'jQuery', 'window.jQuery']
 });

Vissa filer visades inte eftersom för många filer har ändrats