Merge pull request #3064 from pixelfed/staging

Staging
This commit is contained in:
daniel 2021-12-21 21:57:34 -07:00 committed by GitHub
commit 3160f719b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 342 additions and 18 deletions

View File

@ -2459,4 +2459,126 @@ class ApiV1Controller extends Controller
->values(); ->values();
return response()->json(compact('posts')); return response()->json(compact('posts'));
} }
/**
* GET /api/v2/statuses/{id}/replies
*
*
* @return array
*/
public function statusReplies(Request $request, $id)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'limit' => 'int|min:1|max:10',
'sort' => 'in:all,newest,popular'
]);
$limit = $request->input('limit', 3);
$pid = $request->user()->profile_id;
$status = StatusService::get($id);
abort_if(!in_array($status['visibility'], ['public', 'unlisted']), 404);
$sortBy = $request->input('sort', 'all');
if($sortBy == 'all' && !$request->has('cursor')) {
$ids = Cache::remember('status:replies:all:' . $id, 900, function() use($id) {
return DB::table('statuses')
->where('in_reply_to_id', $id)
->orderBy('id')
->cursorPaginate(3);
});
} else {
$ids = DB::table('statuses')
->where('in_reply_to_id', $id)
->when($sortBy, function($q, $sortBy) {
if($sortBy === 'all') {
return $q->orderBy('id');
}
if($sortBy === 'newest') {
return $q->orderByDesc('created_at');
}
if($sortBy === 'popular') {
return $q->orderByDesc('likes_count');
}
})
->cursorPaginate($limit);
}
$data = $ids->map(function($post) use($pid) {
$status = StatusService::get($post->id);
if(!$status || !isset($status['id'])) {
return false;
}
$status['favourited'] = LikeService::liked($pid, $post->id);
return $status;
})
->filter(function($post) {
return $post && isset($post['id']);
})
->values();
$res = [
'data' => $data,
'next' => $ids->nextPageUrl()
];
return $res;
}
/**
* GET /api/v2/statuses/{id}/state
*
*
* @return array
*/
public function statusState(Request $request, $id)
{
abort_if(!$request->user(), 403);
$status = Status::findOrFail($id);
$pid = $request->user()->profile_id;
abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404);
return StatusService::getState($status->id, $pid);
}
/**
* GET /api/v1/discover/accounts/popular
*
*
* @return array
*/
public function discoverAccountsPopular(Request $request)
{
abort_if(!$request->user(), 403);
$pid = $request->user()->profile_id;
$ids = DB::table('profiles')
->where('is_private', false)
->whereNull('status')
->orderByDesc('profiles.followers_count')
->limit(20)
->get();
$ids = $ids->map(function($profile) {
return AccountService::get($profile->id);
})
->filter(function($profile) use($pid) {
return $profile &&
isset($profile['id']) &&
!FollowerService::follows($pid, $profile['id']) &&
$profile['id'] != $pid;
})
->take(6)
->values();
return response()->json($ids, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
} }

View File

@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Cache;
use DB;
use League\CommonMark\CommonMarkConverter;
use App\Services\AccountService;
use App\Services\StatusService;
use App\Services\SnowflakeService;
class SpaController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
abort_unless(config('exp.spa'), 404);
return view('layouts.spa');
}
public function webPost(Request $request, $id)
{
abort_unless(config('exp.spa'), 404);
if($request->user()) {
return view('layouts.spa');
}
if(SnowflakeService::byDate(now()->subDays(30)) > $id) {
abort(404);
}
$post = StatusService::get($id);
if(
$post &&
isset($post['url']) &&
isset($post['local']) &&
$post['local'] === true
) {
return redirect($post['url']);
}
abort(404);
}
public function webProfile(Request $request, $id)
{
abort_unless(config('exp.spa'), 404);
if($request->user()) {
if(substr($id, 0, 1) == '@') {
$id = AccountService::usernameToId(substr($id, 1));
return redirect("/i/web/profile/{$id}");
}
return view('layouts.spa');
}
$account = AccountService::get($id);
if($account && isset($account['url'])) {
return redirect($account['url']);
}
return redirect('404');
}
public function getPrivacy()
{
$body = $this->markdownToHtml('views/page/privacy.md');
return [
'body' => $body
];
}
public function getTerms()
{
$body = $this->markdownToHtml('views/page/terms.md');
return [
'body' => $body
];
}
protected function markdownToHtml($src, $ttl = 600)
{
return Cache::remember(
'pf:doc_cache:markdown:' . $src,
$ttl,
function() use($src) {
$path = resource_path($src);
$file = file_get_contents($path);
$converter = new CommonMarkConverter();
return (string) $converter->convertToHtml($file);
});
}
}

