Merge pull request #410 from pixelfed/frontend-ui-refactor

Frontend ui refactor
This commit is contained in:
daniel 2018-08-26 21:27:15 -06:00 committed by GitHub
commit f1e2ad2d34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 1767 additions and 138 deletions

View file

@ -1,6 +1,6 @@
storage
data
Dockerfile
contrib/docker/Dockerfile.*
docker-compose*.yml
.dockerignore
.git

View file

@ -1,31 +0,0 @@
FROM php:7.2.6-fpm-alpine
ARG COMPOSER_VERSION="1.6.5"
ARG COMPOSER_CHECKSUM="67bebe9df9866a795078bb2cf21798d8b0214f2e0b2fd81f2e907a8ef0be3434"
RUN apk add --no-cache --virtual .build build-base autoconf imagemagick-dev libtool && \
apk --no-cache add imagemagick git && \
docker-php-ext-install pdo_mysql pcntl bcmath && \
pecl install imagick && \
docker-php-ext-enable imagick pcntl imagick && \
curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /tmp/composer.phar && \
echo "${COMPOSER_CHECKSUM} /tmp/composer.phar" | sha256sum -c - && \
install -m0755 -o root -g root /tmp/composer.phar /usr/bin/composer.phar && \
ln -sf /usr/bin/composer.phar /usr/bin/composer && \
rm /tmp/composer.phar && \
apk --no-cache del --purge .build
COPY . /var/www/html/
WORKDIR /var/www/html
RUN install -d -m0755 -o www-data -g www-data \
/var/www/html/storage \
/var/www/html/storage/framework \
/var/www/html/storage/logs \
/var/www/html/storage/framework/sessions \
/var/www/html/storage/framework/views \
/var/www/html/storage/framework/cache && \
composer install --prefer-source --no-interaction
VOLUME ["/var/www/html"]
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"

1
Dockerfile Symbolic link
View file

@ -0,0 +1 @@
contrib/docker/Dockerfile.apache

View file

