mirror of
https://github.com/YunoHost-Apps/pixelfed_ynh.git
synced 2024-09-03 20:06:04 +02:00
commit
14a1a0283c
73 changed files with 1742 additions and 72 deletions
|
@ -2,6 +2,9 @@
|
|||
|
||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.4...dev)
|
||||
|
||||
### New Features
|
||||
- Portfolios ([#3705](https://github.com/pixelfed/pixelfed/pull/3705))
|
||||
|
||||
### Updates
|
||||
- Update ApiV1Controller, include self likes in favourited_by endpoint ([58b331d2](https://github.com/pixelfed/pixelfed/commit/58b331d2))
|
||||
- Update PublicApiController, remove expensive and unused relationships ([2ecc3144](https://github.com/pixelfed/pixelfed/commit/2ecc3144))
|
||||
|
|
318
app/Http/Controllers/PortfolioController.php
Normal file
318
app/Http/Controllers/PortfolioController.php
Normal file
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Portfolio;
|
||||
use Cache;
|
||||
use DB;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\StatusService;
|
||||
|
||||
class PortfolioController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('portfolio.index');
|
||||
}
|
||||
|
||||
public function show(Request $request, $username)
|
||||
{
|
||||
$user = User::whereUsername($username)->first();
|
||||
|
||||
if(!$user) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
$portfolio = Portfolio::whereUserId($user->id)->firstOrFail();
|
||||
$user = AccountService::get($user->profile_id);
|
||||
|
||||
if($user['locked']) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
if($portfolio->active != true) {
|
||||
if(!$request->user()) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
if($request->user()->profile_id == $user['id']) {
|
||||
return redirect(config('portfolio.path') . '/settings');
|
||||
}
|
||||
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
return view('portfolio.show', compact('user', 'portfolio'));
|
||||
}
|
||||
|
||||
public function showPost(Request $request, $username, $id)
|
||||
{
|
||||
$authed = $request->user();
|
||||
$post = StatusService::get($id);
|
||||
|
||||
if(!$post) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
$user = AccountService::get($post['account']['id']);
|
||||
$portfolio = Portfolio::whereProfileId($user['id'])->first();
|
||||
|
||||
if($user['locked'] || $portfolio->active != true) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
if(!$post || $post['visibility'] != 'public' || $post['pf_type'] != 'photo' || $user['id'] != $post['account']['id']) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
return view('portfolio.show_post', compact('user', 'post', 'authed'));
|
||||
}
|
||||
|
||||
public function myRedirect(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if(Portfolio::whereProfileId($user->profile_id)->exists() === false) {
|
||||
$portfolio = new Portfolio;
|
||||
$portfolio->profile_id = $user->profile_id;
|
||||
$portfolio->user_id = $user->id;
|
||||
$portfolio->active = false;
|
||||
$portfolio->save();
|
||||
}
|
||||
|
||||
$domain = config('portfolio.domain');
|
||||
$path = config('portfolio.path');
|
||||
$url = 'https://' . $domain . $path;
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
public function settings(Request $request)
|
||||
{
|
||||
if(!$request->user()) {
|
||||
return redirect(route('home'));
|
||||
}
|
||||
|
||||
$portfolio = Portfolio::whereUserId($request->user()->id)->first();
|
||||
|
||||
if(!$portfolio) {
|
||||
$portfolio = new Portfolio;
|
||||
$portfolio->user_id = $request->user()->id;
|
||||
$portfolio->profile_id = $request->user()->profile_id;
|
||||
$portfolio->save();
|
||||
}
|
||||
|
||||
return view('portfolio.settings', compact('portfolio'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
abort_unless($request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'profile_source' => 'required|in:recent,custom',
|
||||
'layout' => 'required|in:grid,masonry',
|
||||
'layout_container' => 'required|in:fixed,fluid'
|
||||
]);
|
||||
|
||||
$portfolio = Portfolio::whereUserId($request->user()->id)->first();
|
||||
|
||||
if(!$portfolio) {
|
||||
$portfolio = new Portfolio;
|
||||
$portfolio->user_id = $request->user()->id;
|
||||
$portfolio->profile_id = $request->user()->profile_id;
|
||||
$portfolio->save();
|
||||
}
|
||||
|
||||
$portfolio->active = $request->input('enabled') === 'on';
|
||||
$portfolio->show_captions = $request->input('show_captions') === 'on';
|
||||
$portfolio->show_license = $request->input('show_license') === 'on';
|
||||
$portfolio->show_location = $request->input('show_location') === 'on';
|
||||
$portfolio->show_timestamp = $request->input('show_timestamp') === 'on';
|
||||
$portfolio->show_link = $request->input('show_link') === 'on';
|
||||
$portfolio->profile_source = $request->input('profile_source');
|
||||
$portfolio->show_avatar = $request->input('show_avatar') === 'on';
|
||||
$portfolio->show_bio = $request->input('show_bio') === 'on';
|
||||
$portfolio->profile_layout = $request->input('layout');
|
||||
$portfolio->profile_container = $request->input('layout_container');
|
||||
$portfolio->save();
|
||||
|
||||
return redirect('/' . $request->user()->username);
|
||||
}
|
||||
|
||||
public function getFeed(Request $request, $id)
|
||||
{
|
||||
$user = AccountService::get($id, true);
|
||||
|
||||
if(!$user || !isset($user['id'])) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
$portfolio = Portfolio::whereProfileId($user['id'])->first();
|
||||
|
||||
if(!$portfolio || !$portfolio->active) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
if($portfolio->profile_source === 'custom' && $portfolio->metadata) {
|
||||
return $this->getCustomFeed($portfolio);
|
||||
}
|
||||
|
||||
return $this->getRecentFeed($user['id']);
|
||||
}
|
||||
|
||||
protected function getCustomFeed($portfolio) {
|
||||
if(!$portfolio->metadata['posts']) {
|
||||
return response()->json([], 400);
|
||||
}
|
||||
|
||||
return collect($portfolio->metadata['posts'])->map(function($p) {
|
||||
return StatusService::get($p);
|
||||
})
|
||||
->filter(function($p) {
|
||||
return $p && isset($p['account']);
|
||||
})->values();
|
||||
}
|
||||
|
||||
protected function getRecentFeed($id) {
|
||||
$media = Cache::remember('portfolio:recent-feed:' . $id, 3600, function() use($id) {
|
||||
return DB::table('media')
|
||||
->whereProfileId($id)
|
||||
->whereNotNull('status_id')
|
||||
->groupBy('status_id')
|
||||
->orderByDesc('id')
|
||||
->take(50)
|
||||
->pluck('status_id');
|
||||
});
|
||||
|
||||
return $media->map(function($sid) use($id) {
|
||||
return StatusService::get($sid);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post &&
|
||||
isset($post['media_attachments']) &&
|
||||
!empty($post['media_attachments']) &&
|
||||
$post['pf_type'] === 'photo' &&
|
||||
$post['visibility'] === 'public';
|
||||
})
|
||||
->take(24)
|
||||
->values();
|
||||
}
|
||||
|
||||
public function getSettings(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$res = Portfolio::whereUserId($request->user()->id)->get();
|
||||
|
||||
if(!$res) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $res->map(function($p) {
|
||||
return [
|
||||
'url' => $p->url(),
|
||||
'pid' => (string) $p->profile_id,
|
||||
'active' => (bool) $p->active,
|
||||
'show_captions' => (bool) $p->show_captions,
|
||||
'show_license' => (bool) $p->show_license,
|
||||
'show_location' => (bool) $p->show_location,
|
||||
'show_timestamp' => (bool) $p->show_timestamp,
|
||||
'show_link' => (bool) $p->show_link,
|
||||
'show_avatar' => (bool) $p->show_avatar,
|
||||
'show_bio' => (bool) $p->show_bio,
|
||||
'profile_layout' => $p->profile_layout,
|
||||
'profile_source' => $p->profile_source,
|
||||
'metadata' => $p->metadata
|
||||
];
|
||||
})->first();
|
||||
}
|
||||
|
||||
public function getAccountSettings(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'id' => 'required|integer'
|
||||
]);
|
||||
|
||||
$account = AccountService::get($request->input('id'));
|
||||
|
||||
abort_if(!$account, 404);
|
||||
|
||||
$p = Portfolio::whereProfileId($request->input('id'))->whereActive(1)->firstOrFail();
|
||||
|
||||
if(!$p) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $p->url(),
|
||||
'show_captions' => (bool) $p->show_captions,
|
||||
'show_license' => (bool) $p->show_license,
|
||||
'show_location' => (bool) $p->show_location,
|
||||
'show_timestamp' => (bool) $p->show_timestamp,
|
||||
'show_link' => (bool) $p->show_link,
|
||||
'show_avatar' => (bool) $p->show_avatar,
|
||||
'show_bio' => (bool) $p->show_bio,
|
||||
'profile_layout' => $p->profile_layout,
|
||||
'profile_source' => $p->profile_source
|
||||
];
|
||||
}
|
||||
|
||||
public function storeSettings(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'profile_layout' => 'sometimes|in:grid,masonry,album'
|
||||
]);
|
||||
|
||||
$res = Portfolio::whereUserId($request->user()->id)
|
||||
->update($request->only([
|
||||
'active',
|
||||
'show_captions',
|
||||
'show_license',
|
||||
'show_location',
|
||||
'show_timestamp',
|
||||
'show_link',
|
||||
'show_avatar',
|
||||
'show_bio',
|
||||
'profile_layout',
|
||||
'profile_source'
|
||||
]));
|
||||
|
||||
Cache::forget('portfolio:recent-feed:' . $request->user()->profile_id);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
public function storeCurated(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'ids' => 'required|array|max:24'
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$ids = $request->input('ids');
|
||||
|
||||
Status::whereProfileId($pid)
|
||||
->whereScope('public')
|
||||
->whereIn('type', ['photo', 'photo:album'])
|
||||
->findOrFail($ids);
|
||||
|
||||
$p = Portfolio::whereProfileId($pid)->firstOrFail();
|
||||
$p->metadata = ['posts' => $ids];
|
||||
$p->save();
|
||||
|
||||
Cache::forget('portfolio:recent-feed:' . $pid);
|
||||
|
||||
return $request->ids;
|
||||
}
|
||||
}
|
39
app/Models/Portfolio.php
Normal file
39
app/Models/Portfolio.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Services\AccountService;
|
||||
|
||||
class Portfolio extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $fillable = [
|
||||
'active',
|
||||
'show_captions',
|
||||
'show_license',
|
||||
'show_location',
|
||||
'show_timestamp',
|
||||
'show_link',
|
||||
'show_avatar',
|
||||
'show_bio',
|
||||
'profile_layout',
|
||||
'profile_source'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'json'
|
||||
];
|
||||
|
||||
public function url()
|
||||
{
|
||||
$account = AccountService::get($this->profile_id);
|
||||
if(!$account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'https://' . config('portfolio.domain') . config('portfolio.path') . '/' . $account['username'];
|
||||
}
|
||||
}
|
31
config/portfolio.php
Normal file
31
config/portfolio.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Portfolio Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the domain used for the portfolio feature. Only change
|
||||
| the default value if you have a subdomain configured. You must use
|
||||
| a subdomain on the same app domain.
|
||||
|
|
||||
*/
|
||||
'domain' => env('PORTFOLIO_DOMAIN', config('pixelfed.domain.app')),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Portfolio Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the path used for the portfolio feature. Only change
|
||||
| the default value if you have a subdomain configured. If you want
|
||||
| to use the root path of the subdomain, leave this value empty.
|
||||
|
|
||||
| WARNING: SETTING THIS VALUE WITHOUT A SUBDOMAIN COULD BREAK YOUR
|
||||
| INSTANCE, SO ONLY CHANGE THIS IF YOU KNOW WHAT YOU'RE DOING.
|
||||
|
|
||||
*/
|
||||
'path' => env('PORTFOLIO_PATH', '/i/portfolio'),
|
||||
];
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePortfoliosTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('portfolios', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('user_id')->nullable()->unique()->index();
|
||||
$table->bigInteger('profile_id')->unsigned()->unique()->index();
|
||||
$table->boolean('active')->nullable()->index();
|
||||
$table->boolean('show_captions')->default(true)->nullable();
|
||||
$table->boolean('show_license')->default(true)->nullable();
|
||||
$table->boolean('show_location')->default(true)->nullable();
|
||||
$table->boolean('show_timestamp')->default(true)->nullable();
|
||||
$table->boolean('show_link')->default(true)->nullable();
|
||||
$table->string('profile_source')->default('recent')->nullable();
|
||||
$table->boolean('show_avatar')->default(true)->nullable();
|
||||
$table->boolean('show_bio')->default(true)->nullable();
|
||||
$table->string('profile_layout')->default('grid')->nullable();
|
||||
$table->string('profile_container')->default('fixed')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('portfolios');
|
||||
}
|
||||
}
|
1
public/css/portfolio.css
vendored
Normal file
1
public/css/portfolio.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
@font-face{font-display:swap;font-family:Inter;font-style:normal;font-weight:100;src:url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.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-display:swap;font-family:Inter;font-style:normal;font-weight:100;src:url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.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-display:swap;font-family:Inter;font-style:normal;font-weight:400;src:url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.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-display:swap;font-family:Inter;font-style:normal;font-weight:400;src:url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.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-display:swap;font-family:Inter;font-style:normal;font-weight:700;src:url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.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-display:swap;font-family:Inter;font-style:normal;font-weight:700;src:url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.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:#000;color:#d4d4d8;font-family:Inter,sans-serif;font-weight:400!important}.text-primary{color:#3b82f6!important}.font-weight-light,.lead{font-weight:400!important}a{color:#3b82f6;text-decoration:none}.text-gradient-primary{-webkit-text-fill-color:transparent;background:linear-gradient(90deg,#6366f1,#8b5cf6,#d946ef);-webkit-background-clip:text}.logo-mark{background:#212529;border:6px solid #212529;border-radius:1rem;color:#fff!important;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif!important;font-size:2.5rem;font-weight:700!important;letter-spacing:-1.5px;line-height:1.2;text-decoration:none!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media(min-width:768px){.logo-mark{font-size:4.5rem}}.logo-mark-sm{background:#212529;border-radius:10px;border-width:3px;font-size:16px!important;letter-spacing:-1px}.display-4.font-weight-bold{letter-spacing:-.3px;text-transform:uppercase}@media(min-width:768px){.display-4.font-weight-bold{letter-spacing:-3px}}.display-4.font-weight-bold a{color:#d1d5db;text-decoration:underline}.display-4{font-size:1.5rem}@media(min-width:768px){.display-4{font-size:3.5rem}}.btn-primary{background-color:#3b82f6}.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:0;column-gap:0;orphans:1;widows:1}.portfolio-settings .nav-pills .nav-item.disabled span{color:#3f3f46;pointer-events:none}.portfolio-settings .nav-pills .nav-link{color:#9ca3af;font-size:15px;font-weight:400}.portfolio-settings .nav-pills .nav-link.active{background-image:linear-gradient(90deg,#4f46e5 0,#2f80ed 51%,#4f46e5);background-size:200% auto;color:#fff;font-weight:100;transition:.5s}.portfolio-settings .nav-pills .nav-link.active:hover{background-position:100%}.portfolio-settings .card-header{background-color:#000;border:1px solid var(--dark);color:var(--muted);font-size:14px;font-weight:400;text-transform:uppercase}.portfolio-settings .card .list-group-item{background:transparent}.portfolio-settings .custom-select{background:#000 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat;border-color:var(--dark);border-radius:10px;color:#fff;font-weight:700;padding-left:20px}.portfolio-settings .selected-badge{align-items:center;background-color:#0284c7;border:2px solid #fff;border-radius:26px;color:#fff;display:flex;font-size:14px;font-weight:700;height:26px;justify-content:center;width:26px}.slide-fade-enter-active{transition:all .3s ease}.slide-fade-leave-active{transition:all .3s cubic-bezier(1,1)}.slide-fade-enter,.slide-fade-leave-to{opacity:0;transform:translateX(10px)}
|
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2
Normal file
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2
Normal file
Binary file not shown.
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2
Normal file
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2
Normal file
Binary file not shown.
2
public/js/app.js
vendored
2
public/js/app.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/collections.js
vendored
2
public/js/collections.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/components.js
vendored
2
public/js/components.js
vendored
|
@ -1 +1 @@
|
|||
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[8],{9324:(e,t,o)=>{o.r(t);var a=o(70538),l=o(25518),n=o(30306),r=o.n(n),s=o(7398),d=o.n(s),c=o(92987),i=o(37409),u=o.n(i),f=o(74870),h=o.n(f),m=(o(82711),o(46737),o(19755));function p(e){return p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p(e)}window.Vue=a.default,a.default.use(h()),a.default.use(u()),a.default.use(l.default),a.default.use(r()),a.default.use(d()),a.default.use(c.default,{name:"Timeago",locale:"en"}),pixelfed.readmore=function(){m(".read-more").each((function(e,t){var o=m(this),a=o.attr("data-readmore");"undefined"!==p(a)&&!1!==a||o.readmore({collapsedHeight:45,heightMargin:48,moreLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show more</a>',lessLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show less</a>'})}))};try{document.createEvent("TouchEvent"),m("body").addClass("touch")}catch(e){}window.filesize=o(42317),m('[data-toggle="tooltip"]').tooltip();console.log("%cStop!","color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;"),console.log('%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or "hack" someone\'s account, it is a scam and will give them access to your Pixelfed account.',"font-size: 18px;")}},e=>{e.O(0,[898],(()=>{return t=9324,e(e.s=t);var t}));e.O()}]);
|
||||
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[8],{9324:(e,t,o)=>{o.r(t);var a=o(70538),l=o(25518),n=o(30306),r=o.n(n),s=o(7398),d=o.n(s),c=o(92987),i=o(37409),u=o.n(i),f=o(74870),h=o.n(f),m=(o(93142),o(46737),o(19755));function p(e){return p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p(e)}window.Vue=a.default,a.default.use(h()),a.default.use(u()),a.default.use(l.default),a.default.use(r()),a.default.use(d()),a.default.use(c.default,{name:"Timeago",locale:"en"}),pixelfed.readmore=function(){m(".read-more").each((function(e,t){var o=m(this),a=o.attr("data-readmore");"undefined"!==p(a)&&!1!==a||o.readmore({collapsedHeight:45,heightMargin:48,moreLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show more</a>',lessLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show less</a>'})}))};try{document.createEvent("TouchEvent"),m("body").addClass("touch")}catch(e){}window.filesize=o(42317),m('[data-toggle="tooltip"]').tooltip();console.log("%cStop!","color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;"),console.log('%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or "hack" someone\'s account, it is a scam and will give them access to your Pixelfed account.',"font-size: 18px;")}},e=>{e.O(0,[898],(()=>{return t=9324,e(e.s=t);var t}));e.O()}]);
|
2
public/js/compose-classic.js
vendored
2
public/js/compose-classic.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/compose-ivl9d2teh.js
vendored
1
public/js/compose-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/compose-llsjbikoc.js
vendored
Normal file
1
public/js/compose-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/compose.js
vendored
2
public/js/compose.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/daci-ivl9d2teh.js
vendored
1
public/js/daci-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/daci-llsjbikoc.js
vendored
Normal file
1
public/js/daci-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/developers.js
vendored
2
public/js/developers.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dffc-ivl9d2teh.js
vendored
1
public/js/dffc-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dffc-llsjbikoc.js
vendored
Normal file
1
public/js/dffc-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/direct.js
vendored
2
public/js/direct.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/discover-ivl9d2teh.js
vendored
1
public/js/discover-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/discover-llsjbikoc.js
vendored
Normal file
1
public/js/discover-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dms-ivl9d2teh.js
vendored
1
public/js/dms-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dms-llsjbikoc.js
vendored
Normal file
1
public/js/dms-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dmsg-ivl9d2teh.js
vendored
1
public/js/dmsg-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dmsg-llsjbikoc.js
vendored
Normal file
1
public/js/dmsg-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dmyh-ivl9d2teh.js
vendored
1
public/js/dmyh-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dmyh-llsjbikoc.js
vendored
Normal file
1
public/js/dmyh-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dmym-ivl9d2teh.js
vendored
1
public/js/dmym-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dmym-llsjbikoc.js
vendored
Normal file
1
public/js/dmym-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dsfc-ivl9d2teh.js
vendored
1
public/js/dsfc-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dsfc-llsjbikoc.js
vendored
Normal file
1
public/js/dsfc-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dssc-ivl9d2teh.js
vendored
1
public/js/dssc-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/dssc-llsjbikoc.js
vendored
Normal file
1
public/js/dssc-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/hashtag.js
vendored
2
public/js/hashtag.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/home-ivl9d2teh.js
vendored
1
public/js/home-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/home-llsjbikoc.js
vendored
Normal file
1
public/js/home-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/installer.js
vendored
2
public/js/installer.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/live-player.js
vendored
2
public/js/live-player.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/manifest.js
vendored
2
public/js/manifest.js
vendored
|
@ -1 +1 @@
|
|||
(()=>{"use strict";var e,t,r,s={},i={};function o(e){var t=i[e];if(void 0!==t)return t.exports;var r=i[e]={id:e,loaded:!1,exports:{}};return s[e].call(r.exports,r,r.exports,o),r.loaded=!0,r.exports}o.m=s,e=[],o.O=(t,r,s,i)=>{if(!r){var n=1/0;for(c=0;c<e.length;c++){for(var[r,s,i]=e[c],d=!0,l=0;l<r.length;l++)(!1&i||n>=i)&&Object.keys(o.O).every((e=>o.O[e](r[l])))?r.splice(l--,1):(d=!1,i<n&&(n=i));if(d){e.splice(c--,1);var a=s();void 0!==a&&(t=a)}}return t}i=i||0;for(var c=e.length;c>0&&e[c-1][2]>i;c--)e[c]=e[c-1];e[c]=[r,s,i]},o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((t,r)=>(o.f[r](e,t),t)),[])),o.u=e=>113===e?"js/home-ivl9d2teh.js":844===e?"js/compose-ivl9d2teh.js":635===e?"js/post-ivl9d2teh.js":761===e?"js/profile-ivl9d2teh.js":51===e?"js/dmym-ivl9d2teh.js":40===e?"js/dmyh-ivl9d2teh.js":200===e?"js/daci-ivl9d2teh.js":576===e?"js/dffc-ivl9d2teh.js":788===e?"js/dsfc-ivl9d2teh.js":382===e?"js/dssc-ivl9d2teh.js":854===e?"js/discover-ivl9d2teh.js":13===e?"js/notifications-ivl9d2teh.js":641===e?"js/dms-ivl9d2teh.js":732===e?"js/dmsg-ivl9d2teh.js":void 0,o.miniCssF=e=>({138:"css/spa",170:"css/app",242:"css/appdark",703:"css/admin",994:"css/landing"}[e]+".css"),o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),t={},r="pixelfed:",o.l=(e,s,i,n)=>{if(t[e])t[e].push(s);else{var d,l;if(void 0!==i)for(var a=document.getElementsByTagName("script"),c=0;c<a.length;c++){var u=a[c];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==r+i){d=u;break}}d||(l=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,o.nc&&d.setAttribute("nonce",o.nc),d.setAttribute("data-webpack",r+i),d.src=e),t[e]=[s];var f=(r,s)=>{d.onerror=d.onload=null,clearTimeout(p);var i=t[e];if(delete t[e],d.parentNode&&d.parentNode.removeChild(d),i&&i.forEach((e=>e(s))),r)return r(s)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=f.bind(null,d.onerror),d.onload=f.bind(null,d.onload),l&&document.head.appendChild(d)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),o.p="/",(()=>{var e={929:0,242:0,170:0,138:0,703:0,994:0};o.f.j=(t,r)=>{var s=o.o(e,t)?e[t]:void 0;if(0!==s)if(s)r.push(s[2]);else if(/^(138|170|242|703|929|994)$/.test(t))e[t]=0;else{var i=new Promise(((r,i)=>s=e[t]=[r,i]));r.push(s[2]=i);var n=o.p+o.u(t),d=new Error;o.l(n,(r=>{if(o.o(e,t)&&(0!==(s=e[t])&&(e[t]=void 0),s)){var i=r&&("load"===r.type?"missing":r.type),n=r&&r.target&&r.target.src;d.message="Loading chunk "+t+" failed.\n("+i+": "+n+")",d.name="ChunkLoadError",d.type=i,d.request=n,s[1](d)}}),"chunk-"+t,t)}},o.O.j=t=>0===e[t];var t=(t,r)=>{var s,i,[n,d,l]=r,a=0;if(n.some((t=>0!==e[t]))){for(s in d)o.o(d,s)&&(o.m[s]=d[s]);if(l)var c=l(o)}for(t&&t(r);a<n.length;a++)i=n[a],o.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return o.O(c)},r=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})(),o.nc=void 0})();
|
||||
(()=>{"use strict";var e,r,s,o={},t={};function i(e){var r=t[e];if(void 0!==r)return r.exports;var s=t[e]={id:e,loaded:!1,exports:{}};return o[e].call(s.exports,s,s.exports,i),s.loaded=!0,s.exports}i.m=o,e=[],i.O=(r,s,o,t)=>{if(!s){var l=1/0;for(d=0;d<e.length;d++){for(var[s,o,t]=e[d],n=!0,a=0;a<s.length;a++)(!1&t||l>=t)&&Object.keys(i.O).every((e=>i.O[e](s[a])))?s.splice(a--,1):(n=!1,t<l&&(l=t));if(n){e.splice(d--,1);var c=o();void 0!==c&&(r=c)}}return r}t=t||0;for(var d=e.length;d>0&&e[d-1][2]>t;d--)e[d]=e[d-1];e[d]=[s,o,t]},i.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return i.d(r,{a:r}),r},i.d=(e,r)=>{for(var s in r)i.o(r,s)&&!i.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:r[s]})},i.f={},i.e=e=>Promise.all(Object.keys(i.f).reduce(((r,s)=>(i.f[s](e,r),r)),[])),i.u=e=>17===e?"js/home-llsjbikoc.js":434===e?"js/compose-llsjbikoc.js":121===e?"js/post-llsjbikoc.js":825===e?"js/profile-llsjbikoc.js":472===e?"js/dmym-llsjbikoc.js":464===e?"js/dmyh-llsjbikoc.js":206===e?"js/daci-llsjbikoc.js":831===e?"js/dffc-llsjbikoc.js":661===e?"js/dsfc-llsjbikoc.js":310===e?"js/dssc-llsjbikoc.js":731===e?"js/discover-llsjbikoc.js":921===e?"js/notifications-llsjbikoc.js":379===e?"js/dms-llsjbikoc.js":875===e?"js/dmsg-llsjbikoc.js":void 0,i.miniCssF=e=>({138:"css/spa",170:"css/app",242:"css/appdark",703:"css/admin",737:"css/portfolio",994:"css/landing"}[e]+".css"),i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},s="pixelfed:",i.l=(e,o,t,l)=>{if(r[e])r[e].push(o);else{var n,a;if(void 0!==t)for(var c=document.getElementsByTagName("script"),d=0;d<c.length;d++){var u=c[d];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==s+t){n=u;break}}n||(a=!0,(n=document.createElement("script")).charset="utf-8",n.timeout=120,i.nc&&n.setAttribute("nonce",i.nc),n.setAttribute("data-webpack",s+t),n.src=e),r[e]=[o];var j=(s,o)=>{n.onerror=n.onload=null,clearTimeout(f);var t=r[e];if(delete r[e],n.parentNode&&n.parentNode.removeChild(n),t&&t.forEach((e=>e(o))),s)return s(o)},f=setTimeout(j.bind(null,void 0,{type:"timeout",target:n}),12e4);n.onerror=j.bind(null,n.onerror),n.onload=j.bind(null,n.onload),a&&document.head.appendChild(n)}},i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),i.p="/",(()=>{var e={929:0,242:0,170:0,737:0,703:0,994:0,138:0};i.f.j=(r,s)=>{var o=i.o(e,r)?e[r]:void 0;if(0!==o)if(o)s.push(o[2]);else if(/^(138|170|242|703|737|929|994)$/.test(r))e[r]=0;else{var t=new Promise(((s,t)=>o=e[r]=[s,t]));s.push(o[2]=t);var l=i.p+i.u(r),n=new Error;i.l(l,(s=>{if(i.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var t=s&&("load"===s.type?"missing":s.type),l=s&&s.target&&s.target.src;n.message="Loading chunk "+r+" failed.\n("+t+": "+l+")",n.name="ChunkLoadError",n.type=t,n.request=l,o[1](n)}}),"chunk-"+r,r)}},i.O.j=r=>0===e[r];var r=(r,s)=>{var o,t,[l,n,a]=s,c=0;if(l.some((r=>0!==e[r]))){for(o in n)i.o(n,o)&&(i.m[o]=n[o]);if(a)var d=a(i)}for(r&&r(s);c<l.length;c++)t=l[c],i.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return i.O(d)},s=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];s.forEach(r.bind(null,0)),s.push=r.bind(null,s.push.bind(s))})(),i.nc=void 0})();
|
1
public/js/notifications-ivl9d2teh.js
vendored
1
public/js/notifications-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/notifications-llsjbikoc.js
vendored
Normal file
1
public/js/notifications-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/portfolio.js
vendored
Normal file
1
public/js/portfolio.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/post-ivl9d2teh.js
vendored
1
public/js/post-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/post-llsjbikoc.js
vendored
Normal file
1
public/js/post-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/profile-ivl9d2teh.js
vendored
1
public/js/profile-ivl9d2teh.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/profile-llsjbikoc.js
vendored
Normal file
1
public/js/profile-llsjbikoc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/profile.js
vendored
2
public/js/profile.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/rempos.js
vendored
2
public/js/rempos.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/rempro.js
vendored
2
public/js/rempro.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/search.js
vendored
2
public/js/search.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/spa.js
vendored
2
public/js/spa.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/status.js
vendored
2
public/js/status.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/stories.js
vendored
2
public/js/stories.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/story-compose.js
vendored
2
public/js/story-compose.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/timeline.js
vendored
2
public/js/timeline.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/vendor.js
vendored
2
public/js/vendor.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,47 +1,49 @@
|
|||
{
|
||||
"/js/app.js": "/js/app.js?id=855c8b0847137dd56c72229ad305c032",
|
||||
"/js/app.js": "/js/app.js?id=92f95bbb3a3996d97986f0bae1d19854",
|
||||
"/js/activity.js": "/js/activity.js?id=d14e608dd1ecac4d900cd639f2199b62",
|
||||
"/js/components.js": "/js/components.js?id=35beb0748dd5518371686a5f00348fbf",
|
||||
"/js/components.js": "/js/components.js?id=a1d4c69ad0670101d7c449f31dfc0234",
|
||||
"/js/discover.js": "/js/discover.js?id=0c98508635d6adae9bf8f76c9f5c918c",
|
||||
"/js/profile.js": "/js/profile.js?id=a7773ad51508de1b07d3d27a0b750b2a",
|
||||
"/js/status.js": "/js/status.js?id=9fd2af60a035dd86ff3cb31af111c3b7",
|
||||
"/js/timeline.js": "/js/timeline.js?id=a2a547a3c177ae187ba420c21893e8e8",
|
||||
"/js/compose.js": "/js/compose.js?id=61b03caa8ae7827b689847c19fc99212",
|
||||
"/js/compose-classic.js": "/js/compose-classic.js?id=f11c60795e27213e446054259fdc8b50",
|
||||
"/js/search.js": "/js/search.js?id=4bb81cba317cf1ad35f2c98dce78fd9d",
|
||||
"/js/developers.js": "/js/developers.js?id=dd22facb8cf2746992404468a9373ac5",
|
||||
"/js/hashtag.js": "/js/hashtag.js?id=5fe9d15d07a227f91eabd874cbb9fea2",
|
||||
"/js/profile.js": "/js/profile.js?id=5b3fdd28701417e3b07c084ed4010dd1",
|
||||
"/js/status.js": "/js/status.js?id=1256023f2252ea00bd413e8b88af7bba",
|
||||
"/js/timeline.js": "/js/timeline.js?id=70f25266b6e023fce9241957604f2bec",
|
||||
"/js/compose.js": "/js/compose.js?id=9e50779c7c795fb9e91442cc04fc0986",
|
||||
"/js/compose-classic.js": "/js/compose-classic.js?id=bddb9f2188a782aa819d2e993f66132b",
|
||||
"/js/search.js": "/js/search.js?id=5e0597874d446987159f185e3ac4a94f",
|
||||
"/js/developers.js": "/js/developers.js?id=de7f83146a05d8cd9ef3d68de9e2b4f2",
|
||||
"/js/hashtag.js": "/js/hashtag.js?id=45ba17ef807c13297eb6b05c00f5d04c",
|
||||
"/js/collectioncompose.js": "/js/collectioncompose.js?id=96e040f887859e77549afcadf3e0fdc9",
|
||||
"/js/collections.js": "/js/collections.js?id=b7315297770a9c1b02cb64176158d2df",
|
||||
"/js/collections.js": "/js/collections.js?id=c492e564ee9c7bb6fe1b4df71aa5912c",
|
||||
"/js/profile-directory.js": "/js/profile-directory.js?id=04ec970031e6bf15de5ade019147d53e",
|
||||
"/js/story-compose.js": "/js/story-compose.js?id=afe8f35cf52d92ac48ee68a9916d218d",
|
||||
"/js/direct.js": "/js/direct.js?id=29127c125979e275afa50b47d692c892",
|
||||
"/js/story-compose.js": "/js/story-compose.js?id=1ec3e09e9647176b6cb6db768a9d490b",
|
||||
"/js/direct.js": "/js/direct.js?id=659767ee04e19dc4017f2cb358d57a69",
|
||||
"/js/admin.js": "/js/admin.js?id=fd88b96423314b41cc763a0714554a04",
|
||||
"/js/rempro.js": "/js/rempro.js?id=8ad8738264b7e0733f89ca605d6f347c",
|
||||
"/js/rempos.js": "/js/rempos.js?id=47f6c3b3dc7954179a9e2024614449d4",
|
||||
"/js/live-player.js": "/js/live-player.js?id=be9bb8d1d615e03356a7ea2a755dabd9",
|
||||
"/js/spa.js": "/js/spa.js?id=c7903ea5481557c969ffc97999d64a12",
|
||||
"/js/stories.js": "/js/stories.js?id=814a25875cac8987d85c801dcb453114",
|
||||
"/js/installer.js": "/js/installer.js?id=d7b03f6c0bb707bec8ff9f81d328ac4a",
|
||||
"/js/manifest.js": "/js/manifest.js?id=9ba42a85f6a0413c7493b02f749e3cc7",
|
||||
"/js/home-ivl9d2teh.js": "/js/home-ivl9d2teh.js?id=a4f4874c61183b173479a9a67fa2e66f",
|
||||
"/js/compose-ivl9d2teh.js": "/js/compose-ivl9d2teh.js?id=76a6e4e6eebeff5f9134db38263c6cd0",
|
||||
"/js/post-ivl9d2teh.js": "/js/post-ivl9d2teh.js?id=de1b91878b05352f272dc2ab479d87b5",
|
||||
"/js/profile-ivl9d2teh.js": "/js/profile-ivl9d2teh.js?id=a0b0d663e43431010fdaf446866106cf",
|
||||
"/js/dmym-ivl9d2teh.js": "/js/dmym-ivl9d2teh.js?id=83dd473d9e9d005df20dc7973e4d3cf1",
|
||||
"/js/dmyh-ivl9d2teh.js": "/js/dmyh-ivl9d2teh.js?id=e6bcbb23a10d6234ddfec0fcb21a6445",
|
||||
"/js/daci-ivl9d2teh.js": "/js/daci-ivl9d2teh.js?id=5417060d4abef7ee44026081535509c8",
|
||||
"/js/dffc-ivl9d2teh.js": "/js/dffc-ivl9d2teh.js?id=625d349892ac8e8ead5b65b7d20d4bca",
|
||||
"/js/dsfc-ivl9d2teh.js": "/js/dsfc-ivl9d2teh.js?id=e948c7fe25618009af148cc765d3f829",
|
||||
"/js/dssc-ivl9d2teh.js": "/js/dssc-ivl9d2teh.js?id=05be7e6a0bab04e8f23de3246dba5d50",
|
||||
"/js/discover-ivl9d2teh.js": "/js/discover-ivl9d2teh.js?id=4e76e2d0b0bf815e1b23cb721d0ce8c4",
|
||||
"/js/notifications-ivl9d2teh.js": "/js/notifications-ivl9d2teh.js?id=87e6ae02b7626c021e853d706920c647",
|
||||
"/js/dms-ivl9d2teh.js": "/js/dms-ivl9d2teh.js?id=6068ce1647a66a8e9b50f2fbf4b7578e",
|
||||
"/js/dmsg-ivl9d2teh.js": "/js/dmsg-ivl9d2teh.js?id=1a9976846519afea24cb9f316ab77e0e",
|
||||
"/js/rempro.js": "/js/rempro.js?id=1eda2115dc663a8a1617329aba12bc66",
|
||||
"/js/rempos.js": "/js/rempos.js?id=a2355bd9790509723a9b6efce4d33073",
|
||||
"/js/live-player.js": "/js/live-player.js?id=a13ee6667e29c6159c5ba51be211dbaf",
|
||||
"/js/spa.js": "/js/spa.js?id=8a1238ec25f4067b3436041eaf386fbb",
|
||||
"/js/stories.js": "/js/stories.js?id=a4237af19ac2c4460ad34ecbb7bee7dd",
|
||||
"/js/portfolio.js": "/js/portfolio.js?id=204f2ece254271935022cfb72c0042a1",
|
||||
"/js/installer.js": "/js/installer.js?id=6df959ddb067a587dcc40a11909b9b1f",
|
||||
"/js/manifest.js": "/js/manifest.js?id=cb3be33aeb160e02d6840c16f1fad134",
|
||||
"/js/home-llsjbikoc.js": "/js/home-llsjbikoc.js?id=e10f6fadd6228067fccad09665999083",
|
||||
"/js/compose-llsjbikoc.js": "/js/compose-llsjbikoc.js?id=68160b8601d8bf1267bbb9f0b5f61f6d",
|
||||
"/js/post-llsjbikoc.js": "/js/post-llsjbikoc.js?id=c5a542c009aaea963c7487a07608db1e",
|
||||
"/js/profile-llsjbikoc.js": "/js/profile-llsjbikoc.js?id=590c28a9a3c78c815f2d05202d86f00b",
|
||||
"/js/dmym-llsjbikoc.js": "/js/dmym-llsjbikoc.js?id=2e66f1374e89f04f98c48ddbeee3ecba",
|
||||
"/js/dmyh-llsjbikoc.js": "/js/dmyh-llsjbikoc.js?id=4951618f50447b7a6b83d8ca5a644278",
|
||||
"/js/daci-llsjbikoc.js": "/js/daci-llsjbikoc.js?id=7eb70eb0ab1f82c9e9bcafc7bfe9bdfb",
|
||||
"/js/dffc-llsjbikoc.js": "/js/dffc-llsjbikoc.js?id=9729f2c2f877c98ab561f01f34aa3d45",
|
||||
"/js/dsfc-llsjbikoc.js": "/js/dsfc-llsjbikoc.js?id=a694c483689f1176fb7c43c6be735487",
|
||||
"/js/dssc-llsjbikoc.js": "/js/dssc-llsjbikoc.js?id=7db93ebd0c628d659904dfde6e11631e",
|
||||
"/js/discover-llsjbikoc.js": "/js/discover-llsjbikoc.js?id=93e22e635c1d67a41bfeba1cf1883ad4",
|
||||
"/js/notifications-llsjbikoc.js": "/js/notifications-llsjbikoc.js?id=a96d4d13dcaf8c5a48d598756634718d",
|
||||
"/js/dms-llsjbikoc.js": "/js/dms-llsjbikoc.js?id=d0cca63dce739a5e3129b7df3ffc3696",
|
||||
"/js/dmsg-llsjbikoc.js": "/js/dmsg-llsjbikoc.js?id=a46273b401b145b2727616c4565a1e26",
|
||||
"/css/appdark.css": "/css/appdark.css?id=aa186d0136f89d136461f0f5d84de682",
|
||||
"/css/app.css": "/css/app.css?id=140a427d89c3aae4f78e87cf6ec7eef3",
|
||||
"/css/spa.css": "/css/spa.css?id=4c78f163c6ad4e0f25ced75c7dd624b6",
|
||||
"/css/portfolio.css": "/css/portfolio.css?id=b2e5ac36595185abfbeb0f9b114c2163",
|
||||
"/css/admin.css": "/css/admin.css?id=3730bb9975e2a6ddc931f66dce1fcf49",
|
||||
"/css/landing.css": "/css/landing.css?id=b488c3f0db85a50607d8ae12ac394a0f",
|
||||
"/js/vendor.js": "/js/vendor.js?id=898bcd5e9be546c8b5699fe37805770e"
|
||||
"/css/spa.css": "/css/spa.css?id=4c78f163c6ad4e0f25ced75c7dd624b6",
|
||||
"/js/vendor.js": "/js/vendor.js?id=54cd37dcfb87097e5a701f4e4c3d9bc3"
|
||||
}
|
||||
|
|
122
resources/assets/js/components/PortfolioPost.vue
Normal file
122
resources/assets/js/components/PortfolioPost.vue
Normal file
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="loading" class="container">
|
||||
<div class="d-flex justify-content-center align-items-center" style="height: 100vh;">
|
||||
<b-spinner />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="container mb-5">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 mb-4">
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<img :src="post.media_attachments[0].url" class="img-fluid mb-4" style="max-height: 80vh;object-fit: contain;">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 mb-4">
|
||||
<p v-if="settings.show_captions && post.content_text">{{ post.content_text }}</p>
|
||||
<div class="d-md-flex justify-content-between align-items-center">
|
||||
<p class="small text-lighter">by <a :href="profileUrl()" class="text-lighter font-weight-bold">@{{profile.username}}</a></p>
|
||||
<p v-if="settings.show_license && post.media_attachments[0].license" class="small text-muted">Licensed under {{ post.media_attachments[0].license.title }}</p>
|
||||
<p v-if="settings.show_location && post.place" class="small text-muted">{{ post.place.name }}, {{ post.place.country }}</p>
|
||||
<p v-if="settings.show_timestamp" class="small text-muted">
|
||||
<a v-if="settings.show_link" :href="post.url" class="text-lighter font-weight-bold" style="z-index: 2">
|
||||
{{ formatDate(post.created_at) }}
|
||||
</a>
|
||||
<span v-else class="user-select-none">
|
||||
{{ formatDate(post.created_at) }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex fixed-bottom p-3 justify-content-between align-items-center">
|
||||
<a v-if="user" class="logo-mark logo-mark-sm mb-0 p-1" href="/">
|
||||
<span class="text-gradient-primary">portfolio</span>
|
||||
</a>
|
||||
<span v-else class="logo-mark logo-mark-sm mb-0 p-1">
|
||||
<span class="text-gradient-primary">portfolio</span>
|
||||
</span>
|
||||
<p v-if="user && user.id === profile.id" class="text-center mb-0">
|
||||
<a :href="settingsUrl" class="text-muted"><i class="far fa-cog fa-lg"></i></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
props: [ 'initialData' ],
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
isAuthed: undefined,
|
||||
user: undefined,
|
||||
settings: undefined,
|
||||
post: undefined,
|
||||
profile: undefined,
|
||||
settingsUrl: window._portfolio.path + '/settings'
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const initialData = JSON.parse(this.initialData);
|
||||
this.post = initialData.post;
|
||||
this.profile = initialData.profile;
|
||||
this.isAuthed = initialData.authed;
|
||||
this.fetchUser();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchUser() {
|
||||
if(this.isAuthed) {
|
||||
await axios.get('/api/v1/accounts/verify_credentials')
|
||||
.then(res => {
|
||||
this.user = res.data;
|
||||
})
|
||||
.catch(err => {
|
||||
});
|
||||
}
|
||||
await axios.get('/api/portfolio/account/settings.json', {
|
||||
params: {
|
||||
id: this.profile.id
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.settings = res.data;
|
||||
})
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 500);
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
profileUrl() {
|
||||
return `https://${window._portfolio.domain}${window._portfolio.path}/${this.profile.username}`;
|
||||
},
|
||||
|
||||
postUrl(res) {
|
||||
return `/${this.profile.username}/${res.id}`;
|
||||
},
|
||||
|
||||
formatDate(ts) {
|
||||
const dts = new Date(ts);
|
||||
return dts.toLocaleDateString(undefined, { weekday: 'short', year: 'numeric', month: 'long', day: 'numeric' });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
223
resources/assets/js/components/PortfolioProfile.vue
Normal file
223
resources/assets/js/components/PortfolioProfile.vue
Normal file
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<div class="w-100 h-100">
|
||||
<div v-if="loading" class="container">
|
||||
<div class="d-flex justify-content-center align-items-center" style="height: 100vh;">
|
||||
<b-spinner />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="container">
|
||||
<div class="row py-5">
|
||||
<div class="col-12">
|
||||
<div class="d-flex align-items-center flex-column">
|
||||
<img :src="profile.avatar" width="60" height="60" class="rounded-circle shadow" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
|
||||
|
||||
<div class="py-3 text-center" style="max-width: 60%">
|
||||
<h1 class="font-weight-bold">{{ profile.username }}</h1>
|
||||
<p class="font-weight-light mb-0">{{ profile.note_text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mb-5 pb-5">
|
||||
<div :class="[ settings.profile_layout === 'masonry' ? 'card-columns' : 'row']" id="portContainer">
|
||||
<template v-if="settings.profile_layout ==='grid'">
|
||||
<div v-for="(res, index) in feed" class="col-12 col-md-4 mb-1 p-1">
|
||||
<div class="square">
|
||||
<a :href="postUrl(res)">
|
||||
<img :src="res.media_attachments[0].url" width="100%" height="300" style="overflow: hidden;object-fit: cover;" class="square-content pr-1">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else-if="settings.profile_layout ==='album'" class="col-12 mb-1 p-1">
|
||||
<div class="d-flex justify-content-center">
|
||||
<p class="text-muted font-weight-bold">{{ albumIndex + 1 }} <span class="font-weight-light">/</span> {{ feed.length }}</p>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span v-if="albumIndex === 0">
|
||||
<i class="fa fa-arrow-circle-left fa-3x text-dark" />
|
||||
</span>
|
||||
<a v-else @click.prevent="albumPrev()" href="#">
|
||||
<i class="fa fa-arrow-circle-left fa-3x text-muted"/>
|
||||
</a>
|
||||
<transition name="slide-fade">
|
||||
<a :href="postUrl(feed[albumIndex])" class="mx-4" :key="albumIndex">
|
||||
<img
|
||||
:src="feed[albumIndex].media_attachments[0].url"
|
||||
width="100%"
|
||||
class="user-select-none"
|
||||
style="height: 60vh; overflow: hidden;object-fit: contain;"
|
||||
:draggable="false"
|
||||
>
|
||||
</a>
|
||||
</transition>
|
||||
<span v-if="albumIndex === feed.length - 1">
|
||||
<i class="fa fa-arrow-circle-right fa-3x text-dark" />
|
||||
</span>
|
||||
<a v-else @click.prevent="albumNext()" href="#">
|
||||
<i class="fa fa-arrow-circle-right fa-3x text-muted"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="settings.profile_layout ==='masonry'" class="col-12 p-0 m-0">
|
||||
<div v-for="(res, index) in feed" class="p-1">
|
||||
<a :href="postUrl(res)" data-fancybox="recent" :data-src="res.media_attachments[0].url" :data-width="res.media_attachments[0].width" :data-height="res.media_attachments[0].height">
|
||||
<img
|
||||
:src="res.media_attachments[0].url"
|
||||
width="100%"
|
||||
class="user-select-none"
|
||||
style="overflow: hidden;object-fit: contain;"
|
||||
:draggable="false"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex fixed-bottom p-3 justify-content-between align-items-center">
|
||||
<a v-if="user" class="logo-mark logo-mark-sm mb-0 p-1" href="/">
|
||||
<span class="text-gradient-primary">portfolio</span>
|
||||
</a>
|
||||
<span v-else class="logo-mark logo-mark-sm mb-0 p-1">
|
||||
<span class="text-gradient-primary">portfolio</span>
|
||||
</span>
|
||||
<p v-if="user && user.id == profile.id" class="text-center mb-0">
|
||||
<a :href="settingsUrl" class="text-muted"><i class="far fa-cog fa-lg"></i></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
import '@fancyapps/fancybox/dist/jquery.fancybox.js';
|
||||
import '@fancyapps/fancybox/dist/jquery.fancybox.css';
|
||||
|
||||
export default {
|
||||
props: [ 'initialData' ],
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
user: undefined,
|
||||
profile: undefined,
|
||||
settings: undefined,
|
||||
feed: [],
|
||||
albumIndex: 0,
|
||||
settingsUrl: window._portfolio.path + '/settings'
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const initialData = JSON.parse(this.initialData);
|
||||
this.profile = initialData.profile;
|
||||
this.fetchUser();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchUser() {
|
||||
axios.get('/api/v1/accounts/verify_credentials')
|
||||
.then(res => {
|
||||
this.user = res.data;
|
||||
})
|
||||
.catch(err => {
|
||||
});
|
||||
|
||||
await axios.get('/api/portfolio/account/settings.json', {
|
||||
params: {
|
||||
id: this.profile.id
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.settings = res.data;
|
||||
})
|
||||
.then(() => {
|
||||
this.fetchFeed();
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
async fetchFeed() {
|
||||
axios.get('/api/portfolio/' + this.profile.id + '/feed')
|
||||
.then(res => {
|
||||
this.feed = res.data.filter(p => p.pf_type === "photo");
|
||||
})
|
||||
.then(() => {
|
||||
this.setAlbumSlide();
|
||||
})
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 500);
|
||||
})
|
||||
.then(() => {
|
||||
if(this.settings.profile_layout === 'masonry') {
|
||||
setTimeout(() => {
|
||||
this.initMasonry();
|
||||
}, 500);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
postUrl(res) {
|
||||
return `${window._portfolio.path}/${this.profile.username}/${res.id}`;
|
||||
},
|
||||
|
||||
albumPrev() {
|
||||
if(this.albumIndex === 0) {
|
||||
return;
|
||||
}
|
||||
if(this.albumIndex === 1) {
|
||||
this.albumIndex--;
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.delete('slide');
|
||||
window.history.pushState({}, '', url);
|
||||
return;
|
||||
}
|
||||
this.albumIndex--;
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('slide', this.albumIndex + 1);
|
||||
window.history.pushState({}, '', url);
|
||||
},
|
||||
|
||||
albumNext() {
|
||||
if(this.albumIndex === this.feed.length - 1) {
|
||||
return;
|
||||
}
|
||||
this.albumIndex++;
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('slide', this.albumIndex + 1);
|
||||
window.history.pushState({}, '', url);
|
||||
},
|
||||
|
||||
setAlbumSlide() {
|
||||
const url = new URL(window.location);
|
||||
if(url.searchParams.has('slide')) {
|
||||
const slide = Number.parseInt(url.searchParams.get('slide'));
|
||||
if(Number.isNaN(slide)) {
|
||||
return;
|
||||
}
|
||||
if(slide <= 0) {
|
||||
return;
|
||||
}
|
||||
if(slide > this.feed.length) {
|
||||
return;
|
||||
}
|
||||
this.albumIndex = url.searchParams.get('slide') - 1;
|
||||
}
|
||||
},
|
||||
|
||||
initMasonry() {
|
||||
$('[data-fancybox="recent"]').fancybox({
|
||||
gutter: 20,
|
||||
modal: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
459
resources/assets/js/components/PortfolioSettings.vue
Normal file
459
resources/assets/js/components/PortfolioSettings.vue
Normal file
|
@ -0,0 +1,459 @@
|
|||
<template>
|
||||
<div class="portfolio-settings px-3">
|
||||
<div v-if="loading" class="d-flex justify-content-center align-items-center py-5">
|
||||
<b-spinner variant="primary" />
|
||||
</div>
|
||||
<div v-else class="row justify-content-center mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 bg-dark py-2 rounded">
|
||||
<ul class="nav nav-pills nav-fill">
|
||||
<li v-for="(tab, index) in tabs" class="nav-item" :class="{ disabled: index !== 0 && !settings.active}">
|
||||
<span v-if="index !== 0 && !settings.active" class="nav-link">{{ tab }}</span>
|
||||
<a v-else class="nav-link" :class="{ active: tab === tabIndex }" href="#" @click.prevent="toggleTab(tab)">{{ tab }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<transition name="slide-fade">
|
||||
<div v-if="tabIndex === 'Configure'" class="col-12 col-md-8 bg-dark mt-3 py-2 rounded" key="0">
|
||||
<div v-if="!user.statuses_count" class="alert alert-danger">
|
||||
<p class="mb-0 small font-weight-bold">You don't have any public posts, once you share public posts you can enable your portfolio.</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center py-2">
|
||||
<div class="setting-label">
|
||||
<p class="lead mb-0">Portfolio Enabled</p>
|
||||
<p class="small mb-0 text-muted">You must enable your portfolio before you or anyone can view it.</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-switch mt-n1">
|
||||
<b-form-checkbox v-model="settings.active" name="check-button" size="lg" switch :disabled="!user.statuses_count" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center py-2">
|
||||
<div class="setting-label" style="max-width: 50%;">
|
||||
<p class="mb-0">Portfolio Source</p>
|
||||
<p class="small mb-0 text-muted">Choose how you want to populate your portfolio, select Most Recent posts to automatically update your portfolio with recent posts or Curated Posts to select specific posts for your portfolio.</p>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<b-form-select v-model="settings.profile_source" :options="profileSourceOptions" :disabled="!user.statuses_count" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tabIndex === 'Curate'" class="col-12 col-md-8 mt-3 py-2 px-0" key="1">
|
||||
<div v-if="!recentPostsLoaded" class="d-flex align-items-center justify-content-center py-5 my-5">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<p class="text-muted">Loading recent posts...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="mt-n2 mb-4">
|
||||
<p class="text-muted small">Select up to 24 photos from your 100 most recent posts. You can only select public photo posts, videos are not supported at this time.</p>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<p class="font-weight-bold mb-0">Selected {{ selectedRecentPosts.length }}/24</p>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-link font-weight-bold mr-3 text-decoration-none"
|
||||
:disabled="!selectedRecentPosts.length"
|
||||
@click="clearSelected">
|
||||
Clear selected
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-primary py-0 font-weight-bold"
|
||||
style="width: 150px;"
|
||||
:disabled="!canSaveCurated"
|
||||
@click="saveCurated()">
|
||||
<template v-if="!isSavingCurated">Save</template>
|
||||
<b-spinner v-else small />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span @click="recentPostsPrev">
|
||||
<i :class="prevClass" />
|
||||
</span>
|
||||
|
||||
<div class="row flex-grow-1 mx-2">
|
||||
<div v-for="(post, index) in recentPosts.slice(rpStart, rpStart + 9)" class="col-12 col-md-4 mb-1 p-1">
|
||||
<div class="square user-select-none" @click.prevent="toggleRecentPost(post.id)">
|
||||
<transition name="fade">
|
||||
<img
|
||||
:key="post.id"
|
||||
:src="post.media_attachments[0].url"
|
||||
width="100%"
|
||||
height="300"
|
||||
style="overflow: hidden;object-fit: cover;"
|
||||
:draggable="false"
|
||||
class="square-content pr-1">
|
||||
</transition>
|
||||
|
||||
<div v-if="selectedRecentPosts.indexOf(post.id) !== -1" style="position: absolute;right: -5px;bottom:-5px;">
|
||||
<div class="selected-badge">{{ selectedRecentPosts.indexOf(post.id) + 1 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span @click="recentPostsNext()">
|
||||
<i :class="nextClass" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tabIndex === 'Customize'" class="col-12 col-md-8 mt-3 py-2" key="2">
|
||||
<div v-for="setting in customizeSettings" class="card bg-dark mb-5">
|
||||
<div class="card-header">{{ setting.title }}</div>
|
||||
<div class="list-group bg-dark">
|
||||
<div v-for="item in setting.items" class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-center py-2">
|
||||
<div class="setting-label">
|
||||
<p class="mb-0">{{ item.label }}</p>
|
||||
<p v-if="item.description" class="small text-muted mb-0">{{ item.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-switch mt-n1">
|
||||
<b-form-checkbox
|
||||
v-model="settings[item.model]"
|
||||
name="check-button"
|
||||
size="lg"
|
||||
switch
|
||||
:disabled="item.requiredWithTrue && !settings[item.requiredWithTrue]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-dark mb-5">
|
||||
<div class="card-header">Portfolio</div>
|
||||
<div class="list-group bg-dark">
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-center py-2">
|
||||
<div class="setting-label">
|
||||
<p class="mb-0">Layout</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<b-form-select v-model="settings.profile_layout" :options="profileLayoutOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tabIndex === 'Share'" class="col-12 col-md-8 bg-dark mt-3 py-2 rounded" key="0">
|
||||
<div class="py-2">
|
||||
<p class="text-muted">Portfolio URL</p>
|
||||
<p class="lead mb-0"><a :href="settings.url">{{ settings.url }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
tabIndex: "Configure",
|
||||
tabs: [
|
||||
"Configure",
|
||||
"Customize",
|
||||
"View Portfolio"
|
||||
],
|
||||
user: undefined,
|
||||
settings: undefined,
|
||||
recentPostsLoaded: false,
|
||||
rpStart: 0,
|
||||
recentPosts: [],
|
||||
recentPostsPage: undefined,
|
||||
selectedRecentPosts: [],
|
||||
isSavingCurated: false,
|
||||
canSaveCurated: false,
|
||||
customizeSettings: [],
|
||||
profileSourceOptions: [
|
||||
{ value: null, text: 'Please select an option', disabled: true },
|
||||
{ value: 'recent', text: 'Most recent posts' },
|
||||
],
|
||||
profileLayoutOptions: [
|
||||
{ value: null, text: 'Please select an option', disabled: true },
|
||||
{ value: 'grid', text: 'Grid' },
|
||||
{ value: 'masonry', text: 'Masonry' },
|
||||
{ value: 'album', text: 'Album' },
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
prevClass() {
|
||||
return this.rpStart === 0 ?
|
||||
"fa fa-arrow-circle-left fa-3x text-dark" :
|
||||
"fa fa-arrow-circle-left fa-3x text-muted cursor-pointer";
|
||||
},
|
||||
|
||||
nextClass() {
|
||||
return this.rpStart > (this.recentPosts.length - 9) ?
|
||||
"fa fa-arrow-circle-right fa-3x text-dark" :
|
||||
"fa fa-arrow-circle-right fa-3x text-muted cursor-pointer";
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
settings: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler: function(o, n) {
|
||||
if(this.loading) {
|
||||
return;
|
||||
}
|
||||
if(!n.show_timestamp) {
|
||||
this.settings.show_link = false;
|
||||
}
|
||||
this.updateSettings();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchUser();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchUser() {
|
||||
axios.get('/api/v1/accounts/verify_credentials')
|
||||
.then(res => {
|
||||
this.user = res.data;
|
||||
|
||||
if(res.data.statuses_count > 0) {
|
||||
this.profileSourceOptions = [
|
||||
{ value: null, text: 'Please select an option', disabled: true },
|
||||
{ value: 'recent', text: 'Most recent posts' },
|
||||
{ value: 'custom', text: 'Curated posts' },
|
||||
];
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.settings.active = false;
|
||||
this.settings.profile_source = 'recent';
|
||||
this.tabIndex = 'Configure';
|
||||
}, 1000);
|
||||
}
|
||||
})
|
||||
|
||||
axios.post(this.apiPath('/api/portfolio/self/settings.json'))
|
||||
.then(res => {
|
||||
this.settings = res.data;
|
||||
this.updateTabs();
|
||||
if(res.data.metadata && res.data.metadata.posts) {
|
||||
this.selectedRecentPosts = res.data.metadata.posts;
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.initCustomizeSettings();
|
||||
})
|
||||
.then(() => {
|
||||
const url = new URL(window.location);
|
||||
if(url.searchParams.has('tab')) {
|
||||
let tab = url.searchParams.get('tab');
|
||||
let tabs = this.settings.profile_source === 'custom' ?
|
||||
['curate', 'customize', 'share'] :
|
||||
['customize', 'share'];
|
||||
if(tabs.indexOf(tab) !== -1) {
|
||||
this.toggleTab(tab.slice(0, 1).toUpperCase() + tab.slice(1));
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 500);
|
||||
})
|
||||
},
|
||||
|
||||
apiPath(path) {
|
||||
return path;
|
||||
},
|
||||
|
||||
toggleTab(idx) {
|
||||
if(idx === 'Curate' && !this.recentPostsLoaded) {
|
||||
this.loadRecentPosts();
|
||||
}
|
||||
this.tabIndex = idx;
|
||||
this.rpStart = 0;
|
||||
if(idx == 'Configure') {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.delete('tab');
|
||||
window.history.pushState({}, '', url);
|
||||
} else if (idx == 'View Portfolio') {
|
||||
this.tabIndex = 'Configure';
|
||||
window.location.href = `https://${window._portfolio.domain}${window._portfolio.path}/${this.user.username}`;
|
||||
return;
|
||||
} else {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('tab', idx.toLowerCase());
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
},
|
||||
|
||||
updateTabs() {
|
||||
if(this.settings.profile_source === 'custom') {
|
||||
this.tabs = [
|
||||
"Configure",
|
||||
"Curate",
|
||||
"Customize",
|
||||
"View Portfolio"
|
||||
];
|
||||
} else {
|
||||
this.tabs = [
|
||||
"Configure",
|
||||
"Customize",
|
||||
"View Portfolio"
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
updateSettings() {
|
||||
axios.post(this.apiPath('/api/portfolio/self/update-settings.json'), this.settings)
|
||||
.then(res => {
|
||||
this.updateTabs();
|
||||
this.$bvToast.toast(`Your settings have been successfully updated!`, {
|
||||
variant: 'dark',
|
||||
title: 'Settings Updated',
|
||||
autoHideDelay: 2000,
|
||||
appendToast: false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
loadRecentPosts() {
|
||||
axios.get('/api/v1/accounts/' + this.user.id + '/statuses?only_media=1&media_types=photo&limit=100')
|
||||
.then(res => {
|
||||
if(res.data.length) {
|
||||
this.recentPosts = res.data.filter(p => p.visibility === "public");
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
this.recentPostsLoaded = true;
|
||||
}, 500);
|
||||
})
|
||||
},
|
||||
|
||||
toggleRecentPost(id) {
|
||||
if(this.selectedRecentPosts.indexOf(id) == -1) {
|
||||
if(this.selectedRecentPosts.length === 24) {
|
||||
return;
|
||||
}
|
||||
this.selectedRecentPosts.push(id);
|
||||
} else {
|
||||
this.selectedRecentPosts = this.selectedRecentPosts.filter(i => i !== id);
|
||||
}
|
||||
this.canSaveCurated = true;
|
||||
},
|
||||
|
||||
recentPostsPrev() {
|
||||
if(this.rpStart === 0) {
|
||||
return;
|
||||
}
|
||||
this.rpStart = this.rpStart - 9;
|
||||
},
|
||||
|
||||
recentPostsNext() {
|
||||
if(this.rpStart > (this.recentPosts.length - 9)) {
|
||||
return;
|
||||
}
|
||||
this.rpStart = this.rpStart + 9;
|
||||
},
|
||||
|
||||
clearSelected() {
|
||||
this.selectedRecentPosts = [];
|
||||
},
|
||||
|
||||
saveCurated() {
|
||||
this.isSavingCurated = true;
|
||||
event.currentTarget?.blur();
|
||||
|
||||
axios.post('/api/portfolio/self/curated.json', {
|
||||
ids: this.selectedRecentPosts
|
||||
})
|
||||
.then(res => {
|
||||
this.isSavingCurated = false;
|
||||
this.$bvToast.toast(`Your curated posts have been updated!`, {
|
||||
variant: 'dark',
|
||||
title: 'Portfolio Updated',
|
||||
autoHideDelay: 2000,
|
||||
appendToast: false
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
this.isSavingCurated = false;
|
||||
this.$bvToast.toast(`An error occured while attempting to update your portfolio, please try again later and contact an admin if this problem persists.`, {
|
||||
variant: 'dark',
|
||||
title: 'Error',
|
||||
autoHideDelay: 2000,
|
||||
appendToast: false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
initCustomizeSettings() {
|
||||
this.customizeSettings = [
|
||||
{
|
||||
title: "Post Settings",
|
||||
items: [
|
||||
{
|
||||
label: "Show Captions",
|
||||
model: "show_captions"
|
||||
},
|
||||
{
|
||||
label: "Show License",
|
||||
model: "show_license"
|
||||
},
|
||||
{
|
||||
label: "Show Location",
|
||||
model: "show_location"
|
||||
},
|
||||
{
|
||||
label: "Show Timestamp",
|
||||
model: "show_timestamp"
|
||||
},
|
||||
{
|
||||
label: "Link to Post",
|
||||
description: "Add link to timestamp to view the original post url, requires show timestamp to be enabled",
|
||||
model: "show_link",
|
||||
requiredWithTrue: "show_timestamp"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: "Profile Settings",
|
||||
items: [
|
||||
{
|
||||
label: "Show Avatar",
|
||||
model: "show_avatar"
|
||||
},
|
||||
{
|
||||
label: "Show Bio",
|
||||
model: "show_bio"
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
19
resources/assets/js/portfolio.js
vendored
Normal file
19
resources/assets/js/portfolio.js
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Vue from 'vue';
|
||||
window.Vue = Vue;
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
Vue.component(
|
||||
'portfolio-post',
|
||||
require('./components/PortfolioPost.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'portfolio-profile',
|
||||
require('./components/PortfolioProfile.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'portfolio-settings',
|
||||
require('./components/PortfolioSettings.vue').default
|
||||
);
|
54
resources/assets/sass/lib/inter.scss
vendored
Normal file
54
resources/assets/sass/lib/inter.scss
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, 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;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, 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;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, 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;
|
||||
}
|
173
resources/assets/sass/portfolio.scss
vendored
Normal file
173
resources/assets/sass/portfolio.scss
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
@import "lib/inter";
|
||||
|
||||
body {
|
||||
background: #000000;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400 !important;
|
||||
color: #d4d4d8;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #3B82F6 !important;
|
||||
}
|
||||
|
||||
.lead,
|
||||
.font-weight-light {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3B82F6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.text-gradient-primary {
|
||||
background: linear-gradient(to right, #6366f1, #8B5CF6, #D946EF);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.logo-mark {
|
||||
border-radius: 1rem;
|
||||
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif!important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -1.5px;
|
||||
border: 6px solid #212529;
|
||||
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.2;
|
||||
|
||||
user-select: none;
|
||||
color: #fff !important;
|
||||
text-decoration: none !important;
|
||||
background: #212529;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
|
||||
&-sm {
|
||||
font-size: 16px !important;
|
||||
border-width: 3px;
|
||||
border-radius: 10px;
|
||||
letter-spacing: -1px;
|
||||
background: #212529;
|
||||
}
|
||||
}
|
||||
|
||||
.display-4.font-weight-bold {
|
||||
letter-spacing: -0.3px;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
letter-spacing: -3px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #d1d5db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.display-4 {
|
||||
font-size: 1.5rem;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3B82F6;
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
-moz-column-count: 3;
|
||||
column-count: 3;
|
||||
-moz-column-gap: 0px;
|
||||
column-gap: 0px;
|
||||
orphans: 1;
|
||||
widows: 1;
|
||||
}
|
||||
|
||||
.portfolio-settings {
|
||||
.nav-pills {
|
||||
.nav-item {
|
||||
&.disabled {
|
||||
span {
|
||||
pointer-events: none;
|
||||
color: #3f3f46;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 15px;
|
||||
color: #9ca3af;
|
||||
font-weight: 400;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background-image: linear-gradient(to right, #4f46e5 0%, #2F80ED 51%, #4f46e5 100%);
|
||||
background-size: 200% auto;
|
||||
font-weight: 100;
|
||||
transition: 0.5s;
|
||||
|
||||
&:hover {
|
||||
background-position: right center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
&-header {
|
||||
background-color: #000;
|
||||
border: 1px solid var(--dark);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
padding-left: 20px;
|
||||
color: #fff;
|
||||
background: #000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat;
|
||||
border-color: var(--dark);
|
||||
}
|
||||
|
||||
.selected-badge {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
border-radius: 26px;
|
||||
background-color: #0284c7;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-fade-enter-active {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.slide-fade-leave-active {
|
||||
transition: all .3s cubic-bezier(1.0, 1.0);
|
||||
}
|
||||
|
||||
.slide-fade-enter, .slide-fade-leave-to {
|
||||
transform: translateX(10px);
|
||||
opacity: 0;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<nav class="navbar navbar-expand navbar-light navbar-laravel shadow-none border-bottom sticky-top py-1">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/" title="Logo">
|
||||
<a class="navbar-brand d-flex align-items-center" href="{{ config('app.url') }}" title="Logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2" loading="eager" alt="Pixelfed logo">
|
||||
<span class="font-weight-bold mb-0 d-none d-sm-block" style="font-size:20px;">{{ config_cache('app.name') }}</span>
|
||||
</a>
|
||||
|
|
21
resources/views/portfolio/404.blade.php
Normal file
21
resources/views/portfolio/404.blade.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
@extends('portfolio.layout')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row mt-5 pt-5">
|
||||
<div class="col-12 text-center">
|
||||
<p class="mb-5">
|
||||
<span class="logo-mark px-3"><span class="text-gradient-primary">portfolio</span></span>
|
||||
</p>
|
||||
|
||||
<h1>404 - Not Found</h1>
|
||||
|
||||
<p class="lead pt-3 mb-4">This portfolio or post is either not active or has been removed.</p>
|
||||
|
||||
<p class="mt-3">
|
||||
<a href="{{ config('app.url') }}" class="text-muted" style="text-decoration: underline;">Go back home</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
36
resources/views/portfolio/index.blade.php
Normal file
36
resources/views/portfolio/index.blade.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
@extends('portfolio.layout')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5 pt-5">
|
||||
<div class="col-12 col-md-6 text-center">
|
||||
<p class="mb-3">
|
||||
<span class="logo-mark px-3"><span class="text-gradient-primary">portfolio</span></span>
|
||||
</p>
|
||||
|
||||
<div class="spinner-border mt-5" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
@auth
|
||||
axios.get('/api/v1/accounts/verify_credentials')
|
||||
.then(res => {
|
||||
if(res.data.locked == false) {
|
||||
window.location.href = 'https://{{ config('portfolio.domain') }}{{ config('portfolio.path') }}/' + res.data.username
|
||||
} else {
|
||||
window.location.href = "{{ config('app.url') }}";
|
||||
}
|
||||
})
|
||||
@else
|
||||
window.location.href = "{{ config('app.url') }}";
|
||||
@endauth
|
||||
|
||||
</script>
|
||||
@endpush
|
40
resources/views/portfolio/layout.blade.php
Normal file
40
resources/views/portfolio/layout.blade.php
Normal 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="mobile-web-app-capable" content="yes">
|
||||
|
||||
<title>{!! $title ?? config_cache('app.name') !!}</title>
|
||||
|
||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="{{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="{{request()->url()}}">
|
||||
<link href="{{ mix('css/app.css') }}" rel="stylesheet" data-stylesheet="light">
|
||||
<link href="{{ mix('css/portfolio.css') }}" rel="stylesheet" data-stylesheet="light">
|
||||
<script type="text/javascript">window._portfolio = { domain: "{{config('portfolio.domain')}}", path: "{{config('portfolio.path')}}"}</script>
|
||||
|
||||
</head>
|
||||
<body class="w-100 h-100">
|
||||
<main id="content" class="w-100 h-100">
|
||||
@yield('content')
|
||||
</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/app.js') }}"></script>
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
23
resources/views/portfolio/settings.blade.php
Normal file
23
resources/views/portfolio/settings.blade.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
@extends('portfolio.layout')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row mt-5 pt-5 px-0 align-items-center">
|
||||
<div class="col-12 mb-5 col-md-8">
|
||||
<span class="logo-mark px-3"><span class="text-gradient-primary">portfolio</span></span>
|
||||
</div>
|
||||
<div class="col-12 mb-5 col-md-4 text-md-right">
|
||||
<h1 class="font-weight-bold">Settings</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<portfolio-settings />
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/portfolio.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
App.boot();
|
||||
</script>
|
||||
@endpush
|
12
resources/views/portfolio/show.blade.php
Normal file
12
resources/views/portfolio/show.blade.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
@extends('portfolio.layout', ['title' => "@{$user['username']}'s Portfolio"])
|
||||
|
||||
@section('content')
|
||||
<portfolio-profile initial-data="{{json_encode(['profile' => $user])}}" />
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/portfolio.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
App.boot();
|
||||
</script>
|
||||
@endpush
|
17
resources/views/portfolio/show_post.blade.php
Normal file
17
resources/views/portfolio/show_post.blade.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
@extends('portfolio.layout', ['title' => "@{$user['username']}'s Portfolio Photo"])
|
||||
|
||||
@section('content')
|
||||
<portfolio-post initial-data="{{json_encode(['profile' => $user, 'post' => $post, 'authed' => $authed ? true : false])}}" />
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/portfolio.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
App.boot();
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@push('meta')<meta property="og:description" content="{{ $post['content_text'] }}">
|
||||
<meta property="og:image" content="{{ $post['media_attachments'][0]['url']}}">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
@endpush
|
|
@ -100,6 +100,28 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
});
|
||||
});
|
||||
|
||||
Route::domain(config('portfolio.domain'))->group(function () {
|
||||
Route::redirect('redirect/home', config('app.url'));
|
||||
Route::get('/', 'PortfolioController@index');
|
||||
Route::post('api/portfolio/self/curated.json', 'PortfolioController@storeCurated');
|
||||
Route::post('api/portfolio/self/settings.json', 'PortfolioController@getSettings');
|
||||
Route::get('api/portfolio/account/settings.json', 'PortfolioController@getAccountSettings');
|
||||
Route::post('api/portfolio/self/update-settings.json', 'PortfolioController@storeSettings');
|
||||
Route::get('api/portfolio/{username}/feed', 'PortfolioController@getFeed');
|
||||
|
||||
Route::prefix(config('portfolio.path'))->group(function() {
|
||||
Route::get('/', 'PortfolioController@index');
|
||||
Route::get('settings', 'PortfolioController@settings')->name('portfolio.settings');
|
||||
Route::post('settings', 'PortfolioController@store');
|
||||
Route::get('{username}/{id}', 'PortfolioController@showPost');
|
||||
Route::get('{username}', 'PortfolioController@show');
|
||||
|
||||
Route::fallback(function () {
|
||||
return view('errors.404');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
|
||||
Route::get('/', 'SiteController@home')->name('timeline.personal');
|
||||
|
||||
|
@ -268,6 +290,14 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::post('v1/publish', 'StoryController@publishStory');
|
||||
Route::delete('v1/delete/{id}', 'StoryController@apiV1Delete');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'portfolio'], function () {
|
||||
Route::post('self/curated.json', 'PortfolioController@storeCurated');
|
||||
Route::post('self/settings.json', 'PortfolioController@getSettings');
|
||||
Route::get('account/settings.json', 'PortfolioController@getAccountSettings');
|
||||
Route::post('self/update-settings.json', 'PortfolioController@storeSettings');
|
||||
Route::get('{username}/feed', 'PortfolioController@getFeed');
|
||||
});
|
||||
});
|
||||
|
||||
Route::get('discover/tags/{hashtag}', 'DiscoverController@showTags');
|
||||
|
@ -352,6 +382,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::post('warning', 'AccountInterstitialController@read');
|
||||
Route::get('my2020', 'SeasonalController@yearInReview');
|
||||
|
||||
Route::get('web/my-portfolio', 'PortfolioController@myRedirect');
|
||||
Route::get('web/hashtag/{tag}', 'SpaController@hashtagRedirect');
|
||||
Route::get('web/username/{id}', 'SpaController@usernameRedirect');
|
||||
Route::get('web/post/{id}', 'SpaController@webPost');
|
||||
|
|
Loading…
Add table
Reference in a new issue