View File

@ -41,6 +41,25 @@ class StatusService
}); });
} }
public static function getState($id, $pid)
{
$status = self::get($id, false);
if(!$status) {
return [
'liked' => false,
'shared' => false,
'bookmarked' => false
];
}
return [
'liked' => LikeService::liked($pid, $id),
'shared' => self::isShared($id, $pid),
'bookmarked' => self::isBookmarked($id, $pid)
];
}
public static function getFull($id, $pid, $publicOnly = true) public static function getFull($id, $pid, $publicOnly = true)
{ {
$res = self::get($id, $publicOnly); $res = self::get($id, $publicOnly);
@ -89,4 +108,24 @@ class StatusService
self::get($id, false); self::get($id, false);
self::get($id, true); self::get($id, true);
} }
public static function isShared($id, $pid = null)
{
return $pid ?
DB::table('statuses')
->where('reblog_of_id', $id)
->where('profile_id', $pid)
->exists() :
false;
}
public static function isBookmarked($id, $pid = null)
{
return $pid ?
DB::table('bookmarks')
->where('status_id', $id)
->where('profile_id', $pid)
->exists() :
false;
}
} }

View File

@ -8,4 +8,6 @@ return [
'top' => env('EXP_TOP', false), 'top' => env('EXP_TOP', false),
'polls' => env('EXP_POLLS', false), 'polls' => env('EXP_POLLS', false),
'cached_public_timeline' => env('EXP_CPT', false), 'cached_public_timeline' => env('EXP_CPT', false),
'gps' => env('EXP_GPS', false),
'spa' => env('EXP_SPA', false),
]; ];

1
public/css/spa.css vendored Normal file
View File

