mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-27 08:43:17 +00:00
commit
3160f719b6
19 changed files with 320 additions and 0 deletions
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
99
app/Http/Controllers/SpaController.php
Normal file
99
app/Http/Controllers/SpaController.php
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
];
|
];
|
||||||
|
|
BIN
public/css/spa.css
vendored
Normal file
BIN
public/css/spa.css
vendored
Normal file
Binary file not shown.
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
BIN
public/js/components.js
vendored
BIN
public/js/components.js
vendored
Binary file not shown.
BIN
public/js/compose.js
vendored
BIN
public/js/compose.js
vendored
Binary file not shown.
BIN
public/js/discover.js
vendored
BIN
public/js/discover.js
vendored
Binary file not shown.
BIN
public/js/spa.js
vendored
Normal file
BIN
public/js/spa.js
vendored
Normal file
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/stories.js
vendored
BIN
public/js/stories.js
vendored
Binary file not shown.
BIN
public/js/story-compose.js
vendored
BIN
public/js/story-compose.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
Binary file not shown.
51
resources/views/layouts/spa.blade.php
Normal file
51
resources/views/layouts/spa.blade.php
Normal 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>
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
Loading…
Reference in a new issue