@ -65,7 +65,7 @@ class RegisterController extends Controller
];
$rules = [
'name' => 'required|string|max:255',
'name' => 'required|string|max' . config('pixelfed.max_name_length'),
'username' => $usernameRules,
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',

View file

@ -30,8 +30,8 @@ class SettingsController extends Controller
public function homeUpdate(Request $request)
{
$this->validate($request, [
'name' => 'required|string|max:30',
'bio' => 'nullable|string|max:125',
'name' => 'required|string|max:' . config('pixelfed.max_name_length'),
'bio' => 'nullable|string|max:' . config('pixelfed.max_bio_length')
'website' => 'nullable|url',
'email' => 'nullable|email'
]);

View file

@ -21,17 +21,33 @@ class StatusController extends Controller
->withCount(['likes', 'comments', 'media'])
->findOrFail($id);
if(!$status->media_path && $status->in_reply_to_id) {
return redirect($status->url());
}
if($request->wantsJson() && config('pixelfed.activitypub_enabled')) {
return $this->showActivityPub($request, $status);
}
$template = $this->detectTemplate($status);
$replies = Status::whereInReplyToId($status->id)->simplePaginate(30);
return view('status.show', compact('user', 'status', 'replies'));
return view($template, compact('user', 'status', 'replies'));
}
protected function detectTemplate($status)
{
$template = Cache::rememberForever('template:status:type:'.$status->id, function () use($status) {
$template = 'status.show.photo';
if(!$status->media_path && $status->in_reply_to_id) {
$template = 'status.reply';
}
if($status->media->count() > 1) {
$template = 'status.show.album';
}
if($status->viewType() == 'video') {
$template = 'status.show.video';
}
return $template;
});
return $template;
}
public function compose()
@ -42,11 +58,7 @@ class StatusController extends Controller
public function store(Request $request)
{
if(Auth::check() == false)
{
abort(403);
}
$this->authCheck();
$user = Auth::user();
$size = Media::whereUserId($user->id)->sum('size') / 1000;
@ -56,7 +68,7 @@ class StatusController extends Controller
}
$this->validate($request, [
'photo.*' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
'photo.*' => 'required|mimes:jpeg,png,gif|max:' . config('pixelfed.max_photo_size'),
'caption' => 'string|max:' . config('pixelfed.max_caption_length'),
'cw' => 'nullable|string',
'filter_class' => 'nullable|string',
@ -83,11 +95,13 @@ class StatusController extends Controller
foreach ($photos as $k => $v) {
$storagePath = "public/m/{$monthHash}/{$userHash}";
$path = $v->store($storagePath);
$hash = \hash_file('sha256', $v);
$media = new Media;
$media->status_id = $status->id;
$media->profile_id = $profile->id;
$media->user_id = $user->id;
$media->media_path = $path;
$media->original_sha256 = $hash;
$media->size = $v->getClientSize();
$media->mime = $v->getClientMimeType();
$media->filter_class = $request->input('filter_class');
@ -172,6 +186,57 @@ class StatusController extends Controller
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
}
public function edit(Request $request, $username, $id)
{
$this->authCheck();
$user = Auth::user()->profile;
$status = Status::whereProfileId($user->id)
->with(['media'])
->findOrFail($id);
return view('status.edit', compact('user', 'status'));
}
public function editStore(Request $request, $username, $id)
{
$this->authCheck();
$user = Auth::user()->profile;
$status = Status::whereProfileId($user->id)
->with(['media'])
->findOrFail($id);
$this->validate($request, [
'id' => 'required|integer|min:1',
'caption' => 'nullable',
'filter' => 'nullable|alpha_dash|max:30'
]);
$id = $request->input('id');
$caption = $request->input('caption');
$filter = $request->input('filter');
$media = Media::whereProfileId($user->id)
->whereStatusId($status->id)
->find($id);
$changed = false;
if($media->caption != $caption) {
$media->caption = $caption;
$changed = true;
}
if($media->filter_class != $filter) {
$media->filter_class = $filter;
$changed = true;
}
if($changed === true) {
$media->save();
}
return response()->json([], 200);
}
protected function authCheck()
{
if(Auth::check() == false)

View file

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Profile;
class StoryController extends Controller
{
}

View file

@ -4,7 +4,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use App\{Follower, Status, User};
use App\{Follower, Profile, Status, User, UserFilter};
class TimelineController extends Controller
{
@ -15,10 +15,16 @@ class TimelineController extends Controller
public function personal()
{
$pid = Auth::user()->profile->id;
// TODO: Use redis for timelines
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
$following->push(Auth::user()->profile->id);
$following = Follower::whereProfileId($pid)->pluck('following_id');
$following->push($pid);
$filtered = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id');
$timeline = Status::whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered)
->orderBy('id','desc')
->withCount(['comments', 'likes'])
->simplePaginate(20);
@ -30,8 +36,18 @@ class TimelineController extends Controller
{
// TODO: Use redis for timelines
// $timeline = Timeline::build()->local();
$pid = Auth::user()->profile->id;
$filtered = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id');
$private = Profile::whereIsPrivate(true)->pluck('id');
$filtered = $filtered->merge($private);
$timeline = Status::whereHas('media')
->whereNotIn('profile_id', $filtered)
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->withCount(['comments', 'likes'])
->orderBy('id','desc')
->simplePaginate(20);

View file

@ -16,6 +16,12 @@ class ImageUpdate implements ShouldQueue
protected $media;
protected $protectedMimes = [
'image/gif',
'image/bmp',
'video/mp4'
];
/**
* Create a new job instance.
*
@ -37,9 +43,9 @@ class ImageUpdate implements ShouldQueue
$path = storage_path('app/'. $media->media_path);
$thumb = storage_path('app/'. $media->thumbnail_path);
try {
ImageOptimizer::optimize($thumb);
if($media->mime !== 'image/gif')
if(!in_array($media->mime, $this->protectedMimes))
{
ImageOptimizer::optimize($thumb);
ImageOptimizer::optimize($path);
}
} catch (Exception $e) {

View file

@ -13,6 +13,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\ImageOptimizePipeline\ImageThumbnail;
class RemoteFollowImportRecent implements ShouldQueue
{
@ -216,7 +217,9 @@ class RemoteFollowImportRecent implements ShouldQueue
$media->size = 0;
$media->mime = $mime;
$media->save();
ImageThumbnail::dispatch($media);
return true;
} catch (Exception $e) {
return false;

View file

@ -143,7 +143,11 @@ class Profile extends Model
public function statusCount()
{
return $this->statuses()->whereHas('media')->count();
return $this->statuses()
->whereHas('media')
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->count();
}
public function recommendFollowers()
@ -159,6 +163,7 @@ class Profile extends Model
->whereNotIn('following_id', $follows)
->whereIn('profile_id', $following)
->orderByRaw('rand()')
->distinct('id')
->limit(3)
->pluck('following_id');
$recommended = [];

View file

@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
class Report extends Model
{
protected $dates = ['admin_seen'];
public function url()
{
return url('/i/admin/reports/show/' . $this->id);

View file

@ -0,0 +1,94 @@
<?php
namespace App\Util\ActivityPub\Concern;
use Zttp\Zttp;
class HTTPSignature {
protected $localhosts = [
'127.0.0.1', 'localhost', '::1'
];
public $profile;
public $is_url;
public function validateUrl()
{
// If the profile exists, assume its valid
if($this->is_url === false) {
return true;
}
$url = $this->profile;
try {
$url = filter_var($url, FILTER_VALIDATE_URL);
$parsed = parse_url($url, PHP_URL_HOST);
if(!$parsed || in_array($parsed, $this->localhosts)) {
return false;
}
} catch (Exception $e) {
return false;
}
return true;
}
public function fetchPublicKey($profile, bool $is_url = true)
{
$this->profile = $profile;
$this->is_url = $is_url;
$valid = $this->validateUrl();
if(!$valid) {
throw new \Exception('Invalid URL provided');
}
if($is_url && isset($profile->public_key) && $profile->public_key) {
return $profile->public_key;
}
try {
$url = $this->profile;
$res = Zttp::timeout(30)->withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org'
])->get($url);
$actor = json_decode($res->getBody(), true);
} catch (Exception $e) {
throw new Exception('Unable to fetch public key');
}
return $actor['publicKey']['publicKeyPem'];
}
public function sendSignedObject($senderProfile, $url, $body)
{
$profile = $senderProfile;
$context = new Context([
'keys' => [$profile->keyId() => $profile->private_key],
'algorithm' => 'rsa-sha256',
'headers' => ['(request-target)', 'Date'],
]);
$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
$client = new Client(['handler' => $handlerStack]);
$headers = [
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'Date' => date('D, d M Y h:i:s') . ' GMT',
'Content-Type' => 'application/activity+json',
'User-Agent' => 'PixelFedBot - https://pixelfed.org'
];
$response = $client->post($url, [
'options' => [
'allow_redirects' => false,
'verify' => true,
'timeout' => 30
],
'headers' => $headers,
'body' => $body
]);
return $response->getBody();
}
}

View file

@ -108,6 +108,26 @@ return [
*/
'max_caption_length' => env('MAX_CAPTION_LENGTH', 500),
/*
|--------------------------------------------------------------------------
| Bio length limit
|--------------------------------------------------------------------------
|
| Change the bio length limit for user profiles.
|
*/
'max_bio_length' => env('MAX_BIO_LENGTH', 125),
/*
|--------------------------------------------------------------------------
| User name length limit
|--------------------------------------------------------------------------
|
| Change the length limit for user names.
|
*/
'max_name_length' => env('MAX_NAME_LENGTH', 30),
/*
|--------------------------------------------------------------------------
| Album size limit
@ -138,4 +158,4 @@ return [
*/
'image_quality' => (int) env('IMAGE_QUALITY', 80),
];
];

27
config/trustedproxy.php Normal file
View file

@ -0,0 +1,27 @@
<?php
return [
/*
* Set trusted proxy IP addresses.
*
* Both IPv4 and IPv6 addresses are
* supported, along with CIDR notation.
*
* The "*" character is syntactic sugar
* within TrustedProxy to trust any proxy
* that connects directly to your server,
* a requirement when you cannot know the address
* of your proxy (e.g. if using Rackspace balancers).
*
* The "**" character is syntactic sugar within
* TrustedProxy to trust not just any proxy that
* connects directly to your server, but also
* proxies that connect to those proxies, and all
* the way back until you reach the original source
* IP. It will mean that $request->getClientIp()
* always gets the originating client IP, no matter
* how many proxies that client's request has
* subsequently passed through.
*/
'proxies' => explode(',', env('TRUST_PROXIES', '')),
];

View file

@ -0,0 +1,59 @@
FROM php:7-apache
ARG COMPOSER_VERSION="1.6.5"
ARG COMPOSER_CHECKSUM="67bebe9df9866a795078bb2cf21798d8b0214f2e0b2fd81f2e907a8ef0be3434"
RUN apt-get update \
&& apt-get install -y --no-install-recommends git \
optipng pngquant jpegoptim gifsicle \
libfreetype6 libjpeg62-turbo libpng16-16 libxpm4 libvpx4 libmagickwand-6.q16-3 \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libvpx-dev libmagickwand-dev \
&& docker-php-source extract \
&& docker-php-ext-configure gd \
--with-freetype-dir=/usr/lib/x86_64-linux-gnu/ \
--with-jpeg-dir=/usr/lib/x86_64-linux-gnu/ \
--with-xpm-dir=/usr/lib/x86_64-linux-gnu/ \
--with-vpx-dir=/usr/lib/x86_64-linux-gnu/ \
&& docker-php-ext-install pdo_mysql pcntl gd exif bcmath \
&& pecl install imagick \
&& docker-php-ext-enable imagick pcntl imagick gd exif \
&& a2enmod rewrite \
&& curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /usr/bin/composer \
&& echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \
&& chmod 755 /usr/bin/composer \
&& apt-get autoremove --purge -y \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libvpx-dev libmagickwand-dev \
&& rm -rf /var/cache/apt \
&& docker-php-source delete
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
COPY . /var/www/
WORKDIR /var/www/
RUN cp -r storage storage.skel \
&& cp contrib/docker/php.ini /usr/local/etc/php/conf.d/pixelfed.ini \
&& composer install --prefer-source --no-interaction \
&& rm -rf html && ln -s public html
VOLUME ["/var/www/storage"]
ENV APP_ENV=production \
APP_DEBUG=false \
LOG_CHANNEL=stderr \
DB_CONNECTION=mysql \
DB_PORT=3306 \
DB_HOST=db \
BROADCAST_DRIVER=log \
QUEUE_DRIVER=redis \
HORIZON_PREFIX=horizon-pixelfed \
REDIS_HOST=redis \
SESSION_SECURE_COOKIE=true \
API_BASE="/api/1/" \
API_SEARCH="/api/search" \
OPEN_REGISTRATION=true \
ENFORCE_EMAIL_VERIFICATION=true \
REMOTE_FOLLOW=false \
ACTIVITY_PUB=false
CMD /var/www/contrib/docker/start.sh

View file

@ -0,0 +1,31 @@
FROM php:7.2.6-fpm-alpine
ARG COMPOSER_VERSION="1.6.5"
ARG COMPOSER_CHECKSUM="67bebe9df9866a795078bb2cf21798d8b0214f2e0b2fd81f2e907a8ef0be3434"
RUN apk add --no-cache --virtual .build build-base autoconf imagemagick-dev libtool && \
apk --no-cache add imagemagick git && \
docker-php-ext-install pdo_mysql pcntl && \
pecl install imagick && \
docker-php-ext-enable imagick pcntl imagick && \
curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /tmp/composer.phar && \
echo "${COMPOSER_CHECKSUM} /tmp/composer.phar" | sha256sum -c - && \
install -m0755 -o root -g root /tmp/composer.phar /usr/bin/composer.phar && \
ln -sf /usr/bin/composer.phar /usr/bin/composer && \
rm /tmp/composer.phar && \
apk --no-cache del --purge .build
COPY . /var/www/html/
WORKDIR /var/www/html
RUN install -d -m0755 -o www-data -g www-data \
/var/www/html/storage \
/var/www/html/storage/framework \
/var/www/html/storage/logs \
/var/www/html/storage/framework/sessions \
/var/www/html/storage/framework/views \
/var/www/html/storage/framework/cache && \
composer install --prefer-source --no-interaction
VOLUME ["/var/www/html"]
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"

5
contrib/docker/php.ini Normal file
View file

@ -0,0 +1,5 @@
file_uploads = On
memory_limit = 64M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600

17
contrib/docker/start.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/bash
# Create the storage tree if needed and fix permissions
cp -r storage.skel/* storage/
chown -R www-data:www-data storage/
php artisan storage:link
# Migrate database if the app was upgraded
php artisan migrate --force
# Run a worker if it is set as embedded
if [ HORIZON_EMBED = true ]; then
php artisan horizon &
fi
# Finally run Apache
exec apache2-foreground

View file

@ -28,9 +28,11 @@ class UpdateSettingsTable extends Migration
*/
public function down()
{
$table->dropColumn('show_profile_followers');
$table->dropColumn('show_profile_follower_count');
$table->dropColumn('show_profile_following');
$table->dropColumn('show_profile_following_count');
Schema::table('user_settings', function (Blueprint $table) {
$table->dropColumn('show_profile_followers');
$table->dropColumn('show_profile_follower_count');
$table->dropColumn('show_profile_following');
$table->dropColumn('show_profile_following_count');
});
}
}

View file

@ -0,0 +1,44 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class UpdateMediaTableAddAltText extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('media', function (Blueprint $table) {
$table->string('original_sha256')->nullable()->index()->after('user_id');
$table->string('optimized_sha256')->nullable()->index()->after('original_sha256');
$table->string('caption')->nullable()->after('thumbnail_url');
$table->string('hls_path')->nullable()->after('caption');
$table->timestamp('hls_transcoded_at')->nullable()->after('processed_at');
$table->string('key')->nullable();
$table->json('metadata')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('media', function (Blueprint $table) {
$table->dropColumn('original_sha256');
$table->dropColumn('optimized_sha256');
$table->dropColumn('caption');
$table->dropColumn('hls_path');
$table->dropColumn('hls_transcoded_at');
$table->dropColumn('key');
$table->dropColumn('metadata');
});
}
}

View file

@ -1,49 +1,56 @@
---
version: '3'
services:
nginx:
image: nginx:alpine
networks:
- internal
- external
ports:
- 3000:80
volumes:
- "php-storage:/var/www/html"
- ./contrib/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
php:
build: .
# In order to set configuration, please use a .env file in
# your compose project directory (the same directory as your
# docker-compose.yml), and set database options, application
# name, key, and other settings there.
# A list of available settings is available in .env.example
#
# The services should scale properly across a swarm cluster
# if the volumes are properly shared between cluster members.
services:
app:
# Uncomment to build a local copy of the image
# build: .
image: pixelfed
volumes:
- "php-storage:/var/www/html"
networks:
- internal
environment:
- DB_HOST=mysql
- DB_DATABASE=pixelfed
- DB_USERNAME=${DB_USERNAME:-pixelfed}
- DB_PASSWORD=${DB_PASSWORD:-pixelfed}
- REDIS_HOST=redis
- APP_KEY=${APP_KEY}
# If you have a traefik running, uncomment this to expose Pixelfed
# labels:
# - traefik.enable=true
# - traefik.frontend.rule=Host:your.url
# - traefik.port=80
env_file:
- ./.env
volumes:
- "app-storage:/var/www/storage"
networks:
- external
- internal
mysql:
# Uncomment if you set HORIZON_EMBED to false and wish to run a local worker
# worker:
# image: pixelfed
# env_file:
# - ./.env
# volumes:
# - "app-storage:/var/www/storage"
# networks:
# - internal
# command: php artisan horizon
db:
image: mysql:5.7
networks:
- internal
environment:
- MYSQL_DATABASE=pixelfed
- MYSQL_USER=${DB_USERNAME:-pixelfed}
- MYSQL_PASSWORD=${DB_PASSWORD:-pixelfed}
- MYSQL_RANDOM_ROOT_PASSWORD="true"
env_file:
- ./.env
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
- MYSQL_RANDOM_ROOT_PASSWORD=true
volumes:
- "mysql-data:/var/lib/mysql"
- "db-data:/var/lib/mysql"
redis:
image: redis:4-alpine
@ -52,10 +59,11 @@ services:
networks:
- internal
# Adjust your volume data in order to store data where you wish
volumes:
redis-data:
mysql-data:
php-storage:
db-data:
app-storage:
networks:
internal:

View file

@ -298,3 +298,13 @@ details summary::-webkit-details-marker {
.profile-avatar img {
object-fit: cover;
}
.tt-menu {
padding: 0 !important;
border-radius: 0 0 0.25rem 0.25rem !important;
}
.tt-dataset .alert {
border: 0 !important;
border-radius: 0 !important;
}

View file

@ -6,4 +6,7 @@ return [
'emptyFollowing' => 'This user is not following anyone yet!',
'emptySaved' => 'You havent saved any post yet!',
'savedWarning' => 'Only you can see what youve saved',
'privateProfileWarning' => 'This Account is Private',
'alreadyFollow' => 'Already follow :username?',
'loginToSeeProfile' => 'to see their photos and videos.',
];

View file

@ -5,7 +5,12 @@
<div class="col-12 col-md-8 offset-md-2">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
<p class="font-weight-bold mb-0">{{ session('status') }}</p>
</div>
@endif
@if (session('error'))
<div class="alert alert-danger">
<p class="font-weight-bold mb-0">{{ session('error') }}</p>
</div>
@endif
<div class="card">

View file

@ -0,0 +1,57 @@
@extends('admin.partial.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">Reports</h3>
</div>
<hr>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Reporter</th>
<th scope="col">Type</th>
<th scope="col">Reported</th>
<th scope="col">Status</th>
<th scope="col">Created</th>
</tr>
</thead>
<tbody>
@foreach($reports as $report)
<tr>
<th scope="row">
<a href="{{$report->url()}}">
{{$report->id}}
</a>
</th>
<td class="font-weight-bold"><a href="{{$report->reporter->url()}}">{{$report->reporter->username}}</a></td>
<td class="font-weight-bold">{{$report->type}}</td>
<td class="font-weight-bold"><a href="{{$report->reported()->url()}}">{{str_limit($report->reported()->url(), 25)}}</a></td>
@if(!$report->admin_seen)
<td><span class="text-danger font-weight-bold">Unresolved</span></td>
@else
<td><span class="text-success font-weight-bold">Resolved</span></td>
@endif
<td class="font-weight-bold">{{$report->created_at->diffForHumans(null, true, true, true)}}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="d-flex justify-content-center mt-5 small">
{{$reports->links()}}
</div>
@endsection
@push('scripts')
<script type="text/javascript">
$(document).ready(function() {
$('.human-size').each(function(d,a) {
let el = $(a);
let size = el.data('bytes');
el.text(filesize(size, {round: 0}));
});
});
</script>
@endpush

View file

@ -0,0 +1,167 @@
@extends('admin.partial.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">Report #<span class="reportid" data-id="{{$report->id}}">{{$report->id}}</span> - <span class="badge badge-danger">{{ucfirst($report->type)}}</span></h3>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Reported: <a href="{{$report->reported()->url()}}">{{$report->reported()->url()}}</a></h5>
<h6 class="card-subtitle mb-2 text-muted">Reported by: <a href="{{$report->reporter->url()}}">{{$report->reporter->username}}</a> <span class="badge badge-primary">admin</span></h6>
<p class="card-text text-muted">
<span class="font-weight-bold text-dark">Message: </span>
{{$report->message ?? 'No message provided.'}}
</p>
@if(!$report->admin_seen)
<a href="#" class="card-link report-action-btn font-weight-bold" data-action="ignore">Ignore</a>
{{-- <a href="#" class="card-link font-weight-bold">Request Mod Feedback</a> --}}
<a href="#" class="card-link report-action-btn font-weight-bold" data-action="cw">Add CW</a>
<a href="#" class="card-link report-action-btn font-weight-bold" data-action="unlist">Unlist/Hide</a>
<a href="#" class="card-link report-action-btn font-weight-bold text-danger" data-action="delete">Delete</a>
<a href="#" class="card-link report-action-btn font-weight-bold text-danger" data-action="shadowban">Shadowban User</a>
<a href="#" class="card-link report-action-btn font-weight-bold text-danger" data-action="ban">Ban User</a>
@else
<p class="font-weight-bold mb-0">Resolved {{$report->admin_seen->diffForHumans()}}</p>
@endif
</div>
</div>
<div class="accordion mt-3" id="accordianBackground">
<div class="card">
<div class="card-header bg-white" id="headingOne">
<h5 class="mb-0">
<button class="btn btn-link font-weight-bold text-dark" type="button" data-toggle="collapse" data-target="#background" aria-expanded="true" aria-controls="background">
Background
</button>
</h5>
</div>
<div id="background" class="collapse show" aria-labelledby="headingOne">
<div class="card-body">
<div class="row">
<div class="col-12 col-md-6">
<div class="card">
<div class="card-header bg-white font-weight-bold">
Reporter
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Joined <span class="font-weight-bold">{{$report->reporter->created_at->diffForHumans()}}</span></li>
<li class="list-group-item">Total Reports: <span class="font-weight-bold">{{App\Report::whereProfileId($report->reporter->id)->count()}}</span></li>
<li class="list-group-item">Total Reported: <span class="font-weight-bold">{{App\Report::whereReportedProfileId($report->reporter->id)->count()}}</span></li>
</ul>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card">
<div class="card-header bg-white font-weight-bold">
Reported
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Joined <span class="font-weight-bold">{{$report->reportedUser->created_at->diffForHumans()}}</span></li>
<li class="list-group-item">Total Reports: <span class="font-weight-bold">{{App\Report::whereProfileId($report->reportedUser->id)->count()}}</span></li>
<li class="list-group-item">Total Reported: <span class="font-weight-bold">{{App\Report::whereReportedProfileId($report->reportedUser->id)->count()}}</span></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- <div class="accordion mt-3" id="accordianLog">
<div class="card">
<div class="card-header bg-white" id="headingTwo">
<h5 class="mb-0">
<button class="btn btn-link font-weight-bold text-dark" type="button" data-toggle="collapse" data-target="#log" aria-expanded="true" aria-controls="log">
Activity Log
</button>
</h5>
</div>
<div id="log" class="collapse show" aria-labelledby="headingTwo">
<div class="card-body" style="max-height: 200px;overflow-y: scroll;">
<div class="my-3 border-left-primary">
<p class="pl-2"><a href="#">admin</a> ignored this report. <span class="float-right pl-2 small font-weight-bold">2m</span></p>
</div>
<div class="my-3 border-left-primary">
<p class="pl-2"><a href="#">admin</a> ignored this report. <span class="float-right pl-2 small font-weight-bold">2m</span></p>
</div>
<div class="my-3 border-left-primary">
<p class="pl-2"><a href="#">admin</a> ignored this report. <span class="float-right pl-2 small font-weight-bold">2m</span></p>
</div>
</div>
</div>
</div>
</div> --}}
{{-- <div class="accordion mt-3" id="accordianComments">
<div class="card">
<div class="card-header bg-white" id="headingThree">
<h5 class="mb-0">
<button class="btn btn-link font-weight-bold text-dark" type="button" data-toggle="collapse" data-target="#comments" aria-expanded="true" aria-controls="comments">
Comments
</button>
</h5>
</div>
<div id="comments" class="collapse show" aria-labelledby="headingThree">
<div class="card-body" style="max-height: 400px; overflow-y: scroll;">
<div class="report-comment-wrapper">
<div class="my-3 report-comment">
<div class="card bg-primary text-white">
<div class="card-body">
<a href="#" class="text-white font-weight-bold">[username]</a>: {{str_limit('Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod.', 150)}} <span class="float-right small p-2">2m</span>
</div>
</div>
</div>
<div class="my-3 report-comment">
<div class="card bg-light">
<div class="card-body">
<a href="#" class="font-weight-bold">me</a>: {{str_limit('Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod.', 150)}} <span class="float-right small p-2">2m</span>
</div>
</div>
</div>
<div class="my-3 report-comment">
<div class="card bg-light">
<div class="card-body">
<a href="#" class="font-weight-bold">me</a>: {{str_limit('Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod.', 150)}} <span class="float-right small p-2">2m</span>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer">
<form>
@csrf
<input type="hidden" name="report_id" value="{{$report->id}}">
<input type="text" class="form-control" name="comment" placeholder="Add a comment here" autocomplete="off">
</form>
</div>
</div>
</div>
</div> --}}
@endsection
@push('scripts')
<script type="text/javascript">
$(document).on('click', '.report-action-btn', function(e) {
e.preventDefault();
let el = $(this);
let action = el.data('action');
console.log(action);
axios.post(window.location.href, {
'action': action
})
.then(function(res) {
swal('Success', 'Issue updated successfully!', 'success');
window.location.href = window.location.href;
}).catch(function(res) {
swal('Error', res.data.msg, 'error');
});
})
</script>
@endpush

View file

@ -0,0 +1,74 @@
@extends('admin.partial.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">Site Backups</h3>
</div>
<hr>
<div class="row">
<div class="col-md-7">
<div class="card">
<div class="card-header bg-white font-weight-bold">Settings</div>
<div class="card-body">
<form>
<div class="form-group pt-3">
<label class="font-weight-bold text-muted small">Auto Backup Enabled</label>
<div class="switch switch-sm">
<input type="checkbox" class="switch" id="cw-switch" name="cw">
<label for="cw-switch" class="small font-weight-bold">(Default off)</label>
</div>
<small class="form-text text-muted">
Enable automated backups with your own strategy.
</small>
</div>
<div class="form-group pt-3">
<label class="font-weight-bold text-muted small">Frequency</label>
<select class="form-control">
<option>Hourly (1h)</option>
<option selected="">Nightly (24h)</option>
<option>Weekly (7d)</option>
<option>Monthly (1m)</option>
</select>
<small class="form-text text-muted">
Select the backup frequency.
</small>
</div>
<div class="form-group pt-3">
<label class="font-weight-bold text-muted small">Storage Filesystem</label>
<select class="form-control">
<option>Local</option>
<option disabled="">S3 (Not configured)</option>
</select>
<small class="form-text text-muted">
You can use local, S3, or any S3 compatible object storage API to store backups.
</small>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card">
<div class="card-header bg-white font-weight-bold">Current Backups</div>
<div class="list-group list-group-flush">
@foreach($files as $file)
@if($file->isFile())
<li class="list-group-item pb-0">
<p class="font-weight-bold mb-0 text-truncate">{{$file->getFilename()}}</p>
<p class="mb-0 small text-muted font-weight-bold">
<span>
Size: {{App\Util\Lexer\PrettyNumber::convert($file->getSize())}}
</span>
<span class="float-right">
Created: {{\Carbon\Carbon::createFromTimestamp($file->getMTime())->diffForHumans()}}</p>
</span>
</p>
</li>
@endif
@endforeach
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,9 @@
@extends('admin.partial.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">Maintenance</h3>
</div>
<hr>
@endsection

View file

@ -0,0 +1,30 @@
@extends('admin.partial.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">Storage</h3>
</div>
<hr>
<div class="card">
<div class="card-body">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: {{$storage->percentUsed}}%" aria-valuenow="{{$storage->percentUsed}}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="d-flex justify-content-between">
<span class="font-weight-bold">
Used: {{$storage->prettyTotal}}
</span>
<span class="font-weight-bold">
{{$storage->percentUsed}}% Used
</span>
<span class="font-weight-bold">
Free: {{$storage->prettyFree}}
</span>
</div>
</div>
<div class="card-footer bg-white font-weight-bold text-center">
Total Disk Space
</div>
</div>
@endsection

View file

@ -0,0 +1,39 @@
@extends('admin.partial.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">System</h3>
</div>
<hr>
<div class="row">
<div class="col-12 col-md-6">
<div class="card mb-3">
<div class="card-body text-center">
<p class="font-weight-ultralight display-4 mb-0">{{config('pixelfed.version')}}</p>
</div>
<div class="card-footer font-weight-bold text-center bg-white">Pixelfed</div>
</div>
<div class="card mb-3">
<div class="card-body text-center">
<p class="font-weight-ultralight display-4 mb-0">{{DB::select( DB::raw("select version()") )[0]->{'version()'} }}</p>
</div>
<div class="card-footer font-weight-bold text-center bg-white">MySQL</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card mb-3">
<div class="card-body text-center">
<p class="font-weight-ultralight display-4 mb-0">{{phpversion()}}</p>
</div>
<div class="card-footer font-weight-bold text-center bg-white">PHP</div>
</div>
{{-- <div class="card mb-3">
<div class="card-body text-center">
<p class="font-weight-ultralight display-4 mb-0"></p>
</div>
<div class="card-footer font-weight-bold text-center bg-white">Redis</div>
</div> --}}
</div>
</div>
@endsection

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="robots" content="noimageindex, noarchive">
<meta name="mobile-web-app-capable" content="yes">
<title>{{ $title or config('app.name', 'Laravel') }}</title>
@if(isset($title))<meta property="og:site_name" content="{{ config('app.name', 'Laravel') }}">
<meta property="og:title" content="{{ $title or config('app.name', 'Laravel') }}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{request()->url()}}">
@endif
@stack('meta')
<meta name="medium" content="image">
<meta name="theme-color" content="#10c5f8">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="canonical" href="{{request()->url()}}">
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
@stack('styles')
</head>
<body class="">
@include('layouts.partial.noauthnav')
<main id="content">
@yield('content')
</main>
@include('layouts.partial.footer')
<script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
@stack('scripts')
</body>
</html>

View file

@ -0,0 +1,8 @@
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="{{ url('/') }}" title="{{ config('app.name', 'Laravel') }} Logo">
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2">
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
</a>
</div>
</nav>

View file

@ -0,0 +1,32 @@
<div class="bg-white py-5 border-bottom">
<div class="container">
<div class="row">
<div class="col-12 col-md-4 d-flex">
<div class="profile-avatar mx-auto">
<img class="img-thumbnail" src="{{$user->avatarUrl()}}" style="border-radius:100%;" width="172px">
</div>
</div>
<div class="col-12 col-md-8 d-flex align-items-center">
<div class="profile-details">
<div class="username-bar pb-2 d-flex align-items-center">
<span class="font-weight-ultralight h1">{{$user->username}}</span>
</div>
<div class="profile-stats pb-3 d-inline-flex lead">
<div class="font-weight-light pr-5">
<span class="font-weight-bold">{{$user->statuses()->whereNull('reblog_of_id')->whereNull('in_reply_to_id')->count()}}</span>
Posts
</div>
</div>
<p class="lead mb-0">
<span class="font-weight-bold">{{$user->name}}</span>
@if($user->remote_url)
<span class="badge badge-info">REMOTE PROFILE</span>
@endif
</p>
<p class="mb-0 lead">{{$user->bio}}</p>
<p class="mb-0"><a href="{{$user->website}}" class="font-weight-bold" rel="external nofollow noopener" target="_blank">{{str_limit($user->website, 30)}}</a></p>
</div>
</div>
</div>
</div>
</div>

View file

@ -57,18 +57,22 @@
Posts
</a>
</div>
@if($settings->show_profile_follower_count)
<div class="font-weight-light pr-5">
<a class="text-dark" href="{{$user->url('/followers')}}">
<span class="font-weight-bold">{{$user->followerCount(true)}}</span>
Followers
</a>
</div>
@endif
@if($settings->show_profile_following_count)
<div class="font-weight-light pr-5">
<a class="text-dark" href="{{$user->url('/following')}}">
<span class="font-weight-bold">{{$user->followingCount(true)}}</span>
Following
</a>
</div>
@endif
</div>
<p class="lead mb-0">
<span class="font-weight-bold">{{$user->name}}</span>

View file

@ -0,0 +1,33 @@
@extends('layouts.app',['title' => $user->username . " on " . config('app.name')])
@section('content')
@include('profile.partial.private-info')
<div class="container">
<div class="profile-timeline mt-2 mt-md-4">
<div class="card">
<div class="card-body py-5">
<p class="text-center lead font-weight-bold">
{{__('profile.privateProfileWarning')}}
</p>
@if(Auth::check())
<p class="text-center mb-0">{{ __('profile.alreadyFollow', ['username'=>$user->username])}}</p>
<p class="text-center mb-0"><a href="{{route('login')}}">{{__('Log in')}}</a></p>
<p class="text-center mb-0">{{__('profile.loginToSeeProfile')}}</p>
@endif
</div>
</div>
</div>
</div>
@endsection
@push('meta')
<meta property="og:description" content="{{$user->bio}}">
<meta property="og:image" content="{{$user->avatarUrl()}}">
@if($user->remote_url)
<meta name="robots" content="noindex, nofollow">
@endif
@endpush

View file

@ -74,6 +74,7 @@
@push('meta')<meta property="og:description" content="{{$user->bio}}">
<meta property="og:image" content="{{$user->avatarUrl()}}">
<link href="{{$user->permalink('.atom')}}" rel="alternate" title="{{$user->username}} on PixelFed" type="application/atom+xml">
@if(false == $settings->crawlable || $user->remote_url)
<meta name="robots" content="noindex, nofollow">
@endif

View file

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card">
<div class="card-header lead font-weight-bold">
Report Abusive/Harmful Comment
</div>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="abusive"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card">
<div class="card-header lead font-weight-bold">
Report Abusive/Harmful Post
</div>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="abusive"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card">
<div class="card-header lead font-weight-bold">
Report Abusive/Harmful Profile
</div>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="abusive"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -5,41 +5,90 @@
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card my-5">
<div class="card-body">
<p class="mb-0 font-weight-bold">This feature is not yet ready for production. Please try again later.</p>
</div>
</div>
<div class="card sr-only">
<div class="card-header lead font-weight-bold">
<div class="card">
<div class="card-header lead font-weight-bold bg-white">
Report
</div>
<div class="card-body">
<div class="p-5 text-center">
<p class="lead">Please select one of the following options.</p>
<div class="p-3 text-center">
<p class="lead">Please select one of the following options. </p>
</div>
<div class="row">
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" disabled>
Im not interested in this content
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.not-interested', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
I'm not interested in this content
</a></p>
</div>
@switch(request()->query('type'))
@case('comment')
<div class="col-12 col-md-8 offset-md-2 mb-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.spam.comment', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This comment contains spam
</a></p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold">
Its spam
</a></p>
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.sensitive.comment', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This comment contains sensitive content
</a>
</p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold">
It displays a sensitive image
</a></p>
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.abusive.comment', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
Its abusive or harmful
</a>
</p>
</div>
@break
@case('post')
<div class="col-12 col-md-8 offset-md-2 mb-3">
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.spam.post', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This post contains spam
</a>
</p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold">
Its abusive or harmful
</a></p>
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.sensitive.post', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This post contains sensitive content
</a>
</p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.abusive.post', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This post is abusive or harmful
</a>
</p>
</div>
@break
@case('user')
<div class="col-12 col-md-8 offset-md-2 mb-3">
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.spam.profile', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This users profile contains spam
</a>
</p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.sensitive.profile', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This users profile contains sensitive content
</a>
</p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p>
<a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.abusive.profile', ['type' => request()->query('type'),'id' => request()->query('id')])}}">
This profile is abusive or harmful
</a>
</p>
</div>
@break
@endswitch
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="font-weight-bold" href="#">
Learn more

View file

@ -12,6 +12,11 @@
<div class="p-5 text-center">
<p class="lead">You can <b class="font-weight-bold">unfollow</b> or <b class="font-weight-bold">mute</b> a user or hashtag from appearing in your timeline. Unless the content violates our terms of service, there is nothing we can do to remove it.</p>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card">
<div class="card-header lead font-weight-bold">
Report Sensitive Comment
</div>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="sensitive"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card">
<div class="card-header lead font-weight-bold">
Report Sensitive Post
</div>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="sensitive"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4 mb-5 pb-5">
<div class="col-12 col-md-8 offset-md-2">
<div class="card">
<div class="card-header lead font-weight-bold">
Report Sensitive Profile
</div>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="sensitive"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -13,21 +13,30 @@
<p class="lead">Please select one of the following options.</p>
</div>
<div class="row">
<div class="col-12 col-md-8 offset-md-2 my-3">
@switch(request()->query('type'))
@case('comment')
<div class="col-12 col-md-8 offset-md-2 mb-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.spam.comment')}}">
This comment contains spam
</a></p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
@break
@case('post')
<div class="col-12 col-md-8 offset-md-2 mb-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.spam.post')}}">
This post contains spam
</a></p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
@break
@case('user')
<div class="col-12 col-md-8 offset-md-2 mb-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="{{route('report.spam.profile')}}">
This users profile contains spam
</a></p>
</div>
@break
@endswitch
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="font-weight-bold" href="#">
Learn more

View file

@ -9,26 +9,29 @@
Report Post Spam
</div>
<div class="card-body">
<div class="p-5 text-center">
<p class="lead">Please select one of the following options.</p>
</div>
<div class="row">
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="#">
This comment contains spam
</a></p>
<div class="col-12 col-md-10 offset-md-1 my-3">
<form method="post" action="{{route('report.form')}}">
@csrf
<input type="hidden" name="report" value="spam"></input>
<input type="hidden" name="type" value="{{request()->query('type')}}"></input>
<input type="hidden" name="id" value="{{request()->query('id')}}"></input>
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-bold text-right">Message</label>
<div class="col-sm-9">
<textarea class="form-control" name="msg" placeholder="Add an optional message for mods/admins" rows="4"></textarea>
</div>
</div>
<hr>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-primary btn-block font-weight-bold">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="#">
This post contains spam
</a></p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<p><a class="btn btn-light btn-block p-4 font-weight-bold" href="#">
This users profile contains spam
</a></p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>

View file

@ -28,7 +28,7 @@
This users profile contains spam
</a></p>
</div>
<div class="col-12 col-md-8 offset-md-2 my-3">
<div class="col-12 col-md-8 offset-md-2">
<p><a class="font-weight-bold" href="#">
Learn more
</a> about our reporting guidelines and policy.</p>

View file

@ -39,6 +39,9 @@
<label for="bio" class="col-sm-3 col-form-label font-weight-bold text-right">Bio</label>
<div class="col-sm-9">
<textarea class="form-control" id="bio" name="bio" placeholder="Add a bio here" rows="2">{{Auth::user()->profile->bio}}</textarea>
<small class="form-text text-muted">
Max length: {{config('pixelfed.max_bio_length')}} characters.
</small>
</div>
</div>
<div class="pt-5">
@ -134,4 +137,4 @@
});
});
</script>
@endpush
@endpush

View file

@ -0,0 +1,91 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="col-12 col-md-8 offset-md-2 pt-4">
<div class="card">
<div class="card-header bg-white font-weight-bold d-flex justify-content-between align-items-center">
<span>Edit Status</span>
<a class="btn btn-outline-primary btn-sm font-weight-bold" href="{{$status->url()}}">Back to post</a>
</div>
<div class="card-body">
@csrf
<div class="form-group mb-0">
<label class="font-weight-bold text-muted small">CW/NSFW</label>
<div class="switch switch-sm">
<input type="checkbox" class="switch" id="cw-switch" name="cw" {{$status->is_nsfw==true?'checked=""':''}} disabled="">
<label for="cw-switch" class="small font-weight-bold">(Default off)</label>
</div>
</div>
</div>
</div>
@foreach($status->media()->orderBy('order')->get() as $media)
<div class="card mt-4 media-card">
<div class="card-header bg-white font-weight-bold">
Media #{{$media->order}}
</div>
<div class="card-body p-0">
<form method="post" enctype="multipart/form-data" class="media-form">
@csrf
<input type="hidden" name="media_id" value="{{$media->id}}">
<div class="filter-wrapper {{$media->filter_class}}" data-filter="{{$media->filter_class}}">
<img class="img-fluid" src="{{$media->url()}}" width="100%">
</div>
<div class="p-3">
<div class="form-group">
<label class="font-weight-bold text-muted small">Description</label>
<input class="form-control" name="media_caption" value="{{$media->caption}}" placeholder="Add a descriptive caption for screenreaders" autocomplete="off">
</div>
<div class="form-group form-filters" data-filter="{{$media->filter_class}}">
<label for="filterSelectDropdown" class="font-weight-bold text-muted small">Select Filter</label>
<select class="form-control filter-dropdown" name="media_filter"><option value="" selected="">No Filter</option><option value="filter-1977">1977</option><option value="filter-aden">Aden</option><option value="filter-amaro">Amaro</option><option value="filter-ashby">Ashby</option><option value="filter-brannan">Brannan</option><option value="filter-brooklyn">Brooklyn</option><option value="filter-charmes">Charmes</option><option value="filter-clarendon">Clarendon</option><option value="filter-crema">Crema</option><option value="filter-dogpatch">Dogpatch</option><option value="filter-earlybird">Earlybird</option><option value="filter-gingham">Gingham</option><option value="filter-ginza">Ginza</option><option value="filter-hefe">Hefe</option><option value="filter-helena">Helena</option><option value="filter-hudson">Hudson</option><option value="filter-inkwell">Inkwell</option><option value="filter-kelvin">Kelvin</option><option value="filter-juno">Kuno</option><option value="filter-lark">Lark</option><option value="filter-lofi">Lo-Fi</option><option value="filter-ludwig">Ludwig</option><option value="filter-maven">Maven</option><option value="filter-mayfair">Mayfair</option><option value="filter-moon">Moon</option><option value="filter-nashville">Nashville</option><option value="filter-perpetua">Perpetua</option><option value="filter-poprocket">Poprocket</option><option value="filter-reyes">Reyes</option><option value="filter-rise">Rise</option><option value="filter-sierra">Sierra</option><option value="filter-skyline">Skyline</option><option value="filter-slumber">Slumber</option><option value="filter-stinson">Stinson</option><option value="filter-sutro">Sutro</option><option value="filter-toaster">Toaster</option><option value="filter-valencia">Valencia</option><option value="filter-vesper">Vesper</option><option value="filter-walden">Walden</option><option value="filter-willow">Willow</option><option value="filter-xpro-ii">X-Pro II</option></select>
</div>
<hr>
<div class="form-group d-flex justify-content-between align-items-center mb-0">
<p class="text-muted font-weight-bold mb-0 small">Last Updated: {{$media->updated_at->diffForHumans()}}</p>
<button type="submit" class="btn btn-primary btn-sm font-weight-bold px-4">Update</button>
</div>
</div>
</form>
</div>
</div>
@endforeach
</div>
</div>
@endsection
@push('scripts')
<script type="text/javascript">
$('.form-filters').each(function(i,d) {
let el = $(d);
let filter = el.data('filter');
if(filter) {
var opt = el.find('option[value='+filter+']')[0];
$(opt).attr('selected','');
}
});
$('.media-form').on('submit', function(e){
e.preventDefault();
let el = $(this);
let id = el.find('input[name=media_id]').val();
let caption = el.find('input[name=media_caption]').val();
let filter = el.find('.filter-dropdown option:selected').val();
axios.post(window.location.href, {
'id': id,
'caption': caption,
'filter': filter
}).then((res) => {
swal('Success!', 'You have successfully updated your post', 'success');
}).catch((err) => {
swal('Something went wrong', 'An error occured, please try again later', 'error');
});
});
</script>
@endpush

View file

@ -0,0 +1,65 @@
@extends('layouts.app',['title' => $user->username . " posted a photo: " . $status->likes_count . " likes, " . $status->comments_count . " comments" ])
@section('content')
<div class="container px-0 mt-md-4">
<div class="card card-md-rounded-0 status-container orientation-{{$status->firstMedia()->orientation ?? 'unknown'}}">
<div class="row mx-0">
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
<a href="{{$user->url()}}" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" title="{{$user->username}}">
<div class="status-avatar mr-2">
<img src="{{$user->avatarUrl()}}" width="24px" height="24px" style="border-radius:12px;">
</div>
<div class="username">
<span class="username-link font-weight-bold text-dark">{{$user->username}}</span>
</div>
</a>
</div>
<div class="col-12 col-md-8 status-photo px-0">
@if($status->is_nsfw)
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p>
</summary>
@endif
<div id="photoCarousel" class="carousel slide carousel-fade" data-ride="carousel">
<ol class="carousel-indicators">
@for($i = 0; $i < $status->media_count; $i++)
<li data-target="#photoCarousel" data-slide-to="{{$i}}" class="{{$i == 0 ? 'active' : ''}}"></li>
@endfor
</ol>
<div class="carousel-inner">
@foreach($status->media()->orderBy('order')->get() as $media)
<div class="carousel-item {{$loop->iteration == 1 ? 'active' : ''}}">
<figure class="{{$media->filter_class}}">
<img class="d-block w-100" src="{{$media->url()}}" title="{{$media->caption}}" data-toggle="tooltip" data-placement="bottom">
</figure>
</div>
@endforeach
</div>
<a class="carousel-control-prev" href="#photoCarousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#photoCarousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
@if($status->is_nsfw)
</details>
@endif
</div>
@include('status.show.sidebar')
</div>
</div>
</div>
@endsection
@push('meta')
<meta property="og:description" content="{{ $status->caption }}">
<meta property="og:image" content="{{$status->mediaUrl()}}">
<link href='{{$status->url()}}' rel='alternate' type='application/activity+json'>
@endpush

View file

@ -0,0 +1,46 @@
@extends('layouts.app',['title' => $user->username . " posted a photo: " . $status->likes_count . " likes, " . $status->comments_count . " comments" ])
@section('content')
<div class="container px-0 mt-md-4">
<div class="card card-md-rounded-0 status-container orientation-{{$status->firstMedia()->orientation ?? 'unknown'}}">
<div class="row mx-0">
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
<a href="{{$user->url()}}" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" title="{{$user->username}}">
<div class="status-avatar mr-2">
<img src="{{$user->avatarUrl()}}" width="24px" height="24px" style="border-radius:12px;">
</div>
<div class="username">
<span class="username-link font-weight-bold text-dark">{{$user->username}}</span>
</div>
</a>
</div>
<div class="col-12 col-md-8 status-photo px-0">
@if($status->is_nsfw && $status->media_count == 1)
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}">
<img class="card-img-top" src="{{$status->mediaUrl()}}" title="{{$status->firstMedia()->caption}}" data-toggle="tooltip" data-tooltip-placement="bottom">
</a>
</details>
@elseif(!$status->is_nsfw && $status->media_count == 1)
<div class="{{$status->firstMedia()->filter_class}}">
<img src="{{$status->mediaUrl()}}" width="100%" title="{{$status->firstMedia()->caption}}" data-toggle="tooltip" data-placement="bottom">
</div>
@endif
</div>
@include('status.show.sidebar')
</div>
</div>
</div>
@endsection
@push('meta')
<meta property="og:description" content="{{ $status->caption }}">
<meta property="og:image" content="{{$status->mediaUrl()}}">
<link href='{{$status->url()}}' rel='alternate' type='application/activity+json'>
@endpush

View file

@ -0,0 +1,101 @@
<div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
<a href="{{$user->url()}}" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" title="{{$user->username}}">
<div class="status-avatar mr-2">
<img src="{{$user->avatarUrl()}}" width="24px" height="24px" style="border-radius:12px;">
</div>
<div class="username">
<span class="username-link font-weight-bold text-dark">{{$user->username}}</span>
</div>
</a>
<div class="float-right">
<div class="dropdown">
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
<span class="fas fa-ellipsis-v text-muted"></span>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item font-weight-bold" href="{{$status->reportUrl()}}">Report</a>
{{-- <a class="dropdown-item" href="#">Embed</a> --}}
@if(Auth::check())
@if(Auth::user()->profile->id === $status->profile->id || Auth::user()->is_admin == true)
{{-- <a class="dropdown-item" href="{{$status->editUrl()}}">Edit</a> --}}
<form method="post" action="/i/delete">
@csrf
<input type="hidden" name="type" value="post">
<input type="hidden" name="item" value="{{$status->id}}">
<button type="submit" class="dropdown-item btn btn-link font-weight-bold">Delete</button>
</form>
@endif
@endif
</div>
</div>
</div>
</div>
<div class="d-flex flex-md-column flex-column-reverse h-100">
<div class="card-body status-comments">
<div class="status-comment">
<p class="mb-1">
<span class="font-weight-bold pr-1">{{$status->profile->username}}</span>
<span class="comment-text" v-pre>{!! $status->rendered ?? e($status->caption) !!}</span>
</p>
<p class="mb-1"><a href="{{$status->url()}}/c" class="text-muted">View all comments</a></p>
<div class="comments">
@foreach($replies as $item)
<p class="mb-1">
<span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$item->profile->url()}}">{{ str_limit($item->profile->username, 15)}}</a></bdi></span>
<span class="comment-text" v-pre>{!! $item->rendered ?? e($item->caption) !!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right pl-2">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
</p>
@endforeach
</div>
</div>
</div>
<div class="card-body flex-grow-0 py-1">
<div class="reactions my-1">
@if(Auth::check())
<form class="d-inline-flex pr-3" method="post" action="/i/like" style="display: inline;" data-id="{{$status->id}}" data-action="like">
@csrf
<input type="hidden" name="item" value="{{$status->id}}">
<button class="btn btn-link text-dark p-0 border-0" type="submit" title="Like!">
<h3 class="m-0 {{$status->liked() ? 'fas fa-heart text-danger':'far fa-heart text-dark'}}"></h3>
</button>
</form>
<h3 class="far fa-comment pr-3 m-0" title="Comment"></h3>
<form class="d-inline-flex share-form pr-3" method="post" action="/i/share" style="display: inline;" data-id="{{$status->id}}" data-action="share" data-count="{{$status->shares_count}}">
@csrf
<input type="hidden" name="item" value="{{$status->id}}">
<button class="btn btn-link text-dark p-0" type="submit" title="Share">
<h3 class="m-0 {{$status->shared() ? 'fas fa-share-square text-primary':'far fa-share-square '}}"></h3>
</button>
</form>
@endif
<span class="float-right">
<form class="d-inline-flex " method="post" action="/i/bookmark" style="display: inline;" data-id="{{$status->id}}" data-action="bookmark">
@csrf
<input type="hidden" name="item" value="{{$status->id}}">
<button class="btn btn-link text-dark p-0 border-0" type="submit" title="Save">
<h3 class="m-0 {{$status->bookmarked() ? 'fas fa-bookmark text-warning':'far fa-bookmark'}}"></h3>
</button>
</form>
</span>
</div>
<div class="likes font-weight-bold mb-0">
<span class="like-count" data-count="{{$status->likes_count}}">{{$status->likes_count}}</span> likes
</div>
<div class="timestamp">
<a href="{{$status->url()}}" class="small text-muted">
{{$status->created_at->format('F j, Y')}}
</a>
</div>
</div>
</div>
<div class="card-footer bg-white sticky-md-bottom">
<form class="comment-form" method="post" action="/i/comment" data-id="{{$status->id}}" data-truncate="false">
@csrf
<input type="hidden" name="item" value="{{$status->id}}">
<input class="form-control" name="comment" placeholder="Add a comment..." autocomplete="off">
</form>
</div>
</div>

View file

@ -0,0 +1,50 @@
@extends('layouts.app',['title' => $user->username . " posted a photo: " . $status->likes_count . " likes, " . $status->comments_count . " comments" ])
@section('content')
<div class="container px-0 mt-md-4">
<div class="card card-md-rounded-0 status-container orientation-video">
<div class="row mx-0">
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
<a href="{{$user->url()}}" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" title="{{$user->username}}">
<div class="status-avatar mr-2">
<img src="{{$user->avatarUrl()}}" width="24px" height="24px" style="border-radius:12px;">
</div>
<div class="username">
<span class="username-link font-weight-bold text-dark">{{$user->username}}</span>
</div>
</a>
</div>
<div class="col-12 col-md-8 status-photo px-0">
@if($status->is_nsfw && $status->media_count == 1)
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<div class="embed-responsive embed-responsive-16by9">
<video class="embed-responsive-item" controls="">
<source src="{{$status->mediaUrl()}}" type="video/mp4">
</video>
</div>
</details>
@elseif(!$status->is_nsfw && $status->media_count == 1)
<div class="embed-responsive embed-responsive-16by9">
<video class="embed-responsive-item" controls="">
<source src="{{$status->mediaUrl()}}" type="video/mp4">
</video>
</div>
@endif
</div>
@include('status.show.sidebar')
</div>
</div>
</div>
@endsection
@push('meta')
<meta property="og:description" content="{{ $status->caption }}">
<meta property="og:image" content="{{$status->mediaUrl()}}">
<link href='{{$status->url()}}' rel='alternate' type='application/activity+json'>
@endpush

View file

@ -67,11 +67,18 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
Route::group(['prefix' => 'report'], function() {
Route::get('/', 'ReportController@showForm')->name('report.form');
Route::post('/', 'ReportController@formStore');
Route::get('not-interested', 'ReportController@notInterestedForm')->name('report.not-interested');
Route::get('spam', 'ReportController@spamForm')->name('report.spam');
Route::get('spam/comment', 'ReportController@spamCommentForm')->name('report.spam.comment');
Route::get('spam/post', 'ReportController@spamPostForm')->name('report.spam.post');
Route::get('spam/profile', 'ReportController@spamProfileForm')->name('report.spam.profile');
Route::get('sensitive/comment', 'ReportController@sensitiveCommentForm')->name('report.sensitive.comment');
Route::get('sensitive/post', 'ReportController@sensitivePostForm')->name('report.sensitive.post');
Route::get('sensitive/profile', 'ReportController@sensitiveProfileForm')->name('report.sensitive.profile');
Route::get('abusive/comment', 'ReportController@abusiveCommentForm')->name('report.abusive.comment');
Route::get('abusive/post', 'ReportController@abusivePostForm')->name('report.abusive.post');
Route::get('abusive/profile', 'ReportController@abusiveProfileForm')->name('report.abusive.profile');
});
});
@ -128,6 +135,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(fu
Route::get('p/{username}/{id}/c/{cid}', 'CommentController@show');
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
Route::get('p/{username}/{id}/edit', 'StatusController@edit');
Route::post('p/{username}/{id}/edit', 'StatusController@editStore');
Route::get('p/{username}/{id}', 'StatusController@show');
Route::get('{username}/saved', 'ProfileController@savedBookmarks');
Route::get('{username}/followers', 'ProfileController@followers');

31
tests/Unit/CryptoTest.php Normal file
View file

@ -0,0 +1,31 @@
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class CryptoTest extends TestCase
{
/**
* A basic test to check if PHPSecLib is installed.
*
* @return void
*/
public function testLibraryInstalled()
{
$this->assertTrue(class_exists('\phpseclib\Crypt\RSA'));
}
public function testRSASigning()
{
$rsa = new \phpseclib\Crypt\RSA();
extract($rsa->createKey());
$rsa->loadKey($privatekey);
$plaintext = 'pixelfed rsa test';
$signature = $rsa->sign($plaintext);
$rsa->loadKey($publickey);
$this->assertTrue($rsa->verify($plaintext, $signature));
}
}