@ -0,0 +1 @@
@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/zYXgKVElMYYaJe8bpLHnCwDKhdzeFaxOedfTDw.woff2) format("woff2");unicode-range:U+0460-052f,U+1c80-1c88,U+20b4,U+2de0-2dff,U+a640-a69f,U+fe2e-fe2f}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/zYXgKVElMYYaJe8bpLHnCwDKhdXeFaxOedfTDw.woff2) format("woff2");unicode-range:U+0400-045f,U+0490-0491,U+04b0-04b1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/zYXgKVElMYYaJe8bpLHnCwDKhdLeFaxOedfTDw.woff2) format("woff2");unicode-range:U+0370-03ff}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/zYXgKVElMYYaJe8bpLHnCwDKhd7eFaxOedfTDw.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01a0-01a1,U+01af-01b0,U+1ea0-1ef9,U+20ab}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/zYXgKVElMYYaJe8bpLHnCwDKhd_eFaxOedfTDw.woff2) format("woff2");unicode-range:U+0100-024f,U+0259,U+1e??,U+2020,U+20a0-20ab,U+20ad-20cf,U+2113,U+2c60-2c7f,U+a720-a7ff}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/zYXgKVElMYYaJe8bpLHnCwDKhdHeFaxOedc.woff2) format("woff2");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/zYX9KVElMYYaJe8bpLHnCwDKjQ76AIxsdP3pBmtF8A.woff2) format("woff2");unicode-range:U+0460-052f,U+1c80-1c88,U+20b4,U+2de0-2dff,U+a640-a69f,U+fe2e-fe2f}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/zYX9KVElMYYaJe8bpLHnCwDKjQ76AIVsdP3pBmtF8A.woff2) format("woff2");unicode-range:U+0400-045f,U+0490-0491,U+04b0-04b1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/zYX9KVElMYYaJe8bpLHnCwDKjQ76AIJsdP3pBmtF8A.woff2) format("woff2");unicode-range:U+0370-03ff}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/zYX9KVElMYYaJe8bpLHnCwDKjQ76AI5sdP3pBmtF8A.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01a0-01a1,U+01af-01b0,U+1ea0-1ef9,U+20ab}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/zYX9KVElMYYaJe8bpLHnCwDKjQ76AI9sdP3pBmtF8A.woff2) format("woff2");unicode-range:U+0100-024f,U+0259,U+1e??,U+2020,U+20a0-20ab,U+20ad-20cf,U+2113,U+2c60-2c7f,U+a720-a7ff}@font-face{font-family:IBM Plex Sans;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/zYX9KVElMYYaJe8bpLHnCwDKjQ76AIFsdP3pBms.woff2) format("woff2");unicode-range:U+00??,U+0131,U+0152-0153,U+02bb-02bc,U+02c6,U+02da,U+02dc,U+2000-206f,U+2074,U+20ac,U+2122,U+2191,U+2193,U+2212,U+2215,U+feff,U+fffd}body{background:#f3f4f6;font-family:IBM Plex Sans,sans-serif}.primary{color:#3b82f6}.web-wrapper{margin-bottom:10rem}.jumbotron{border-radius:18px}.doc-body p:last-child{margin-bottom:0}

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/spa.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/vendor.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,21 +1,22 @@
{ {
"/js/manifest.js": "/js/manifest.js?id=7db827d654313dce4250", "/js/manifest.js": "/js/manifest.js?id=7db827d654313dce4250",
"/js/vendor.js": "/js/vendor.js?id=e95a4fe1c5a9580438d3", "/js/vendor.js": "/js/vendor.js?id=131c5e4445728ce4224a",
"/js/activity.js": "/js/activity.js?id=1d855c03383566536e13", "/js/activity.js": "/js/activity.js?id=1d855c03383566536e13",
"/js/admin.js": "/js/admin.js?id=05c8d6766650050d8ef0", "/js/admin.js": "/js/admin.js?id=05c8d6766650050d8ef0",
"/js/app.js": "/js/app.js?id=cc182786e3a5693b1433", "/js/app.js": "/js/app.js?id=26f7c3ba07877a3e7953",
"/css/app.css": "/css/app.css?id=70554ace925b5a7ecef2", "/css/app.css": "/css/app.css?id=70554ace925b5a7ecef2",
"/css/appdark.css": "/css/appdark.css?id=ccdcd84f1087f89bd7b2", "/css/appdark.css": "/css/appdark.css?id=ccdcd84f1087f89bd7b2",
"/css/admin.css": "/css/admin.css?id=8884d2f48c1dd491ba04", "/css/admin.css": "/css/admin.css?id=8884d2f48c1dd491ba04",
"/css/spa.css": "/css/spa.css?id=0dbac8e067414fa2195e",
"/css/landing.css": "/css/landing.css?id=b6a06b969dd1c3c274e6", "/css/landing.css": "/css/landing.css?id=b6a06b969dd1c3c274e6",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=684458578e59365b9b7f", "/js/collectioncompose.js": "/js/collectioncompose.js?id=684458578e59365b9b7f",
"/js/collections.js": "/js/collections.js?id=0d1fc5ad7d4a47a8adbb", "/js/collections.js": "/js/collections.js?id=0d1fc5ad7d4a47a8adbb",
"/js/components.js": "/js/components.js?id=56aa48f8042553148a78", "/js/components.js": "/js/components.js?id=2978883dc0ca9cd1681c",
"/js/compose.js": "/js/compose.js?id=fe56d0c40817ec2b0ec7", "/js/compose.js": "/js/compose.js?id=76bc7fa5a894c0cac4c5",
"/js/compose-classic.js": "/js/compose-classic.js?id=ee4ad4759a55261c429c", "/js/compose-classic.js": "/js/compose-classic.js?id=ee4ad4759a55261c429c",
"/js/developers.js": "/js/developers.js?id=d578157a93aaa9070669", "/js/developers.js": "/js/developers.js?id=d578157a93aaa9070669",
"/js/direct.js": "/js/direct.js?id=76bdac276fca6ac60320", "/js/direct.js": "/js/direct.js?id=76bdac276fca6ac60320",
"/js/discover.js": "/js/discover.js?id=4230e32e8f7d7413b6c4", "/js/discover.js": "/js/discover.js?id=32ad2e3e43a9b9c6d2c4",
"/js/hashtag.js": "/js/hashtag.js?id=5dfd9c4873cc9d78727c", "/js/hashtag.js": "/js/hashtag.js?id=5dfd9c4873cc9d78727c",
"/js/loops.js": "/js/loops.js?id=4ae79e81965c4fd1ae66", "/js/loops.js": "/js/loops.js?id=4ae79e81965c4fd1ae66",
"/js/profile.js": "/js/profile.js?id=a2e8bd5346850f805b5c", "/js/profile.js": "/js/profile.js?id=a2e8bd5346850f805b5c",
@ -23,8 +24,9 @@
"/js/rempos.js": "/js/rempos.js?id=2ef46b249c6390aa4ef8", "/js/rempos.js": "/js/rempos.js?id=2ef46b249c6390aa4ef8",
"/js/rempro.js": "/js/rempro.js?id=3971b84aef886204d286", "/js/rempro.js": "/js/rempro.js?id=3971b84aef886204d286",
"/js/search.js": "/js/search.js?id=dc888b173463dc3894ba", "/js/search.js": "/js/search.js?id=dc888b173463dc3894ba",
"/js/status.js": "/js/status.js?id=4493959f638ad286630c", "/js/spa.js": "/js/spa.js?id=432974792792e560cf8d",
"/js/stories.js": "/js/stories.js?id=d52c02c0e4970022d76c", "/js/status.js": "/js/status.js?id=49fd7ac5b254220021f9",
"/js/story-compose.js": "/js/story-compose.js?id=d60103d1ed56705af2dc", "/js/stories.js": "/js/stories.js?id=ca9d1db6b472a5d58392",
"/js/timeline.js": "/js/timeline.js?id=3adb0631a435da363ba7" "/js/story-compose.js": "/js/story-compose.js?id=0f9d2f7ce5e63ef14c6a",
"/js/timeline.js": "/js/timeline.js?id=9957922a584a21f8f74f"
} }

View File

@ -0,0 +1,51 @@
<!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="mobile-web-app-capable" content="yes">
<title>{{ $title ?? config_cache('app.name') }}</title>
<link rel="manifest" href="/manifest.json">
<meta property="og:site_name" content="{{ config_cache('app.name') }}">
<meta property="og:title" content="{{ $title ?? config_cache('app.name') }}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{url(request()->url())}}">
@stack('meta')
<meta name="medium" content="image">
<meta name="theme-color" content="#10c5f8">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="canonical" href="{{url(request()->url())}}">
<link href="{{ mix('css/app.css') }}" rel="stylesheet" data-stylesheet="light">
<link href="{{ mix('css/spa.css') }}" rel="stylesheet" data-stylesheet="light">
@auth
<script type="text/javascript">
window._sharedData = {
curUser: {},
user: {!! json_encode(\App\Services\ProfileService::get(request()->user()->profile_id)) !!},
version: 0
};
window.App = {
config: {!!App\Util\Site\Config::json()!!}
};
</script>
@endauth
</head>
<body class="loggedIn">
<main id="content">
<noscript>
<div class="container">
<p class="pt-5 text-center lead">Please enable javascript to view this content.</p>
</div>
</noscript>
<navbar></navbar>
<router-view></router-view>
</main>
<script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/vendor.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/spa.js') }}"></script>
</body>
</html>

View File

@ -85,6 +85,8 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::group(['prefix' => 'v2'], function() use($middleware) { Route::group(['prefix' => 'v2'], function() use($middleware) {
Route::get('search', 'Api\ApiV1Controller@searchV2')->middleware($middleware); Route::get('search', 'Api\ApiV1Controller@searchV2')->middleware($middleware);
Route::get('statuses/{id}/replies', 'Api\ApiV1Controller@statusReplies')->middleware($middleware);
Route::get('statuses/{id}/state', 'Api\ApiV1Controller@statusState')->middleware($middleware);
}); });
}); });

View File

@ -333,6 +333,11 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('warning', 'AccountInterstitialController@get'); Route::get('warning', 'AccountInterstitialController@get');
Route::post('warning', 'AccountInterstitialController@read'); Route::post('warning', 'AccountInterstitialController@read');
Route::get('my2020', 'SeasonalController@yearInReview'); Route::get('my2020', 'SeasonalController@yearInReview');
Route::get('web/post/{id}', 'SpaController@webPost');
Route::get('web/profile/{id}', 'SpaController@webProfile');
Route::get('web/{q}', 'SpaController@index')->where('q', '.*');
Route::get('web', 'SpaController@index');
}); });
Route::group(['prefix' => 'account'], function () { Route::group(['prefix' => 'account'], function () {