mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-23 06:51:27 +00:00
Merge pull request #1104 from pixelfed/frontend-ui-refactor
Frontend ui refactor
This commit is contained in:
commit
82102376fe
40 changed files with 1439 additions and 3186 deletions
|
@ -9,6 +9,7 @@ use Cache;
|
||||||
use App\Comment;
|
use App\Comment;
|
||||||
use App\Jobs\CommentPipeline\CommentPipeline;
|
use App\Jobs\CommentPipeline\CommentPipeline;
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
|
@ -53,10 +54,11 @@ class CommentController extends Controller
|
||||||
|
|
||||||
Cache::forget('transform:status:'.$status->url());
|
Cache::forget('transform:status:'.$status->url());
|
||||||
|
|
||||||
|
$autolink = Autolink::create()->autolink($comment);
|
||||||
$reply = new Status();
|
$reply = new Status();
|
||||||
$reply->profile_id = $profile->id;
|
$reply->profile_id = $profile->id;
|
||||||
$reply->caption = e($comment);
|
$reply->caption = e($comment);
|
||||||
$reply->rendered = $comment;
|
$reply->rendered = $autolink;
|
||||||
$reply->in_reply_to_id = $status->id;
|
$reply->in_reply_to_id = $status->id;
|
||||||
$reply->in_reply_to_profile_id = $status->profile_id;
|
$reply->in_reply_to_profile_id = $status->profile_id;
|
||||||
$reply->save();
|
$reply->save();
|
||||||
|
|
|
@ -35,8 +35,8 @@ class FederationController extends Controller
|
||||||
{
|
{
|
||||||
$this->authCheck();
|
$this->authCheck();
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'acct' => 'required|string|min:3|max:255',
|
'acct' => 'required|string|min:3|max:255',
|
||||||
]);
|
]);
|
||||||
$acct = $request->input('acct');
|
$acct = $request->input('acct');
|
||||||
$nickname = Nickname::normalizeProfileUrl($acct);
|
$nickname = Nickname::normalizeProfileUrl($acct);
|
||||||
|
|
||||||
|
@ -63,6 +63,11 @@ class FederationController extends Controller
|
||||||
|
|
||||||
$follower = Auth::user()->profile;
|
$follower = Auth::user()->profile;
|
||||||
$url = $request->input('url');
|
$url = $request->input('url');
|
||||||
|
$url = Helpers::validateUrl($url);
|
||||||
|
|
||||||
|
if(!$url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RemoteFollowPipeline::dispatch($follower, $url);
|
RemoteFollowPipeline::dispatch($follower, $url);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,11 @@ use App\Status;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use App\Transformer\Api\{
|
||||||
|
AccountTransformer,
|
||||||
|
HashtagTransformer,
|
||||||
|
StatusTransformer,
|
||||||
|
};
|
||||||
|
|
||||||
class SearchController extends Controller
|
class SearchController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -22,26 +27,33 @@ class SearchController extends Controller
|
||||||
if(mb_strlen($tag) < 3) {
|
if(mb_strlen($tag) < 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$tag = e(urldecode($tag));
|
||||||
|
|
||||||
$hash = hash('sha256', $tag);
|
$hash = hash('sha256', $tag);
|
||||||
$tokens = Cache::remember('api:search:tag:'.$hash, now()->addMinutes(5), function () use ($tag) {
|
$tokens = Cache::remember('api:search:tag:'.$hash, now()->addMinutes(5), function () use ($tag) {
|
||||||
$tokens = collect([]);
|
$tokens = [];
|
||||||
if(Helpers::validateUrl($tag)) {
|
if(Helpers::validateUrl($tag) != false) {
|
||||||
$remote = Helpers::fetchFromUrl($tag);
|
$remote = Helpers::fetchFromUrl($tag);
|
||||||
if(isset($remote['type']) && in_array($remote['type'], ['Create', 'Person']) == true) {
|
if(isset($remote['type']) && in_array($remote['type'], ['Create', 'Person']) == true) {
|
||||||
$type = $remote['type'];
|
$type = $remote['type'];
|
||||||
if($type == 'Person') {
|
if($type == 'Person') {
|
||||||
$item = Helpers::profileFirstOrNew($tag);
|
$item = Helpers::profileFirstOrNew($tag);
|
||||||
$tokens->push([[
|
$tokens['profiles'] = [[
|
||||||
'count' => 1,
|
'count' => 1,
|
||||||
'url' => $item->url(),
|
'url' => $item->url(),
|
||||||
'type' => 'profile',
|
'type' => 'profile',
|
||||||
'value' => $item->username,
|
'value' => $item->username,
|
||||||
'tokens' => [$item->username],
|
'tokens' => [$item->username],
|
||||||
'name' => $item->name,
|
'name' => $item->name,
|
||||||
]]);
|
'entity' => [
|
||||||
|
'id' => $item->id,
|
||||||
|
'following' => $item->followedBy(Auth::user()->profile),
|
||||||
|
'thumb' => $item->avatarUrl()
|
||||||
|
]
|
||||||
|
]];
|
||||||
} else if ($type == 'Create') {
|
} else if ($type == 'Create') {
|
||||||
$item = Helpers::statusFirstOrFetch($tag, false);
|
$item = Helpers::statusFirstOrFetch($tag, false);
|
||||||
$tokens->push([[
|
$tokens['posts'] = [[
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
'url' => $item->url(),
|
'url' => $item->url(),
|
||||||
'type' => 'status',
|
'type' => 'status',
|
||||||
|
@ -49,10 +61,9 @@ class SearchController extends Controller
|
||||||
'tokens' => [$item->caption],
|
'tokens' => [$item->caption],
|
||||||
'name' => $item->caption,
|
'name' => $item->caption,
|
||||||
'thumb' => $item->thumb(),
|
'thumb' => $item->thumb(),
|
||||||
]]);
|
]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$hashtags = Hashtag::select('id', 'name', 'slug')->where('slug', 'like', '%'.$tag.'%')->whereHas('posts')->limit(20)->get();
|
$hashtags = Hashtag::select('id', 'name', 'slug')->where('slug', 'like', '%'.$tag.'%')->whereHas('posts')->limit(20)->get();
|
||||||
if($hashtags->count() > 0) {
|
if($hashtags->count() > 0) {
|
||||||
|
@ -62,37 +73,46 @@ class SearchController extends Controller
|
||||||
'url' => $item->url(),
|
'url' => $item->url(),
|
||||||
'type' => 'hashtag',
|
'type' => 'hashtag',
|
||||||
'value' => $item->name,
|
'value' => $item->name,
|
||||||
'tokens' => explode('-', $item->name),
|
'tokens' => '',
|
||||||
'name' => null,
|
'name' => null,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$tokens->push($tags);
|
$tokens['hashtags'] = $tags;
|
||||||
}
|
}
|
||||||
$users = Profile::select('username', 'name', 'id')
|
|
||||||
->whereNull('status')
|
|
||||||
->whereNull('domain')
|
|
||||||
->where('username', 'like', '%'.$tag.'%')
|
|
||||||
//->orWhere('remote_url', $tag)
|
|
||||||
->limit(20)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if($users->count() > 0) {
|
|
||||||
$profiles = $users->map(function ($item, $key) {
|
|
||||||
return [
|
|
||||||
'count' => 0,
|
|
||||||
'url' => $item->url(),
|
|
||||||
'type' => 'profile',
|
|
||||||
'value' => $item->username,
|
|
||||||
'tokens' => [$item->username],
|
|
||||||
'name' => $item->name,
|
|
||||||
'id' => $item->id
|
|
||||||
];
|
|
||||||
});
|
|
||||||
$tokens->push($profiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tokens;
|
return $tokens;
|
||||||
});
|
});
|
||||||
|
$users = Profile::select('username', 'name', 'id')
|
||||||
|
->whereNull('status')
|
||||||
|
->where('id', '!=', Auth::user()->profile->id)
|
||||||
|
->where('username', 'like', '%'.$tag.'%')
|
||||||
|
->orWhere('remote_url', $tag)
|
||||||
|
->limit(20)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if($users->count() > 0) {
|
||||||
|
$profiles = $users->map(function ($item, $key) {
|
||||||
|
return [
|
||||||
|
'count' => 0,
|
||||||
|
'url' => $item->url(),
|
||||||
|
'type' => 'profile',
|
||||||
|
'value' => $item->username,
|
||||||
|
'tokens' => [$item->username],
|
||||||
|
'name' => $item->name,
|
||||||
|
'avatar' => $item->avatarUrl(),
|
||||||
|
'id' => $item->id,
|
||||||
|
'entity' => [
|
||||||
|
'id' => $item->id,
|
||||||
|
'following' => $item->followedBy(Auth::user()->profile),
|
||||||
|
'thumb' => $item->avatarUrl()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
});
|
||||||
|
if(isset($tokens['profiles'])) {
|
||||||
|
array_push($tokens['profiles'], $profiles);
|
||||||
|
} else {
|
||||||
|
$tokens['profiles'] = $profiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
||||||
->whereHas('media')
|
->whereHas('media')
|
||||||
->whereNull('in_reply_to_id')
|
->whereNull('in_reply_to_id')
|
||||||
|
@ -100,7 +120,8 @@ class SearchController extends Controller
|
||||||
->whereProfileId(Auth::user()->profile->id)
|
->whereProfileId(Auth::user()->profile->id)
|
||||||
->where('caption', 'like', '%'.$tag.'%')
|
->where('caption', 'like', '%'.$tag.'%')
|
||||||
->orWhere('uri', $tag)
|
->orWhere('uri', $tag)
|
||||||
->orderBy('created_at', 'desc')
|
->latest()
|
||||||
|
->limit(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
if($posts->count() > 0) {
|
if($posts->count() > 0) {
|
||||||
|
@ -115,11 +136,9 @@ class SearchController extends Controller
|
||||||
'thumb' => $item->thumb(),
|
'thumb' => $item->thumb(),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$tokens = $tokens->push($posts);
|
$tokens['posts'] = $posts;
|
||||||
}
|
|
||||||
if($tokens->count() > 0) {
|
|
||||||
$tokens = $tokens[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($tokens);
|
return response()->json($tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ return [
|
||||||
| This value is the version of your PixelFed instance.
|
| This value is the version of your PixelFed instance.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'version' => '0.8.4',
|
'version' => '0.8.5',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
BIN
public/css/app.css
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/css/appdark.css
vendored
BIN
public/css/appdark.css
vendored
Binary file not shown.
BIN
public/css/landing.css
vendored
Normal file
BIN
public/css/landing.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/landing.js
vendored
BIN
public/js/landing.js
vendored
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/search.js
vendored
Normal file
BIN
public/js/search.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/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
2
resources/assets/js/bootstrap.js
vendored
2
resources/assets/js/bootstrap.js
vendored
|
@ -3,8 +3,6 @@ window.Popper = require('popper.js').default;
|
||||||
window.pixelfed = window.pixelfed || {};
|
window.pixelfed = window.pixelfed || {};
|
||||||
window.$ = window.jQuery = require('jquery');
|
window.$ = window.jQuery = require('jquery');
|
||||||
require('bootstrap');
|
require('bootstrap');
|
||||||
window.typeahead = require('./lib/typeahead');
|
|
||||||
window.Bloodhound = require('./lib/bloodhound');
|
|
||||||
window.axios = require('axios');
|
window.axios = require('axios');
|
||||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
require('readmore-js');
|
require('readmore-js');
|
||||||
|
|
7
resources/assets/js/components.js
vendored
7
resources/assets/js/components.js
vendored
|
@ -38,7 +38,7 @@ import swal from 'sweetalert';
|
||||||
|
|
||||||
// require('./components/localstorage');
|
// require('./components/localstorage');
|
||||||
// require('./components/commentform');
|
// require('./components/commentform');
|
||||||
require('./components/searchform');
|
//require('./components/searchform');
|
||||||
// require('./components/bookmarkform');
|
// require('./components/bookmarkform');
|
||||||
// require('./components/statusform');
|
// require('./components/statusform');
|
||||||
//require('./components/embed');
|
//require('./components/embed');
|
||||||
|
@ -63,6 +63,11 @@ require('./components/searchform');
|
||||||
// Initialize Notification Helper
|
// Initialize Notification Helper
|
||||||
window.pixelfed.n = {};
|
window.pixelfed.n = {};
|
||||||
|
|
||||||
|
// Vue.component(
|
||||||
|
// 'search-results',
|
||||||
|
// require('./components/SearchResults.vue').default
|
||||||
|
// );
|
||||||
|
|
||||||
// Vue.component(
|
// Vue.component(
|
||||||
// 'photo-presenter',
|
// 'photo-presenter',
|
||||||
// require('./components/presenter/PhotoPresenter.vue').default
|
// require('./components/presenter/PhotoPresenter.vue').default
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,18 +1,3 @@
|
||||||
<style scoped>
|
|
||||||
.status-comments,
|
|
||||||
.reactions,
|
|
||||||
.col-md-4 {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.postPresenterContainer {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
@media(min-width: 720px) {
|
|
||||||
.postPresenterContainer {
|
|
||||||
min-height: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<template>
|
<template>
|
||||||
<div class="postComponent d-none">
|
<div class="postComponent d-none">
|
||||||
<div class="container px-0">
|
<div class="container px-0">
|
||||||
|
@ -40,7 +25,7 @@
|
||||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile()">Block Profile</a>
|
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile()">Block Profile</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="ownerOrAdmin()">
|
<div v-if="ownerOrAdmin()">
|
||||||
<!-- <a class="dropdown-item font-weight-bold" :href="editUrl()">Disable Comments</a> -->
|
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
|
||||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
||||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
|
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,19 +77,18 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<div class="post-actions">
|
<div class="post-actions">
|
||||||
<div class="dropdown">
|
<div v-if="user != false" class="dropdown">
|
||||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||||
<span class="fas fa-ellipsis-v text-muted"></span>
|
<span class="fas fa-ellipsis-v text-muted"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||||
<span class="menu-user d-none">
|
<span v-if="!owner()">
|
||||||
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
|
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
|
||||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile">Mute Profile</a>
|
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile">Mute Profile</a>
|
||||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile">Block Profile</a>
|
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile">Block Profile</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="menu-author d-none">
|
<span v-if="ownerOrAdmin()">
|
||||||
<!-- <a class="dropdown-item font-weight-bold" :href="editUrl()">Mute Comments</a>
|
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
|
||||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Disable Comments</a> -->
|
|
||||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
||||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost">Delete</a>
|
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost">Delete</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -114,19 +98,49 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-md-column flex-column-reverse h-100">
|
<div class="d-flex flex-md-column flex-column-reverse h-100">
|
||||||
<div class="card-body status-comments">
|
<div class="card-body status-comments pb-5">
|
||||||
<div class="status-comment">
|
<div class="status-comment">
|
||||||
<p class="mb-1 read-more" style="overflow: hidden;">
|
<p class="mb-1 read-more" style="overflow: hidden;">
|
||||||
<span class="font-weight-bold pr-1">{{statusUsername}}</span>
|
<span class="font-weight-bold pr-1">{{statusUsername}}</span>
|
||||||
<span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
|
<span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
|
||||||
</p>
|
</p>
|
||||||
<post-comments :user="this.user" :post-id="statusId" :post-username="statusUsername"></post-comments>
|
|
||||||
|
<div v-if="showComments">
|
||||||
|
<div class="postCommentsLoader text-center">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="postCommentsContainer d-none pt-3">
|
||||||
|
<p class="mb-1 text-center load-more-link d-none"><a href="#" class="text-muted" v-on:click="loadMore">Load more comments</a></p>
|
||||||
|
<div class="comments" data-min-id="0" data-max-id="0">
|
||||||
|
<div v-for="(reply, index) in results" class="pb-3">
|
||||||
|
<p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
|
||||||
|
<span>
|
||||||
|
<a class="text-dark font-weight-bold mr-1" :href="reply.account.url" v-bind:title="reply.account.username">{{truncate(reply.account.username,15)}}</a>
|
||||||
|
<span class="text-break" v-html="reply.content"></span>
|
||||||
|
</span>
|
||||||
|
<span class="pl-2" style="min-width:38px">
|
||||||
|
<span v-on:click="likeReply(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
|
||||||
|
<post-menu :status="reply" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteComment(reply.id, index)"></post-menu>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
<span class="text-muted mr-3" style="width: 20px;" v-text="timeAgo(reply.created_at)"></span>
|
||||||
|
<span v-if="reply.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{reply.favourites_count == 1 ? '1 like' : reply.favourites_count + ' likes'}}</span>
|
||||||
|
<span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply)">Reply</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body flex-grow-0 py-1">
|
<div class="card-body flex-grow-0 py-1">
|
||||||
<div class="reactions my-1">
|
<div class="reactions my-1">
|
||||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
||||||
<h3 class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus"></h3>
|
<h3 class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
||||||
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||||
<h3 v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
<h3 v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -145,15 +159,29 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-white sticky-md-bottom">
|
<div v-if="showComments && user.length !== 0" class="card-footer bg-white px-2 py-0">
|
||||||
<div class="comment-form-guest">
|
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😂</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">💯</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">❤️</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">🙌</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">👏</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😍</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😯</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😢</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😅</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😁</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">🙂</li>
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction">😎</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-if="showComments" class="card-footer bg-white sticky-md-bottom p-0">
|
||||||
|
<div v-if="user.length == 0" class="comment-form-guest p-3">
|
||||||
<a href="/login">Login</a> to like or comment.
|
<a href="/login">Login</a> to like or comment.
|
||||||
</div>
|
</div>
|
||||||
<form class="comment-form d-none" method="post" action="/i/comment" :data-id="statusId" data-truncate="false">
|
<form v-else class="border-0 rounded-0 align-middle" method="post" action="/i/comment" :data-id="statusId" data-truncate="false">
|
||||||
<input type="hidden" name="_token" value="">
|
<textarea class="form-control border-0 rounded-0" name="comment" placeholder="Add a comment…" autocomplete="off" autocorrect="off" style="height:56px;line-height: 18px;max-height:80px;resize: none; padding-right:4.2rem;" v-model="replyText"></textarea>
|
||||||
<input type="hidden" name="item" :value="statusId">
|
<input type="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="postReply"/>
|
||||||
<input class="form-control" name="comment" placeholder="Add a comment…" autocomplete="off">
|
|
||||||
<input type="submit" value="Send" class="btn btn-primary comment-submit" />
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -243,6 +271,68 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style type="text/css" scoped>
|
||||||
|
.status-comments,
|
||||||
|
.reactions,
|
||||||
|
.col-md-4 {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.postPresenterContainer {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
@media(min-width: 720px) {
|
||||||
|
.postPresenterContainer {
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.reply-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 12px;
|
||||||
|
right: 20px;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
}
|
||||||
|
.text-lighter {
|
||||||
|
color:#B8C2CC !important;
|
||||||
|
}
|
||||||
|
.text-break {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.comments p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.comment-reaction {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
.show-reply-bar {
|
||||||
|
display: inline-block;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
height: 0;
|
||||||
|
margin-right: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
.comment-thread {
|
||||||
|
margin: 4px 0 0 40px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
.emoji-reactions .nav-item {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 7px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.emoji-reactions::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
pixelfed.postComponent = {};
|
pixelfed.postComponent = {};
|
||||||
|
@ -262,7 +352,16 @@ export default {
|
||||||
likesPage: 1,
|
likesPage: 1,
|
||||||
shares: [],
|
shares: [],
|
||||||
sharesPage: 1,
|
sharesPage: 1,
|
||||||
lightboxMedia: false
|
lightboxMedia: false,
|
||||||
|
replyText: '',
|
||||||
|
|
||||||
|
results: [],
|
||||||
|
pagination: {},
|
||||||
|
min_id: 0,
|
||||||
|
max_id: 0,
|
||||||
|
reply_to_profile_id: 0,
|
||||||
|
thread: false,
|
||||||
|
showComments: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -279,6 +378,8 @@ export default {
|
||||||
updated() {
|
updated() {
|
||||||
$('.carousel').carousel();
|
$('.carousel').carousel();
|
||||||
|
|
||||||
|
pixelfed.readmore();
|
||||||
|
|
||||||
if(this.reactions) {
|
if(this.reactions) {
|
||||||
if(this.reactions.bookmarked == true) {
|
if(this.reactions.bookmarked == true) {
|
||||||
$('.postComponent .far.fa-bookmark').removeClass('far').addClass('fas text-warning');
|
$('.postComponent .far.fa-bookmark').removeClass('far').addClass('fas text-warning');
|
||||||
|
@ -345,6 +446,10 @@ export default {
|
||||||
this.showMuteBlock();
|
this.showMuteBlock();
|
||||||
loader.hide();
|
loader.hide();
|
||||||
pixelfed.readmore();
|
pixelfed.readmore();
|
||||||
|
if(self.status.comments_disabled == false) {
|
||||||
|
self.showComments = true;
|
||||||
|
this.fetchComments();
|
||||||
|
}
|
||||||
$('.postComponent').removeClass('d-none');
|
$('.postComponent').removeClass('d-none');
|
||||||
$('.postPresenterLoader').addClass('d-none');
|
$('.postPresenterLoader').addClass('d-none');
|
||||||
$('.postPresenterContainer').removeClass('d-none');
|
$('.postPresenterContainer').removeClass('d-none');
|
||||||
|
@ -369,10 +474,6 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
commentFocus() {
|
|
||||||
$('.comment-form input[name="comment"]').focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
likesModal() {
|
likesModal() {
|
||||||
if(this.status.favourites_count == 0 || $('body').hasClass('loggedIn') == false) {
|
if(this.status.favourites_count == 0 || $('body').hasClass('loggedIn') == false) {
|
||||||
return;
|
return;
|
||||||
|
@ -422,6 +523,7 @@ export default {
|
||||||
|
|
||||||
likeStatus(event) {
|
likeStatus(event) {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
|
window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,6 +550,7 @@ export default {
|
||||||
|
|
||||||
shareStatus() {
|
shareStatus() {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
|
window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,6 +577,7 @@ export default {
|
||||||
|
|
||||||
bookmarkStatus() {
|
bookmarkStatus() {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
|
window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,6 +625,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
deletePost(status) {
|
deletePost(status) {
|
||||||
|
if(!this.ownerOrAdmin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var result = confirm('Are you sure you want to delete this post?');
|
var result = confirm('Are you sure you want to delete this post?');
|
||||||
if (result) {
|
if (result) {
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
|
@ -553,6 +660,198 @@ export default {
|
||||||
lightbox(src) {
|
lightbox(src) {
|
||||||
this.lightboxMedia = src;
|
this.lightboxMedia = src;
|
||||||
this.$refs.lightboxModal.show();
|
this.$refs.lightboxModal.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
postReply() {
|
||||||
|
let self = this;
|
||||||
|
if(this.replyText.length == 0 ||
|
||||||
|
this.replyText.trim() == '@'+this.status.account.acct) {
|
||||||
|
self.replyText = null;
|
||||||
|
$('textarea[name="comment"]').blur();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = {
|
||||||
|
item: this.statusId,
|
||||||
|
comment: this.replyText
|
||||||
|
}
|
||||||
|
axios.post('/i/comment', data)
|
||||||
|
.then(function(res) {
|
||||||
|
let entity = res.data.entity;
|
||||||
|
self.results.push(entity);
|
||||||
|
self.replyText = '';
|
||||||
|
let elem = $('.status-comments')[0];
|
||||||
|
elem.scrollTop = elem.clientHeight;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteComment(id, i) {
|
||||||
|
axios.post('/i/delete', {
|
||||||
|
type: 'comment',
|
||||||
|
item: id
|
||||||
|
}).then(res => {
|
||||||
|
this.results.splice(i, 1);
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Something went wrong!', 'Please try again later', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
l(e) {
|
||||||
|
let len = e.length;
|
||||||
|
if(len < 10) { return e; }
|
||||||
|
return e.substr(0, 10)+'...';
|
||||||
|
},
|
||||||
|
replyFocus(e) {
|
||||||
|
this.reply_to_profile_id = e.account.id;
|
||||||
|
this.replyText = '@' + e.account.username + ' ';
|
||||||
|
$('textarea[name="comment"]').focus();
|
||||||
|
},
|
||||||
|
fetchComments() {
|
||||||
|
let url = '/api/v2/comments/'+this.statusUsername+'/status/'+this.statusId;
|
||||||
|
axios.get(url)
|
||||||
|
.then(response => {
|
||||||
|
let self = this;
|
||||||
|
this.results = _.reverse(response.data.data);
|
||||||
|
this.pagination = response.data.meta.pagination;
|
||||||
|
if(this.results.length > 0) {
|
||||||
|
$('.load-more-link').removeClass('d-none');
|
||||||
|
}
|
||||||
|
$('.postCommentsLoader').addClass('d-none');
|
||||||
|
$('.postCommentsContainer').removeClass('d-none');
|
||||||
|
}).catch(error => {
|
||||||
|
if(!error.response) {
|
||||||
|
$('.postCommentsLoader .lds-ring')
|
||||||
|
.attr('style','width:100%')
|
||||||
|
.addClass('pt-4 font-weight-bold text-muted')
|
||||||
|
.text('An error occurred, cannot fetch comments. Please try again later.');
|
||||||
|
} else {
|
||||||
|
switch(error.response.status) {
|
||||||
|
case 401:
|
||||||
|
$('.postCommentsLoader .lds-ring')
|
||||||
|
.attr('style','width:100%')
|
||||||
|
.addClass('pt-4 font-weight-bold text-muted')
|
||||||
|
.text('Please login to view.');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$('.postCommentsLoader .lds-ring')
|
||||||
|
.attr('style','width:100%')
|
||||||
|
.addClass('pt-4 font-weight-bold text-muted')
|
||||||
|
.text('An error occurred, cannot fetch comments. Please try again later.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadMore(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if(this.pagination.total_pages == 1 || this.pagination.current_page == this.pagination.total_pages) {
|
||||||
|
$('.load-more-link').addClass('d-none');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('.postCommentsLoader').removeClass('d-none');
|
||||||
|
let next = this.pagination.links.next;
|
||||||
|
axios.get(next)
|
||||||
|
.then(response => {
|
||||||
|
let self = this;
|
||||||
|
let res = response.data.data;
|
||||||
|
$('.postCommentsLoader').addClass('d-none');
|
||||||
|
for(let i=0; i < res.length; i++) {
|
||||||
|
this.results.unshift(res[i]);
|
||||||
|
}
|
||||||
|
this.pagination = response.data.meta.pagination;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
likeReply(status, $event) {
|
||||||
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/i/like', {
|
||||||
|
item: status.id
|
||||||
|
}).then(res => {
|
||||||
|
status.favourites_count = res.data.count;
|
||||||
|
if(status.favourited == true) {
|
||||||
|
status.favourited = false;
|
||||||
|
} else {
|
||||||
|
status.favourited = true;
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Error', 'Something went wrong, please try again later.', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
truncate(str,lim) {
|
||||||
|
return _.truncate(str,{
|
||||||
|
length: lim
|
||||||
|
});
|
||||||
|
},
|
||||||
|
timeAgo(ts) {
|
||||||
|
let date = Date.parse(ts);
|
||||||
|
let seconds = Math.floor((new Date() - date) / 1000);
|
||||||
|
let interval = Math.floor(seconds / 31536000);
|
||||||
|
if (interval >= 1) {
|
||||||
|
return interval + "y";
|
||||||
|
}
|
||||||
|
interval = Math.floor(seconds / 604800);
|
||||||
|
if (interval >= 1) {
|
||||||
|
return interval + "w";
|
||||||
|
}
|
||||||
|
interval = Math.floor(seconds / 86400);
|
||||||
|
if (interval >= 1) {
|
||||||
|
return interval + "d";
|
||||||
|
}
|
||||||
|
interval = Math.floor(seconds / 3600);
|
||||||
|
if (interval >= 1) {
|
||||||
|
return interval + "h";
|
||||||
|
}
|
||||||
|
interval = Math.floor(seconds / 60);
|
||||||
|
if (interval >= 1) {
|
||||||
|
return interval + "m";
|
||||||
|
}
|
||||||
|
return Math.floor(seconds) + "s";
|
||||||
|
},
|
||||||
|
|
||||||
|
emojiReaction() {
|
||||||
|
let em = event.target.innerText;
|
||||||
|
if(this.replyText.length == 0) {
|
||||||
|
this.reply_to_profile_id = this.status.account.id;
|
||||||
|
this.replyText = '@' + this.status.account.username + ' ' + em;
|
||||||
|
$('textarea[name="comment"]').focus();
|
||||||
|
} else {
|
||||||
|
this.reply_to_profile_id = this.status.account.id;
|
||||||
|
this.replyText += em;
|
||||||
|
$('textarea[name="comment"]').focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleCommentVisibility() {
|
||||||
|
if(this.ownerOrAdmin() == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = this.status.comments_disabled;
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
if(state == true) {
|
||||||
|
// re-enable comments
|
||||||
|
axios.post('/i/visibility', {
|
||||||
|
item: self.status.id,
|
||||||
|
disableComments: false
|
||||||
|
}).then(function(res) {
|
||||||
|
window.location.href = self.status.url;
|
||||||
|
}).catch(function(err) {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// disable comments
|
||||||
|
axios.post('/i/visibility', {
|
||||||
|
item: self.status.id,
|
||||||
|
disableComments: true
|
||||||
|
}).then(function(res) {
|
||||||
|
self.status.comments_disabled = false;
|
||||||
|
self.showComments = false;
|
||||||
|
}).catch(function(err) {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,33 +8,56 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-4 d-flex">
|
<div class="col-12 col-md-4 d-flex">
|
||||||
<div class="profile-avatar mx-auto">
|
<div class="profile-avatar mx-md-auto">
|
||||||
<img class="rounded-circle box-shadow" :src="profile.avatar" width="172px" height="172px">
|
<div class="d-block d-md-none">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-5">
|
||||||
|
<img class="rounded-circle box-shadow mr-5" :src="profile.avatar" width="77px" height="77px">
|
||||||
|
</div>
|
||||||
|
<div class="col-7 pl-2">
|
||||||
|
<p class="font-weight-ultralight h3 mb-0">{{profile.username}}</p>
|
||||||
|
<p v-if="profile.id == user.id && user.hasOwnProperty('id')">
|
||||||
|
<a class="btn btn-outline-dark py-0 px-4 mt-3" href="/settings/home">Edit Profile</a>
|
||||||
|
</p>
|
||||||
|
<div v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||||
|
<p class="mt-3 mb-0" v-if="relationship.following == true">
|
||||||
|
<button type="button" class="btn btn-outline-dark font-weight-bold px-4 py-0" v-on:click="followProfile()" data-toggle="tooltip" title="Unfollow">Unfollow</button>
|
||||||
|
</p>
|
||||||
|
<p class="mt-3 mb-0" v-if="!relationship.following">
|
||||||
|
<button type="button" class="btn btn-outline-dark font-weight-bold px-4 py-0" v-on:click="followProfile()" data-toggle="tooltip" title="Follow">Follow</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-none d-md-block">
|
||||||
|
<img class="rounded-circle box-shadow" :src="profile.avatar" width="172px" height="172px">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-8 d-flex align-items-center">
|
<div class="col-12 col-md-8 d-flex align-items-center">
|
||||||
<div class="profile-details">
|
<div class="profile-details">
|
||||||
<div class="username-bar pb-2 d-flex align-items-center">
|
<div class="d-none d-md-flex username-bar pb-2 align-items-center">
|
||||||
<span class="font-weight-ultralight h1">{{profile.username}}</span>
|
<span class="font-weight-ultralight h3">{{profile.username}}</span>
|
||||||
<span class="pl-4" v-if="profile.is_admin">
|
<span class="pl-4" v-if="profile.is_admin">
|
||||||
<span class="btn btn-outline-secondary font-weight-bold py-0">ADMIN</span>
|
<span class="btn btn-outline-secondary font-weight-bold py-0">ADMIN</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="pl-4">
|
<span class="pl-4">
|
||||||
<a :href="'/users/'+profile.username+'.atom'" class="fas fa-rss fa-lg text-muted"></a>
|
<a :href="'/users/'+profile.username+'.atom'" class="fas fa-rss fa-lg text-muted text-decoration-none"></a>
|
||||||
</span>
|
</span>
|
||||||
<span class="pl-4" v-if="owner">
|
<span class="pl-4" v-if="owner">
|
||||||
<a class="fas fa-cog fa-lg text-muted" href="/settings/home"></a>
|
<a class="fas fa-cog fa-lg text-muted text-decoration-none" href="/settings/home"></a>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
<span v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||||
<span class="pl-4" v-if="relationship.following == true">
|
<span class="pl-4" v-if="relationship.following == true">
|
||||||
<button type="button" class="btn btn-outline-secondary font-weight-bold px-4 py-0" v-on:click="followProfile()">Unfollow</button>
|
<button type="button" class="btn btn-outline-secondary font-weight-bold btn-sm" v-on:click="followProfile()" data-toggle="tooltip" title="Unfollow"><i class="fas fa-user-minus"></i></button>
|
||||||
</span>
|
</span>
|
||||||
<span class="pl-4" v-if="!relationship.following">
|
<span class="pl-4" v-if="!relationship.following">
|
||||||
<button type="button" class="btn btn-primary font-weight-bold px-4 py-0" v-on:click="followProfile()">Follow</button>
|
<button type="button" class="btn btn-primary font-weight-bold btn-sm" v-on:click="followProfile()" data-toggle="tooltip" title="Follow"><i class="fas fa-user-plus"></i></button>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-stats pb-3 d-inline-flex lead">
|
<div class="d-none d-md-inline-flex profile-stats pb-3 lead">
|
||||||
<div class="font-weight-light pr-5">
|
<div class="font-weight-light pr-5">
|
||||||
<a class="text-dark" :href="profile.url">
|
<a class="text-dark" :href="profile.url">
|
||||||
<span class="font-weight-bold">{{profile.statuses_count}}</span>
|
<span class="font-weight-bold">{{profile.statuses_count}}</span>
|
||||||
|
@ -54,7 +77,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="lead mb-0 d-flex align-items-center">
|
<p class="lead mb-0 d-flex align-items-center pt-3">
|
||||||
<span class="font-weight-bold pr-3">{{profile.display_name}}</span>
|
<span class="font-weight-bold pr-3">{{profile.display_name}}</span>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="profile.note" class="mb-0 lead" v-html="profile.note"></div>
|
<div v-if="profile.note" class="mb-0 lead" v-html="profile.note"></div>
|
||||||
|
@ -64,22 +87,50 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="d-block d-md-none bg-white my-0 py-2 border-bottom">
|
||||||
|
<ul class="nav d-flex justify-content-center">
|
||||||
|
<li class="nav-item">
|
||||||
|
<div class="font-weight-light">
|
||||||
|
<span class="text-dark text-center">
|
||||||
|
<p class="font-weight-bold mb-0">{{profile.statuses_count}}</p>
|
||||||
|
<p class="text-muted mb-0">Posts</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item px-5">
|
||||||
|
<div v-if="profileSettings.followers.count" class="font-weight-light">
|
||||||
|
<a class="text-dark cursor-pointer text-center" v-on:click="followersModal()">
|
||||||
|
<p class="font-weight-bold mb-0">{{profile.followers_count}}</p>
|
||||||
|
<p class="text-muted mb-0">Followers</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<div v-if="profileSettings.following.count" class="font-weight-light">
|
||||||
|
<a class="text-dark cursor-pointer text-center" v-on:click="followingModal()">
|
||||||
|
<p class="font-weight-bold mb-0">{{profile.following_count}}</p>
|
||||||
|
<p class="text-muted mb-0">Following</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white">
|
||||||
<ul class="nav nav-topbar d-flex justify-content-center border-0">
|
<ul class="nav nav-topbar d-flex justify-content-center border-0">
|
||||||
<!-- <li class="nav-item">
|
<!-- <li class="nav-item">
|
||||||
<a class="nav-link active font-weight-bold text-uppercase" :href="profile.url">Posts</a>
|
<a class="nav-link active font-weight-bold text-uppercase" :href="profile.url">Posts</a>
|
||||||
</li>
|
</li>
|
||||||
-->
|
-->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a :class="this.mode == 'grid' ? 'nav-link font-weight-bold text-uppercase active' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('grid')"><i class="fas fa-th"></i></a>
|
<a :class="this.mode == 'grid' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('grid')"><i class="fas fa-th fa-lg"></i></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- <li class="nav-item">
|
<!-- <li class="nav-item">
|
||||||
<a :class="this.mode == 'masonry' ? 'nav-link font-weight-bold text-uppercase active' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('masonry')"><i class="fas fa-th-large"></i></a>
|
<a :class="this.mode == 'masonry' ? 'nav-link font-weight-bold text-uppercase active' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('masonry')"><i class="fas fa-th-large"></i></a>
|
||||||
</li> -->
|
</li> -->
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item px-3">
|
||||||
<a :class="this.mode == 'list' ? 'nav-link font-weight-bold text-uppercase active' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('list')"><i class="fas fa-th-list"></i></a>
|
<a :class="this.mode == 'list' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('list')"><i class="fas fa-th-list fa-lg"></i></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item" v-if="owner">
|
<li class="nav-item" v-if="owner">
|
||||||
|
@ -89,7 +140,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="profile-timeline mt-2 mt-md-4">
|
<div class="profile-timeline mt-md-4">
|
||||||
<div class="row" v-if="mode == 'grid'">
|
<div class="row" v-if="mode == 'grid'">
|
||||||
<div class="col-4 p-0 p-sm-2 p-md-3" v-for="(s, index) in timeline">
|
<div class="col-4 p-0 p-sm-2 p-md-3" v-for="(s, index) in timeline">
|
||||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||||
|
@ -116,8 +167,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" v-if="mode == 'list'">
|
<div class="row" v-if="mode == 'list'">
|
||||||
<div class="col-md-8 col-lg-8 offset-md-2 pt-2 px-0 my-3 timeline">
|
<div class="col-md-8 col-lg-8 offset-md-2 px-0 mb-3 timeline">
|
||||||
<div class="card mb-4 status-card card-md-rounded-0" :data-status-id="status.id" v-for="(status, index) in timeline" :key="status.id">
|
<div class="card status-card card-md-rounded-0" :data-status-id="status.id" v-for="(status, index) in timeline" :key="status.id">
|
||||||
|
|
||||||
<div class="card-header d-inline-flex align-items-center bg-white">
|
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||||
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
|
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<div v-if="!loading && !networkError" class="mt-5 row">
|
<div v-if="!loading && !networkError" class="mt-5 row">
|
||||||
|
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-weight-bold">Filters</p>
|
<p class="font-weight-bold">Filters</p>
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
|
@ -29,14 +29,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-9">
|
<div class="col-12 col-md-9">
|
||||||
<p class="h3 font-weight-lighter">Showing results for <i>{{query}}</i></p>
|
<p class="h5 font-weight-bold">Showing results for <i>{{query}}</i></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div v-if="filters.hashtags && results.hashtags.length" class="row mb-4">
|
<div v-if="filters.hashtags && results.hashtags" class="row mb-4">
|
||||||
<p class="col-12 font-weight-bold text-muted">Hashtags</p>
|
<p class="col-12 font-weight-bold text-muted">Hashtags</p>
|
||||||
<a v-for="(hashtag, index) in results.hashtags" class="col-12 col-md-4" style="text-decoration: none;" :href="hashtag.url">
|
<a v-for="(hashtag, index) in results.hashtags" class="col-12 col-md-3 mb-3" style="text-decoration: none;" :href="hashtag.url">
|
||||||
<div class="card card-body text-center">
|
<div class="card card-body text-center">
|
||||||
<p class="lead mb-0 text-truncate text-dark">
|
<p class="lead mb-0 text-truncate text-dark" data-toggle="tooltip" :title="hashtag.value">
|
||||||
#{{hashtag.value}}
|
#{{hashtag.value}}
|
||||||
</p>
|
</p>
|
||||||
<p class="lead mb-0 small font-weight-bold text-dark">
|
<p class="lead mb-0 small font-weight-bold text-dark">
|
||||||
|
@ -46,28 +46,42 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="filters.profiles && results.profiles.length" class="row mb-4">
|
<div v-if="filters.profiles && results.profiles" class="row mb-4">
|
||||||
<p class="col-12 font-weight-bold text-muted">Profiles</p>
|
<p class="col-12 font-weight-bold text-muted">Profiles</p>
|
||||||
<a v-for="(profile, index) in results.profiles" class="col-12 col-md-4" style="text-decoration: none;" :href="profile.url">
|
<a v-for="(profile, index) in results.profiles" class="col-12 col-md-4 mb-3" style="text-decoration: none;" :href="profile.url">
|
||||||
<div class="card card-body text-center border-left-primary">
|
<div class="card card-body text-center">
|
||||||
<p class="lead mb-0 text-truncate text-dark">
|
<p class="text-center">
|
||||||
|
<img :src="profile.entity.thumb" width="32px" height="32px" class="rounded-circle box-shadow">
|
||||||
|
</p>
|
||||||
|
<p class="font-weight-bold text-truncate text-dark">
|
||||||
{{profile.value}}
|
{{profile.value}}
|
||||||
</p>
|
</p>
|
||||||
|
<!-- <p class="mb-0 text-center">
|
||||||
|
<button :class="[profile.entity.following ? 'btn btn-secondary btn-sm py-1 font-weight-bold' : 'btn btn-primary btn-sm py-1 font-weight-bold']" v-on:click="followProfile(profile.entity.id)">
|
||||||
|
{{profile.entity.following ? 'Unfollow' : 'Follow'}}
|
||||||
|
</button>
|
||||||
|
</p> -->
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="filters.statuses && results.statuses.length" class="row mb-4">
|
<div v-if="filters.statuses && results.statuses" class="row mb-4">
|
||||||
<p class="col-12 font-weight-bold text-muted">Statuses</p>
|
<p class="col-12 font-weight-bold text-muted">Statuses</p>
|
||||||
<a v-for="(status, index) in results.statuses" class="col-12 col-md-4" style="text-decoration: none;" :href="status.url">
|
<a v-for="(status, index) in results.statuses" class="col-12 col-md-4 mb-3" style="text-decoration: none;" :href="status.url">
|
||||||
<div class="card card-body text-center border-left-primary">
|
<div class="card">
|
||||||
<p class="lead mb-0 text-truncate text-dark">
|
<img class="card-img-top img-fluid" :src="status.thumb" style="height:180px;">
|
||||||
{{status.value}}
|
<div class="card-body text-center ">
|
||||||
</p>
|
<p class="mb-0 small text-truncate font-weight-bold text-muted" v-html="status.value">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!results.hashtags && !results.profiles && !results.statuses">
|
||||||
|
<p class="text-center lead">No results found!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +95,7 @@
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
export default {
|
export default {
|
||||||
props: ['query'],
|
props: ['query', 'profileId'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -103,31 +117,35 @@ export default {
|
||||||
this.fetchSearchResults();
|
this.fetchSearchResults();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
$('.search-form input').val(this.query);
|
$('.search-bar input').val(this.query);
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchSearchResults() {
|
fetchSearchResults() {
|
||||||
axios.get('/api/search/' + this.query)
|
axios.get('/api/search/' + encodeURI(this.query))
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let results = res.data;
|
let results = res.data;
|
||||||
this.results.hashtags = results.filter(i => {
|
this.results.hashtags = results.hashtags;
|
||||||
return i.type == 'hashtag';
|
this.results.profiles = results.profiles;
|
||||||
});
|
this.results.statuses = results.posts;
|
||||||
this.results.profiles = results.filter(i => {
|
|
||||||
return i.type == 'profile';
|
|
||||||
});
|
|
||||||
this.results.statuses = results.filter(i => {
|
|
||||||
return i.type == 'status';
|
|
||||||
});
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.networkError = true;
|
// this.networkError = true;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
followProfile(id) {
|
||||||
|
// todo: finish AP Accept handling to enable remote follows
|
||||||
|
return;
|
||||||
|
// axios.post('/i/follow', {
|
||||||
|
// item: id
|
||||||
|
// }).then(res => {
|
||||||
|
// window.location.href = window.location.href;
|
||||||
|
// });
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
71
resources/assets/js/components/searchform.js
vendored
71
resources/assets/js/components/searchform.js
vendored
|
@ -1,71 +0,0 @@
|
||||||
$(document).ready(function() {
|
|
||||||
|
|
||||||
let queryEngine = new Bloodhound({
|
|
||||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
remote: {
|
|
||||||
url: process.env.MIX_API_SEARCH + '/%QUERY%',
|
|
||||||
wildcard: '%QUERY%'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.search-form .search-form-input').typeahead(null, {
|
|
||||||
name: 'search',
|
|
||||||
display: 'value',
|
|
||||||
source: queryEngine,
|
|
||||||
limit: 40,
|
|
||||||
templates: {
|
|
||||||
empty: [
|
|
||||||
'<div class="alert alert-info mb-0 font-weight-bold">',
|
|
||||||
'No Results Found',
|
|
||||||
'</div>'
|
|
||||||
].join('\n'),
|
|
||||||
suggestion: function(data) {
|
|
||||||
let type = data.type;
|
|
||||||
let res = false;
|
|
||||||
switch(type) {
|
|
||||||
case 'hashtag':
|
|
||||||
res = '<a href="'+data.url+'?src=search">' +
|
|
||||||
'<div class="media d-flex align-items-center">' +
|
|
||||||
'<div class="mr-3 h4 text-muted"><span class="fas fa-hashtag"></span></div>' +
|
|
||||||
'<div class="media-body text-truncate">' +
|
|
||||||
'<p class="mt-0 mb-0 font-weight-bold">'+data.value+'</p>' +
|
|
||||||
'<p class="text-muted mb-0">'+data.count+' posts</p>' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>' +
|
|
||||||
'</a>';
|
|
||||||
break;
|
|
||||||
case 'profile':
|
|
||||||
res = '<a href="'+data.url+'?src=search">' +
|
|
||||||
'<div class="media d-flex align-items-center">' +
|
|
||||||
'<div class="mr-3 h4 text-muted"><span class="far fa-user"></span></div>' +
|
|
||||||
'<div class="media-body text-truncate">' +
|
|
||||||
'<p class="mt-0 mb-0 font-weight-bold">'+data.name+'</p>' +
|
|
||||||
'<p class="text-muted mb-0">'+data.value+'</p>' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>' +
|
|
||||||
'</a>';
|
|
||||||
break;
|
|
||||||
case 'status':
|
|
||||||
res = '<a href="'+data.url+'?src=search">' +
|
|
||||||
'<div class="media d-flex align-items-center">' +
|
|
||||||
'<div class="mr-3 h4 text-muted"><img src="'+data.thumb+'" width="32px"></div>' +
|
|
||||||
'<div class="media-body text-truncate">' +
|
|
||||||
'<p class="mt-0 mb-0 font-weight-bold">'+data.name+'</p>' +
|
|
||||||
'<p class="text-muted mb-0 small">'+data.value+'</p>' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>' +
|
|
||||||
'</a>';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(res !== false) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
10
resources/assets/js/landing.js
vendored
10
resources/assets/js/landing.js
vendored
|
@ -1,10 +0,0 @@
|
||||||
window.Vue = require('vue');
|
|
||||||
|
|
||||||
Vue.component(
|
|
||||||
'landing-page',
|
|
||||||
require('./components/LandingPage.vue').default
|
|
||||||
);
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#content'
|
|
||||||
});
|
|
952
resources/assets/js/lib/bloodhound.js
vendored
952
resources/assets/js/lib/bloodhound.js
vendored
|
@ -1,952 +0,0 @@
|
||||||
/*!
|
|
||||||
* typeahead.js 1.2.0
|
|
||||||
* https://github.com/twitter/typeahead.js
|
|
||||||
* Copyright 2013-2017 Twitter, Inc. and other contributors; Licensed MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function(root, factory) {
|
|
||||||
if (typeof define === "function" && define.amd) {
|
|
||||||
define([ "jquery" ], function(a0) {
|
|
||||||
return root["Bloodhound"] = factory(a0);
|
|
||||||
});
|
|
||||||
} else if (typeof exports === "object") {
|
|
||||||
module.exports = factory(require("jquery"));
|
|
||||||
} else {
|
|
||||||
root["Bloodhound"] = factory(root["jQuery"]);
|
|
||||||
}
|
|
||||||
})(this, function($) {
|
|
||||||
var _ = function() {
|
|
||||||
"use strict";
|
|
||||||
return {
|
|
||||||
isMsie: function() {
|
|
||||||
return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
|
|
||||||
},
|
|
||||||
isBlankString: function(str) {
|
|
||||||
return !str || /^\s*$/.test(str);
|
|
||||||
},
|
|
||||||
escapeRegExChars: function(str) {
|
|
||||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
||||||
},
|
|
||||||
isString: function(obj) {
|
|
||||||
return typeof obj === "string";
|
|
||||||
},
|
|
||||||
isNumber: function(obj) {
|
|
||||||
return typeof obj === "number";
|
|
||||||
},
|
|
||||||
isArray: $.isArray,
|
|
||||||
isFunction: $.isFunction,
|
|
||||||
isObject: $.isPlainObject,
|
|
||||||
isUndefined: function(obj) {
|
|
||||||
return typeof obj === "undefined";
|
|
||||||
},
|
|
||||||
isElement: function(obj) {
|
|
||||||
return !!(obj && obj.nodeType === 1);
|
|
||||||
},
|
|
||||||
isJQuery: function(obj) {
|
|
||||||
return obj instanceof $;
|
|
||||||
},
|
|
||||||
toStr: function toStr(s) {
|
|
||||||
return _.isUndefined(s) || s === null ? "" : s + "";
|
|
||||||
},
|
|
||||||
bind: $.proxy,
|
|
||||||
each: function(collection, cb) {
|
|
||||||
$.each(collection, reverseArgs);
|
|
||||||
function reverseArgs(index, value) {
|
|
||||||
return cb(value, index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
map: $.map,
|
|
||||||
filter: $.grep,
|
|
||||||
every: function(obj, test) {
|
|
||||||
var result = true;
|
|
||||||
if (!obj) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
$.each(obj, function(key, val) {
|
|
||||||
if (!(result = test.call(null, val, key, obj))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return !!result;
|
|
||||||
},
|
|
||||||
some: function(obj, test) {
|
|
||||||
var result = false;
|
|
||||||
if (!obj) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
$.each(obj, function(key, val) {
|
|
||||||
if (result = test.call(null, val, key, obj)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return !!result;
|
|
||||||
},
|
|
||||||
mixin: $.extend,
|
|
||||||
identity: function(x) {
|
|
||||||
return x;
|
|
||||||
},
|
|
||||||
clone: function(obj) {
|
|
||||||
return $.extend(true, {}, obj);
|
|
||||||
},
|
|
||||||
getIdGenerator: function() {
|
|
||||||
var counter = 0;
|
|
||||||
return function() {
|
|
||||||
return counter++;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
templatify: function templatify(obj) {
|
|
||||||
return $.isFunction(obj) ? obj : template;
|
|
||||||
function template() {
|
|
||||||
return String(obj);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defer: function(fn) {
|
|
||||||
setTimeout(fn, 0);
|
|
||||||
},
|
|
||||||
debounce: function(func, wait, immediate) {
|
|
||||||
var timeout, result;
|
|
||||||
return function() {
|
|
||||||
var context = this, args = arguments, later, callNow;
|
|
||||||
later = function() {
|
|
||||||
timeout = null;
|
|
||||||
if (!immediate) {
|
|
||||||
result = func.apply(context, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
callNow = immediate && !timeout;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(later, wait);
|
|
||||||
if (callNow) {
|
|
||||||
result = func.apply(context, args);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
throttle: function(func, wait) {
|
|
||||||
var context, args, timeout, result, previous, later;
|
|
||||||
previous = 0;
|
|
||||||
later = function() {
|
|
||||||
previous = new Date();
|
|
||||||
timeout = null;
|
|
||||||
result = func.apply(context, args);
|
|
||||||
};
|
|
||||||
return function() {
|
|
||||||
var now = new Date(), remaining = wait - (now - previous);
|
|
||||||
context = this;
|
|
||||||
args = arguments;
|
|
||||||
if (remaining <= 0) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = null;
|
|
||||||
previous = now;
|
|
||||||
result = func.apply(context, args);
|
|
||||||
} else if (!timeout) {
|
|
||||||
timeout = setTimeout(later, remaining);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
stringify: function(val) {
|
|
||||||
return _.isString(val) ? val : JSON.stringify(val);
|
|
||||||
},
|
|
||||||
guid: function() {
|
|
||||||
function _p8(s) {
|
|
||||||
var p = (Math.random().toString(16) + "000000000").substr(2, 8);
|
|
||||||
return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p;
|
|
||||||
}
|
|
||||||
return "tt-" + _p8() + _p8(true) + _p8(true) + _p8();
|
|
||||||
},
|
|
||||||
noop: function() {}
|
|
||||||
};
|
|
||||||
}();
|
|
||||||
var VERSION = "1.2.0";
|
|
||||||
var tokenizers = function() {
|
|
||||||
"use strict";
|
|
||||||
return {
|
|
||||||
nonword: nonword,
|
|
||||||
whitespace: whitespace,
|
|
||||||
ngram: ngram,
|
|
||||||
obj: {
|
|
||||||
nonword: getObjTokenizer(nonword),
|
|
||||||
whitespace: getObjTokenizer(whitespace),
|
|
||||||
ngram: getObjTokenizer(ngram)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function whitespace(str) {
|
|
||||||
str = _.toStr(str);
|
|
||||||
return str ? str.split(/\s+/) : [];
|
|
||||||
}
|
|
||||||
function nonword(str) {
|
|
||||||
str = _.toStr(str);
|
|
||||||
return str ? str.split(/\W+/) : [];
|
|
||||||
}
|
|
||||||
function ngram(str) {
|
|
||||||
str = _.toStr(str);
|
|
||||||
var tokens = [], word = "";
|
|
||||||
_.each(str.split(""), function(char) {
|
|
||||||
if (char.match(/\s+/)) {
|
|
||||||
word = "";
|
|
||||||
} else {
|
|
||||||
tokens.push(word + char);
|
|
||||||
word += char;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
function getObjTokenizer(tokenizer) {
|
|
||||||
return function setKey(keys) {
|
|
||||||
keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0);
|
|
||||||
return function tokenize(o) {
|
|
||||||
var tokens = [];
|
|
||||||
_.each(keys, function(k) {
|
|
||||||
tokens = tokens.concat(tokenizer(_.toStr(o[k])));
|
|
||||||
});
|
|
||||||
return tokens;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
var LruCache = function() {
|
|
||||||
"use strict";
|
|
||||||
function LruCache(maxSize) {
|
|
||||||
this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
|
|
||||||
this.reset();
|
|
||||||
if (this.maxSize <= 0) {
|
|
||||||
this.set = this.get = $.noop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_.mixin(LruCache.prototype, {
|
|
||||||
set: function set(key, val) {
|
|
||||||
var tailItem = this.list.tail, node;
|
|
||||||
if (this.size >= this.maxSize) {
|
|
||||||
this.list.remove(tailItem);
|
|
||||||
delete this.hash[tailItem.key];
|
|
||||||
this.size--;
|
|
||||||
}
|
|
||||||
if (node = this.hash[key]) {
|
|
||||||
node.val = val;
|
|
||||||
this.list.moveToFront(node);
|
|
||||||
} else {
|
|
||||||
node = new Node(key, val);
|
|
||||||
this.list.add(node);
|
|
||||||
this.hash[key] = node;
|
|
||||||
this.size++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get: function get(key) {
|
|
||||||
var node = this.hash[key];
|
|
||||||
if (node) {
|
|
||||||
this.list.moveToFront(node);
|
|
||||||
return node.val;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
reset: function reset() {
|
|
||||||
this.size = 0;
|
|
||||||
this.hash = {};
|
|
||||||
this.list = new List();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
function List() {
|
|
||||||
this.head = this.tail = null;
|
|
||||||
}
|
|
||||||
_.mixin(List.prototype, {
|
|
||||||
add: function add(node) {
|
|
||||||
if (this.head) {
|
|
||||||
node.next = this.head;
|
|
||||||
this.head.prev = node;
|
|
||||||
}
|
|
||||||
this.head = node;
|
|
||||||
this.tail = this.tail || node;
|
|
||||||
},
|
|
||||||
remove: function remove(node) {
|
|
||||||
node.prev ? node.prev.next = node.next : this.head = node.next;
|
|
||||||
node.next ? node.next.prev = node.prev : this.tail = node.prev;
|
|
||||||
},
|
|
||||||
moveToFront: function(node) {
|
|
||||||
this.remove(node);
|
|
||||||
this.add(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
function Node(key, val) {
|
|
||||||
this.key = key;
|
|
||||||
this.val = val;
|
|
||||||
this.prev = this.next = null;
|
|
||||||
}
|
|
||||||
return LruCache;
|
|
||||||
}();
|
|
||||||
var PersistentStorage = function() {
|
|
||||||
"use strict";
|
|
||||||
var LOCAL_STORAGE;
|
|
||||||
try {
|
|
||||||
LOCAL_STORAGE = window.localStorage;
|
|
||||||
LOCAL_STORAGE.setItem("~~~", "!");
|
|
||||||
LOCAL_STORAGE.removeItem("~~~");
|
|
||||||
} catch (err) {
|
|
||||||
LOCAL_STORAGE = null;
|
|
||||||
}
|
|
||||||
function PersistentStorage(namespace, override) {
|
|
||||||
this.prefix = [ "__", namespace, "__" ].join("");
|
|
||||||
this.ttlKey = "__ttl__";
|
|
||||||
this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix));
|
|
||||||
this.ls = override || LOCAL_STORAGE;
|
|
||||||
!this.ls && this._noop();
|
|
||||||
}
|
|
||||||
_.mixin(PersistentStorage.prototype, {
|
|
||||||
_prefix: function(key) {
|
|
||||||
return this.prefix + key;
|
|
||||||
},
|
|
||||||
_ttlKey: function(key) {
|
|
||||||
return this._prefix(key) + this.ttlKey;
|
|
||||||
},
|
|
||||||
_noop: function() {
|
|
||||||
this.get = this.set = this.remove = this.clear = this.isExpired = _.noop;
|
|
||||||
},
|
|
||||||
_safeSet: function(key, val) {
|
|
||||||
try {
|
|
||||||
this.ls.setItem(key, val);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.name === "QuotaExceededError") {
|
|
||||||
this.clear();
|
|
||||||
this._noop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get: function(key) {
|
|
||||||
if (this.isExpired(key)) {
|
|
||||||
this.remove(key);
|
|
||||||
}
|
|
||||||
return decode(this.ls.getItem(this._prefix(key)));
|
|
||||||
},
|
|
||||||
set: function(key, val, ttl) {
|
|
||||||
if (_.isNumber(ttl)) {
|
|
||||||
this._safeSet(this._ttlKey(key), encode(now() + ttl));
|
|
||||||
} else {
|
|
||||||
this.ls.removeItem(this._ttlKey(key));
|
|
||||||
}
|
|
||||||
return this._safeSet(this._prefix(key), encode(val));
|
|
||||||
},
|
|
||||||
remove: function(key) {
|
|
||||||
this.ls.removeItem(this._ttlKey(key));
|
|
||||||
this.ls.removeItem(this._prefix(key));
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
clear: function() {
|
|
||||||
var i, keys = gatherMatchingKeys(this.keyMatcher);
|
|
||||||
for (i = keys.length; i--; ) {
|
|
||||||
this.remove(keys[i]);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
isExpired: function(key) {
|
|
||||||
var ttl = decode(this.ls.getItem(this._ttlKey(key)));
|
|
||||||
return _.isNumber(ttl) && now() > ttl ? true : false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return PersistentStorage;
|
|
||||||
function now() {
|
|
||||||
return new Date().getTime();
|
|
||||||
}
|
|
||||||
function encode(val) {
|
|
||||||
return JSON.stringify(_.isUndefined(val) ? null : val);
|
|
||||||
}
|
|
||||||
function decode(val) {
|
|
||||||
return $.parseJSON(val);
|
|
||||||
}
|
|
||||||
function gatherMatchingKeys(keyMatcher) {
|
|
||||||
var i, key, keys = [], len = LOCAL_STORAGE.length;
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) {
|
|
||||||
keys.push(key.replace(keyMatcher, ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
var Transport = function() {
|
|
||||||
"use strict";
|
|
||||||
var pendingRequestsCount = 0, pendingRequests = {}, sharedCache = new LruCache(10);
|
|
||||||
function Transport(o) {
|
|
||||||
o = o || {};
|
|
||||||
this.maxPendingRequests = o.maxPendingRequests || 6;
|
|
||||||
this.cancelled = false;
|
|
||||||
this.lastReq = null;
|
|
||||||
this._send = o.transport;
|
|
||||||
this._get = o.limiter ? o.limiter(this._get) : this._get;
|
|
||||||
this._cache = o.cache === false ? new LruCache(0) : sharedCache;
|
|
||||||
}
|
|
||||||
Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
|
|
||||||
this.maxPendingRequests = num;
|
|
||||||
};
|
|
||||||
Transport.resetCache = function resetCache() {
|
|
||||||
sharedCache.reset();
|
|
||||||
};
|
|
||||||
_.mixin(Transport.prototype, {
|
|
||||||
_fingerprint: function fingerprint(o) {
|
|
||||||
o = o || {};
|
|
||||||
return o.url + o.type + $.param(o.data || {});
|
|
||||||
},
|
|
||||||
_get: function(o, cb) {
|
|
||||||
var that = this, fingerprint, jqXhr;
|
|
||||||
fingerprint = this._fingerprint(o);
|
|
||||||
if (this.cancelled || fingerprint !== this.lastReq) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (jqXhr = pendingRequests[fingerprint]) {
|
|
||||||
jqXhr.done(done).fail(fail);
|
|
||||||
} else if (pendingRequestsCount < this.maxPendingRequests) {
|
|
||||||
pendingRequestsCount++;
|
|
||||||
pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always);
|
|
||||||
} else {
|
|
||||||
this.onDeckRequestArgs = [].slice.call(arguments, 0);
|
|
||||||
}
|
|
||||||
function done(resp) {
|
|
||||||
cb(null, resp);
|
|
||||||
that._cache.set(fingerprint, resp);
|
|
||||||
}
|
|
||||||
function fail() {
|
|
||||||
cb(true);
|
|
||||||
}
|
|
||||||
function always() {
|
|
||||||
pendingRequestsCount--;
|
|
||||||
delete pendingRequests[fingerprint];
|
|
||||||
if (that.onDeckRequestArgs) {
|
|
||||||
that._get.apply(that, that.onDeckRequestArgs);
|
|
||||||
that.onDeckRequestArgs = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get: function(o, cb) {
|
|
||||||
var resp, fingerprint;
|
|
||||||
cb = cb || $.noop;
|
|
||||||
o = _.isString(o) ? {
|
|
||||||
url: o
|
|
||||||
} : o || {};
|
|
||||||
fingerprint = this._fingerprint(o);
|
|
||||||
this.cancelled = false;
|
|
||||||
this.lastReq = fingerprint;
|
|
||||||
if (resp = this._cache.get(fingerprint)) {
|
|
||||||
cb(null, resp);
|
|
||||||
} else {
|
|
||||||
this._get(o, cb);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancel: function() {
|
|
||||||
this.cancelled = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Transport;
|
|
||||||
}();
|
|
||||||
var SearchIndex = window.SearchIndex = function() {
|
|
||||||
"use strict";
|
|
||||||
var CHILDREN = "c", IDS = "i";
|
|
||||||
function SearchIndex(o) {
|
|
||||||
o = o || {};
|
|
||||||
if (!o.datumTokenizer || !o.queryTokenizer) {
|
|
||||||
$.error("datumTokenizer and queryTokenizer are both required");
|
|
||||||
}
|
|
||||||
this.identify = o.identify || _.stringify;
|
|
||||||
this.datumTokenizer = o.datumTokenizer;
|
|
||||||
this.queryTokenizer = o.queryTokenizer;
|
|
||||||
this.matchAnyQueryToken = o.matchAnyQueryToken;
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
_.mixin(SearchIndex.prototype, {
|
|
||||||
bootstrap: function bootstrap(o) {
|
|
||||||
this.datums = o.datums;
|
|
||||||
this.trie = o.trie;
|
|
||||||
},
|
|
||||||
add: function(data) {
|
|
||||||
var that = this;
|
|
||||||
data = _.isArray(data) ? data : [ data ];
|
|
||||||
_.each(data, function(datum) {
|
|
||||||
var id, tokens;
|
|
||||||
that.datums[id = that.identify(datum)] = datum;
|
|
||||||
tokens = normalizeTokens(that.datumTokenizer(datum));
|
|
||||||
_.each(tokens, function(token) {
|
|
||||||
var node, chars, ch;
|
|
||||||
node = that.trie;
|
|
||||||
chars = token.split("");
|
|
||||||
while (ch = chars.shift()) {
|
|
||||||
node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode());
|
|
||||||
node[IDS].push(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get: function get(ids) {
|
|
||||||
var that = this;
|
|
||||||
return _.map(ids, function(id) {
|
|
||||||
return that.datums[id];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
search: function search(query) {
|
|
||||||
var that = this, tokens, matches;
|
|
||||||
tokens = normalizeTokens(this.queryTokenizer(query));
|
|
||||||
_.each(tokens, function(token) {
|
|
||||||
var node, chars, ch, ids;
|
|
||||||
if (matches && matches.length === 0 && !that.matchAnyQueryToken) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
node = that.trie;
|
|
||||||
chars = token.split("");
|
|
||||||
while (node && (ch = chars.shift())) {
|
|
||||||
node = node[CHILDREN][ch];
|
|
||||||
}
|
|
||||||
if (node && chars.length === 0) {
|
|
||||||
ids = node[IDS].slice(0);
|
|
||||||
matches = matches ? getIntersection(matches, ids) : ids;
|
|
||||||
} else {
|
|
||||||
if (!that.matchAnyQueryToken) {
|
|
||||||
matches = [];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return matches ? _.map(unique(matches), function(id) {
|
|
||||||
return that.datums[id];
|
|
||||||
}) : [];
|
|
||||||
},
|
|
||||||
all: function all() {
|
|
||||||
var values = [];
|
|
||||||
for (var key in this.datums) {
|
|
||||||
values.push(this.datums[key]);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
},
|
|
||||||
reset: function reset() {
|
|
||||||
this.datums = {};
|
|
||||||
this.trie = newNode();
|
|
||||||
},
|
|
||||||
serialize: function serialize() {
|
|
||||||
return {
|
|
||||||
datums: this.datums,
|
|
||||||
trie: this.trie
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return SearchIndex;
|
|
||||||
function normalizeTokens(tokens) {
|
|
||||||
tokens = _.filter(tokens, function(token) {
|
|
||||||
return !!token;
|
|
||||||
});
|
|
||||||
tokens = _.map(tokens, function(token) {
|
|
||||||
return token.toLowerCase();
|
|
||||||
});
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
function newNode() {
|
|
||||||
var node = {};
|
|
||||||
node[IDS] = [];
|
|
||||||
node[CHILDREN] = {};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
function unique(array) {
|
|
||||||
var seen = {}, uniques = [];
|
|
||||||
for (var i = 0, len = array.length; i < len; i++) {
|
|
||||||
if (!seen[array[i]]) {
|
|
||||||
seen[array[i]] = true;
|
|
||||||
uniques.push(array[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uniques;
|
|
||||||
}
|
|
||||||
function getIntersection(arrayA, arrayB) {
|
|
||||||
var ai = 0, bi = 0, intersection = [];
|
|
||||||
arrayA = arrayA.sort();
|
|
||||||
arrayB = arrayB.sort();
|
|
||||||
var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
|
|
||||||
while (ai < lenArrayA && bi < lenArrayB) {
|
|
||||||
if (arrayA[ai] < arrayB[bi]) {
|
|
||||||
ai++;
|
|
||||||
} else if (arrayA[ai] > arrayB[bi]) {
|
|
||||||
bi++;
|
|
||||||
} else {
|
|
||||||
intersection.push(arrayA[ai]);
|
|
||||||
ai++;
|
|
||||||
bi++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return intersection;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
var Prefetch = function() {
|
|
||||||
"use strict";
|
|
||||||
var keys;
|
|
||||||
keys = {
|
|
||||||
data: "data",
|
|
||||||
protocol: "protocol",
|
|
||||||
thumbprint: "thumbprint"
|
|
||||||
};
|
|
||||||
function Prefetch(o) {
|
|
||||||
this.url = o.url;
|
|
||||||
this.ttl = o.ttl;
|
|
||||||
this.cache = o.cache;
|
|
||||||
this.prepare = o.prepare;
|
|
||||||
this.transform = o.transform;
|
|
||||||
this.transport = o.transport;
|
|
||||||
this.thumbprint = o.thumbprint;
|
|
||||||
this.storage = new PersistentStorage(o.cacheKey);
|
|
||||||
}
|
|
||||||
_.mixin(Prefetch.prototype, {
|
|
||||||
_settings: function settings() {
|
|
||||||
return {
|
|
||||||
url: this.url,
|
|
||||||
type: "GET",
|
|
||||||
dataType: "json"
|
|
||||||
};
|
|
||||||
},
|
|
||||||
store: function store(data) {
|
|
||||||
if (!this.cache) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.storage.set(keys.data, data, this.ttl);
|
|
||||||
this.storage.set(keys.protocol, location.protocol, this.ttl);
|
|
||||||
this.storage.set(keys.thumbprint, this.thumbprint, this.ttl);
|
|
||||||
},
|
|
||||||
fromCache: function fromCache() {
|
|
||||||
var stored = {}, isExpired;
|
|
||||||
if (!this.cache) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
stored.data = this.storage.get(keys.data);
|
|
||||||
stored.protocol = this.storage.get(keys.protocol);
|
|
||||||
stored.thumbprint = this.storage.get(keys.thumbprint);
|
|
||||||
isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol;
|
|
||||||
return stored.data && !isExpired ? stored.data : null;
|
|
||||||
},
|
|
||||||
fromNetwork: function(cb) {
|
|
||||||
var that = this, settings;
|
|
||||||
if (!cb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settings = this.prepare(this._settings());
|
|
||||||
this.transport(settings).fail(onError).done(onResponse);
|
|
||||||
function onError() {
|
|
||||||
cb(true);
|
|
||||||
}
|
|
||||||
function onResponse(resp) {
|
|
||||||
cb(null, that.transform(resp));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clear: function clear() {
|
|
||||||
this.storage.clear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Prefetch;
|
|
||||||
}();
|
|
||||||
var Remote = function() {
|
|
||||||
"use strict";
|
|
||||||
function Remote(o) {
|
|
||||||
this.url = o.url;
|
|
||||||
this.prepare = o.prepare;
|
|
||||||
this.transform = o.transform;
|
|
||||||
this.indexResponse = o.indexResponse;
|
|
||||||
this.transport = new Transport({
|
|
||||||
cache: o.cache,
|
|
||||||
limiter: o.limiter,
|
|
||||||
transport: o.transport,
|
|
||||||
maxPendingRequests: o.maxPendingRequests
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_.mixin(Remote.prototype, {
|
|
||||||
_settings: function settings() {
|
|
||||||
return {
|
|
||||||
url: this.url,
|
|
||||||
type: "GET",
|
|
||||||
dataType: "json"
|
|
||||||
};
|
|
||||||
},
|
|
||||||
get: function get(query, cb) {
|
|
||||||
var that = this, settings;
|
|
||||||
if (!cb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
query = query || "";
|
|
||||||
settings = this.prepare(query, this._settings());
|
|
||||||
return this.transport.get(settings, onResponse);
|
|
||||||
function onResponse(err, resp) {
|
|
||||||
err ? cb([]) : cb(that.transform(resp));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancelLastRequest: function cancelLastRequest() {
|
|
||||||
this.transport.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Remote;
|
|
||||||
}();
|
|
||||||
var oParser = function() {
|
|
||||||
"use strict";
|
|
||||||
return function parse(o) {
|
|
||||||
var defaults, sorter;
|
|
||||||
defaults = {
|
|
||||||
initialize: true,
|
|
||||||
identify: _.stringify,
|
|
||||||
datumTokenizer: null,
|
|
||||||
queryTokenizer: null,
|
|
||||||
matchAnyQueryToken: false,
|
|
||||||
sufficient: 5,
|
|
||||||
indexRemote: false,
|
|
||||||
sorter: null,
|
|
||||||
local: [],
|
|
||||||
prefetch: null,
|
|
||||||
remote: null
|
|
||||||
};
|
|
||||||
o = _.mixin(defaults, o || {});
|
|
||||||
!o.datumTokenizer && $.error("datumTokenizer is required");
|
|
||||||
!o.queryTokenizer && $.error("queryTokenizer is required");
|
|
||||||
sorter = o.sorter;
|
|
||||||
o.sorter = sorter ? function(x) {
|
|
||||||
return x.sort(sorter);
|
|
||||||
} : _.identity;
|
|
||||||
o.local = _.isFunction(o.local) ? o.local() : o.local;
|
|
||||||
o.prefetch = parsePrefetch(o.prefetch);
|
|
||||||
o.remote = parseRemote(o.remote);
|
|
||||||
return o;
|
|
||||||
};
|
|
||||||
function parsePrefetch(o) {
|
|
||||||
var defaults;
|
|
||||||
if (!o) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
defaults = {
|
|
||||||
url: null,
|
|
||||||
ttl: 24 * 60 * 60 * 1e3,
|
|
||||||
cache: true,
|
|
||||||
cacheKey: null,
|
|
||||||
thumbprint: "",
|
|
||||||
prepare: _.identity,
|
|
||||||
transform: _.identity,
|
|
||||||
transport: null
|
|
||||||
};
|
|
||||||
o = _.isString(o) ? {
|
|
||||||
url: o
|
|
||||||
} : o;
|
|
||||||
o = _.mixin(defaults, o);
|
|
||||||
!o.url && $.error("prefetch requires url to be set");
|
|
||||||
o.transform = o.filter || o.transform;
|
|
||||||
o.cacheKey = o.cacheKey || o.url;
|
|
||||||
o.thumbprint = VERSION + o.thumbprint;
|
|
||||||
o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
function parseRemote(o) {
|
|
||||||
var defaults;
|
|
||||||
if (!o) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
defaults = {
|
|
||||||
url: null,
|
|
||||||
cache: true,
|
|
||||||
prepare: null,
|
|
||||||
replace: null,
|
|
||||||
wildcard: null,
|
|
||||||
limiter: null,
|
|
||||||
rateLimitBy: "debounce",
|
|
||||||
rateLimitWait: 300,
|
|
||||||
transform: _.identity,
|
|
||||||
transport: null
|
|
||||||
};
|
|
||||||
o = _.isString(o) ? {
|
|
||||||
url: o
|
|
||||||
} : o;
|
|
||||||
o = _.mixin(defaults, o);
|
|
||||||
!o.url && $.error("remote requires url to be set");
|
|
||||||
o.transform = o.filter || o.transform;
|
|
||||||
o.prepare = toRemotePrepare(o);
|
|
||||||
o.limiter = toLimiter(o);
|
|
||||||
o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax;
|
|
||||||
delete o.replace;
|
|
||||||
delete o.wildcard;
|
|
||||||
delete o.rateLimitBy;
|
|
||||||
delete o.rateLimitWait;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
function toRemotePrepare(o) {
|
|
||||||
var prepare, replace, wildcard;
|
|
||||||
prepare = o.prepare;
|
|
||||||
replace = o.replace;
|
|
||||||
wildcard = o.wildcard;
|
|
||||||
if (prepare) {
|
|
||||||
return prepare;
|
|
||||||
}
|
|
||||||
if (replace) {
|
|
||||||
prepare = prepareByReplace;
|
|
||||||
} else if (o.wildcard) {
|
|
||||||
prepare = prepareByWildcard;
|
|
||||||
} else {
|
|
||||||
prepare = identityPrepare;
|
|
||||||
}
|
|
||||||
return prepare;
|
|
||||||
function prepareByReplace(query, settings) {
|
|
||||||
settings.url = replace(settings.url, query);
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
function prepareByWildcard(query, settings) {
|
|
||||||
settings.url = settings.url.replace(wildcard, encodeURIComponent(query));
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
function identityPrepare(query, settings) {
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function toLimiter(o) {
|
|
||||||
var limiter, method, wait;
|
|
||||||
limiter = o.limiter;
|
|
||||||
method = o.rateLimitBy;
|
|
||||||
wait = o.rateLimitWait;
|
|
||||||
if (!limiter) {
|
|
||||||
limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait);
|
|
||||||
}
|
|
||||||
return limiter;
|
|
||||||
function debounce(wait) {
|
|
||||||
return function debounce(fn) {
|
|
||||||
return _.debounce(fn, wait);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function throttle(wait) {
|
|
||||||
return function throttle(fn) {
|
|
||||||
return _.throttle(fn, wait);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function callbackToDeferred(fn) {
|
|
||||||
return function wrapper(o) {
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
fn(o, onSuccess, onError);
|
|
||||||
return deferred;
|
|
||||||
function onSuccess(resp) {
|
|
||||||
_.defer(function() {
|
|
||||||
deferred.resolve(resp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function onError(err) {
|
|
||||||
_.defer(function() {
|
|
||||||
deferred.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
var Bloodhound = function() {
|
|
||||||
"use strict";
|
|
||||||
var old;
|
|
||||||
old = window && window.Bloodhound;
|
|
||||||
function Bloodhound(o) {
|
|
||||||
o = oParser(o);
|
|
||||||
this.sorter = o.sorter;
|
|
||||||
this.identify = o.identify;
|
|
||||||
this.sufficient = o.sufficient;
|
|
||||||
this.indexRemote = o.indexRemote;
|
|
||||||
this.local = o.local;
|
|
||||||
this.remote = o.remote ? new Remote(o.remote) : null;
|
|
||||||
this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null;
|
|
||||||
this.index = new SearchIndex({
|
|
||||||
identify: this.identify,
|
|
||||||
datumTokenizer: o.datumTokenizer,
|
|
||||||
queryTokenizer: o.queryTokenizer
|
|
||||||
});
|
|
||||||
o.initialize !== false && this.initialize();
|
|
||||||
}
|
|
||||||
Bloodhound.noConflict = function noConflict() {
|
|
||||||
window && (window.Bloodhound = old);
|
|
||||||
return Bloodhound;
|
|
||||||
};
|
|
||||||
Bloodhound.tokenizers = tokenizers;
|
|
||||||
_.mixin(Bloodhound.prototype, {
|
|
||||||
__ttAdapter: function ttAdapter() {
|
|
||||||
var that = this;
|
|
||||||
return this.remote ? withAsync : withoutAsync;
|
|
||||||
function withAsync(query, sync, async) {
|
|
||||||
return that.search(query, sync, async);
|
|
||||||
}
|
|
||||||
function withoutAsync(query, sync) {
|
|
||||||
return that.search(query, sync);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_loadPrefetch: function loadPrefetch() {
|
|
||||||
var that = this, deferred, serialized;
|
|
||||||
deferred = $.Deferred();
|
|
||||||
if (!this.prefetch) {
|
|
||||||
deferred.resolve();
|
|
||||||
} else if (serialized = this.prefetch.fromCache()) {
|
|
||||||
this.index.bootstrap(serialized);
|
|
||||||
deferred.resolve();
|
|
||||||
} else {
|
|
||||||
this.prefetch.fromNetwork(done);
|
|
||||||
}
|
|
||||||
return deferred.promise();
|
|
||||||
function done(err, data) {
|
|
||||||
if (err) {
|
|
||||||
return deferred.reject();
|
|
||||||
}
|
|
||||||
that.add(data);
|
|
||||||
that.prefetch.store(that.index.serialize());
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_initialize: function initialize() {
|
|
||||||
var that = this, deferred;
|
|
||||||
this.clear();
|
|
||||||
(this.initPromise = this._loadPrefetch()).done(addLocalToIndex);
|
|
||||||
return this.initPromise;
|
|
||||||
function addLocalToIndex() {
|
|
||||||
that.add(that.local);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initialize: function initialize(force) {
|
|
||||||
return !this.initPromise || force ? this._initialize() : this.initPromise;
|
|
||||||
},
|
|
||||||
add: function add(data) {
|
|
||||||
this.index.add(data);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
get: function get(ids) {
|
|
||||||
ids = _.isArray(ids) ? ids : [].slice.call(arguments);
|
|
||||||
return this.index.get(ids);
|
|
||||||
},
|
|
||||||
search: function search(query, sync, async) {
|
|
||||||
var that = this, local;
|
|
||||||
sync = sync || _.noop;
|
|
||||||
async = async || _.noop;
|
|
||||||
local = this.sorter(this.index.search(query));
|
|
||||||
sync(this.remote ? local.slice() : local);
|
|
||||||
if (this.remote && local.length < this.sufficient) {
|
|
||||||
this.remote.get(query, processRemote);
|
|
||||||
} else if (this.remote) {
|
|
||||||
this.remote.cancelLastRequest();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
function processRemote(remote) {
|
|
||||||
var nonDuplicates = [];
|
|
||||||
_.each(remote, function(r) {
|
|
||||||
!_.some(local, function(l) {
|
|
||||||
return that.identify(r) === that.identify(l);
|
|
||||||
}) && nonDuplicates.push(r);
|
|
||||||
});
|
|
||||||
that.indexRemote && that.add(nonDuplicates);
|
|
||||||
async(nonDuplicates);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
all: function all() {
|
|
||||||
return this.index.all();
|
|
||||||
},
|
|
||||||
clear: function clear() {
|
|
||||||
this.index.reset();
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
clearPrefetchCache: function clearPrefetchCache() {
|
|
||||||
this.prefetch && this.prefetch.clear();
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
clearRemoteCache: function clearRemoteCache() {
|
|
||||||
Transport.resetCache();
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
ttAdapter: function ttAdapter() {
|
|
||||||
return this.__ttAdapter();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Bloodhound;
|
|
||||||
}();
|
|
||||||
return Bloodhound;
|
|
||||||
});
|
|
1674
resources/assets/js/lib/typeahead.js
vendored
1674
resources/assets/js/lib/typeahead.js
vendored
File diff suppressed because it is too large
Load diff
4
resources/assets/js/search.js
vendored
Normal file
4
resources/assets/js/search.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Vue.component(
|
||||||
|
'search-results',
|
||||||
|
require('./components/SearchResults.vue').default
|
||||||
|
);
|
10
resources/assets/sass/custom.scss
vendored
10
resources/assets/sass/custom.scss
vendored
|
@ -37,16 +37,6 @@ body, button, input, textarea {
|
||||||
color: #212529 !important;
|
color: #212529 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-form {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-form input,
|
|
||||||
.search-form .form-inline,
|
|
||||||
.search-form .form-control {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-nav .active {
|
.settings-nav .active {
|
||||||
border-left: 2px solid #6c757d !important
|
border-left: 2px solid #6c757d !important
|
||||||
}
|
}
|
||||||
|
|
13
resources/assets/sass/landing.scss
vendored
Normal file
13
resources/assets/sass/landing.scss
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Landing Page bundle
|
||||||
|
|
||||||
|
@import 'variables';
|
||||||
|
@import '~bootstrap/scss/bootstrap';
|
||||||
|
@import 'custom';
|
||||||
|
@import 'landing/carousel';
|
||||||
|
@import 'landing/devices';
|
||||||
|
|
||||||
|
.container.slim {
|
||||||
|
width: auto;
|
||||||
|
max-width: 680px;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
126
resources/assets/sass/landing/carousel.scss
vendored
Normal file
126
resources/assets/sass/landing/carousel.scss
vendored
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
@-webkit-keyframes iosDeviceCarousel {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
17% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
92% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes iosDeviceCarousel {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
17% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
92% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-o-keyframes iosDeviceCarousel {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
17% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
92% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes iosDeviceCarousel {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
17% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
92% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#iosDevice {
|
||||||
|
position:relative;
|
||||||
|
margin:0 auto;
|
||||||
|
}
|
||||||
|
#iosDevice img {
|
||||||
|
position:absolute;
|
||||||
|
left:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#iosDevice img {
|
||||||
|
-webkit-animation-name: iosDeviceCarousel;
|
||||||
|
-webkit-animation-timing-function: ease-in-out;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-duration: 16s;
|
||||||
|
|
||||||
|
-moz-animation-name: iosDeviceCarousel;
|
||||||
|
-moz-animation-timing-function: ease-in-out;
|
||||||
|
-moz-animation-iteration-count: infinite;
|
||||||
|
-moz-animation-duration: 16s;
|
||||||
|
|
||||||
|
-o-animation-name: iosDeviceCarousel;
|
||||||
|
-o-animation-timing-function: ease-in-out;
|
||||||
|
-o-animation-iteration-count: infinite;
|
||||||
|
-o-animation-duration: 16s;
|
||||||
|
|
||||||
|
animation-name: iosDeviceCarousel;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-duration: 16s;
|
||||||
|
}
|
||||||
|
#iosDevice img:nth-of-type(1) {
|
||||||
|
-webkit-animation-delay: 12s;
|
||||||
|
-moz-animation-delay: 12s;
|
||||||
|
-o-animation-delay: 12s;
|
||||||
|
animation-delay: 12s;
|
||||||
|
}
|
||||||
|
#iosDevice img:nth-of-type(2) {
|
||||||
|
-webkit-animation-delay: 8s;
|
||||||
|
-moz-animation-delay: 8s;
|
||||||
|
-o-animation-delay: 8s;
|
||||||
|
animation-delay: 8s;
|
||||||
|
}
|
||||||
|
#iosDevice img:nth-of-type(3) {
|
||||||
|
-webkit-animation-delay: 4s;
|
||||||
|
-moz-animation-delay: 4s;
|
||||||
|
-o-animation-delay: 4s;
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
#iosDevice img:nth-of-type(4) {
|
||||||
|
-webkit-animation-delay: 0;
|
||||||
|
-moz-animation-delay: 0;
|
||||||
|
-o-animation-delay: 0;
|
||||||
|
animation-delay: 0;
|
||||||
|
}
|
593
resources/assets/sass/landing/devices.scss
vendored
Normal file
593
resources/assets/sass/landing/devices.scss
vendored
Normal file
|
@ -0,0 +1,593 @@
|
||||||
|
.marvel-device {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
-webkit-box-sizing: content-box !important;
|
||||||
|
box-sizing: content-box !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device .screen {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
background: white;
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
border-radius: 1px;
|
||||||
|
-webkit-box-shadow: 0 0 0 3px #111;
|
||||||
|
box-shadow: 0 0 0 3px #111
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device .top-bar,
|
||||||
|
.marvel-device .bottom-bar {
|
||||||
|
height: 3px;
|
||||||
|
background: black;
|
||||||
|
width: 100%;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device .middle-bar {
|
||||||
|
width: 3px;
|
||||||
|
height: 4px;
|
||||||
|
top: 0px;
|
||||||
|
left: 90px;
|
||||||
|
background: black;
|
||||||
|
position: absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x {
|
||||||
|
width: 375px;
|
||||||
|
height: 812px;
|
||||||
|
padding: 26px;
|
||||||
|
background: #fdfdfd;
|
||||||
|
-webkit-box-shadow: inset 0 0 11px 0 black;
|
||||||
|
box-shadow: inset 0 0 11px 0 black;
|
||||||
|
border-radius: 66px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .overflow {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 66px;
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .shadow {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
position: absolute;
|
||||||
|
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.6) 0%, rgba(255, 255, 255, 0) 60%)
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .shadow--tl {
|
||||||
|
top: -20px;
|
||||||
|
left: -20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .shadow--tr {
|
||||||
|
top: -20px;
|
||||||
|
right: -20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .shadow--bl {
|
||||||
|
bottom: -20px;
|
||||||
|
left: -20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .shadow--br {
|
||||||
|
bottom: -20px;
|
||||||
|
right: -20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x:before {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
height: calc(100% - 10px);
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
content: '';
|
||||||
|
left: 5px;
|
||||||
|
border-radius: 61px;
|
||||||
|
background: black;
|
||||||
|
z-index: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .inner-shadow {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 10px;
|
||||||
|
border-radius: 56px;
|
||||||
|
-webkit-box-shadow: inset 0 0 15px 0 rgba(255, 255, 255, 0.66);
|
||||||
|
box-shadow: inset 0 0 15px 0 rgba(255, 255, 255, 0.66);
|
||||||
|
z-index: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .inner-shadow:before {
|
||||||
|
-webkit-box-shadow: inset 0 0 20px 0 #FFFFFF;
|
||||||
|
box-shadow: inset 0 0 20px 0 #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
height: 116%;
|
||||||
|
position: absolute;
|
||||||
|
top: -8%;
|
||||||
|
content: '';
|
||||||
|
left: 0;
|
||||||
|
border-radius: 200px / 112px;
|
||||||
|
z-index: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .screen {
|
||||||
|
border-radius: 40px;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .top-bar,
|
||||||
|
.marvel-device.iphone-x .bottom-bar {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .top-bar {
|
||||||
|
top: 80px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .bottom-bar {
|
||||||
|
bottom: 80px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .volume,
|
||||||
|
.marvel-device.iphone-x .volume:before,
|
||||||
|
.marvel-device.iphone-x .volume:after,
|
||||||
|
.marvel-device.iphone-x .sleep {
|
||||||
|
width: 3px;
|
||||||
|
background: #b5b5b5;
|
||||||
|
position: absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .volume {
|
||||||
|
left: -3px;
|
||||||
|
top: 116px;
|
||||||
|
height: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .volume:before {
|
||||||
|
height: 62px;
|
||||||
|
top: 62px;
|
||||||
|
content: '';
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .volume:after {
|
||||||
|
height: 62px;
|
||||||
|
top: 140px;
|
||||||
|
content: '';
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .sleep {
|
||||||
|
height: 96px;
|
||||||
|
top: 200px;
|
||||||
|
right: -3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .camera {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
top: 9px;
|
||||||
|
border-radius: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 154px;
|
||||||
|
background: #0d4d71
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .speaker {
|
||||||
|
height: 6px;
|
||||||
|
width: 60px;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
margin-left: -30px;
|
||||||
|
background: #171818;
|
||||||
|
border-radius: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .notch {
|
||||||
|
position: absolute;
|
||||||
|
width: 210px;
|
||||||
|
height: 30px;
|
||||||
|
top: 26px;
|
||||||
|
left: 108px;
|
||||||
|
z-index: 4;
|
||||||
|
background: black;
|
||||||
|
border-bottom-left-radius: 24px;
|
||||||
|
border-bottom-right-radius: 24px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .notch:before,
|
||||||
|
.marvel-device.iphone-x .notch:after {
|
||||||
|
content: '';
|
||||||
|
height: 8px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 8px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .notch:after {
|
||||||
|
background: radial-gradient(circle at bottom left, transparent 0, transparent 70%, black 70%, black 100%);
|
||||||
|
left: -8px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x .notch:before {
|
||||||
|
background: radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);
|
||||||
|
right: -8px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape {
|
||||||
|
height: 375px;
|
||||||
|
width: 812px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .top-bar,
|
||||||
|
.marvel-device.iphone-x.landscape .bottom-bar {
|
||||||
|
width: 8px;
|
||||||
|
height: 100%;
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .top-bar {
|
||||||
|
left: 80px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .bottom-bar {
|
||||||
|
right: 80px;
|
||||||
|
bottom: auto;
|
||||||
|
left: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .volume,
|
||||||
|
.marvel-device.iphone-x.landscape .volume:before,
|
||||||
|
.marvel-device.iphone-x.landscape .volume:after,
|
||||||
|
.marvel-device.iphone-x.landscape .sleep {
|
||||||
|
height: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .inner-shadow:before {
|
||||||
|
height: 100%;
|
||||||
|
width: 116%;
|
||||||
|
left: -8%;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 112px / 200px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .volume {
|
||||||
|
bottom: -3px;
|
||||||
|
top: auto;
|
||||||
|
left: 116px;
|
||||||
|
width: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .volume:before {
|
||||||
|
width: 62px;
|
||||||
|
left: 62px;
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .volume:after {
|
||||||
|
width: 62px;
|
||||||
|
left: 140px;
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .sleep {
|
||||||
|
width: 96px;
|
||||||
|
left: 200px;
|
||||||
|
top: -3px;
|
||||||
|
right: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .camera {
|
||||||
|
left: 9px;
|
||||||
|
bottom: 154px;
|
||||||
|
top: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .speaker {
|
||||||
|
width: 6px;
|
||||||
|
height: 60px;
|
||||||
|
left: 9px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -30px;
|
||||||
|
margin-left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .notch {
|
||||||
|
height: 210px;
|
||||||
|
width: 30px;
|
||||||
|
left: 26px;
|
||||||
|
bottom: 108px;
|
||||||
|
top: auto;
|
||||||
|
border-top-right-radius: 24px;
|
||||||
|
border-bottom-right-radius: 24px;
|
||||||
|
border-bottom-left-radius: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .notch:before,
|
||||||
|
.marvel-device.iphone-x.landscape .notch:after {
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .notch:after {
|
||||||
|
background: radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);
|
||||||
|
bottom: -8px;
|
||||||
|
top: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.iphone-x.landscape .notch:before {
|
||||||
|
background: radial-gradient(circle at top right, transparent 0, transparent 70%, black 70%, black 100%);
|
||||||
|
top: -8px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 {
|
||||||
|
width: 400px;
|
||||||
|
height: 822px;
|
||||||
|
background: black;
|
||||||
|
border-radius: 34px;
|
||||||
|
padding: 45px 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .overflow {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 34px;
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .speaker {
|
||||||
|
height: 8px;
|
||||||
|
width: 56px;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
margin-left: -28px;
|
||||||
|
background: #171818;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 8px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .camera {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 86px;
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
background: #212b36;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .camera:before {
|
||||||
|
content: '';
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
left: -22px;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
background: #212b36;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .sensors {
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
left: 120px;
|
||||||
|
position: absolute;
|
||||||
|
top: 22px;
|
||||||
|
background: #1d233b;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .sensors:before {
|
||||||
|
content: '';
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
left: 18px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background: #1d233b;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .more-sensors {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 285px;
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
background: #33244a;
|
||||||
|
-webkit-box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .more-sensors:before {
|
||||||
|
content: '';
|
||||||
|
height: 11px;
|
||||||
|
width: 11px;
|
||||||
|
left: 40px;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
background: #214a61;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .sleep {
|
||||||
|
width: 2px;
|
||||||
|
height: 56px;
|
||||||
|
background: black;
|
||||||
|
position: absolute;
|
||||||
|
top: 288px;
|
||||||
|
right: -2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .volume {
|
||||||
|
width: 2px;
|
||||||
|
height: 120px;
|
||||||
|
background: black;
|
||||||
|
position: absolute;
|
||||||
|
top: 168px;
|
||||||
|
left: -2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .volume:before {
|
||||||
|
content: '';
|
||||||
|
top: 168px;
|
||||||
|
width: 2px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
background: black;
|
||||||
|
height: 56px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .inner {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 8px);
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
content: '';
|
||||||
|
left: 0px;
|
||||||
|
border-radius: 34px;
|
||||||
|
border-top: 2px solid #9fa0a2;
|
||||||
|
border-bottom: 2px solid #9fa0a2;
|
||||||
|
background: black;
|
||||||
|
z-index: 1;
|
||||||
|
-webkit-box-shadow: inset 0 0 6px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: inset 0 0 6px 0 rgba(255, 255, 255, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .shadow {
|
||||||
|
-webkit-box-shadow: inset 0 0 60px 0 white, inset 0 0 30px 0 rgba(255, 255, 255, 0.5), 0 0 20px 0 white, 0 0 20px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: inset 0 0 60px 0 white, inset 0 0 30px 0 rgba(255, 255, 255, 0.5), 0 0 20px 0 white, 0 0 20px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
height: 101%;
|
||||||
|
position: absolute;
|
||||||
|
top: -0.5%;
|
||||||
|
content: '';
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
left: 10px;
|
||||||
|
border-radius: 38px;
|
||||||
|
z-index: 5;
|
||||||
|
pointer-events: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8 .screen {
|
||||||
|
border-radius: 14px;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape {
|
||||||
|
height: 400px;
|
||||||
|
width: 822px;
|
||||||
|
padding: 10px 45px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .speaker {
|
||||||
|
height: 56px;
|
||||||
|
width: 8px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -28px;
|
||||||
|
margin-left: 0;
|
||||||
|
right: 25px;
|
||||||
|
left: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .camera {
|
||||||
|
top: 86px;
|
||||||
|
right: 18px;
|
||||||
|
left: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .camera:before {
|
||||||
|
top: -22px;
|
||||||
|
left: 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .sensors {
|
||||||
|
top: 120px;
|
||||||
|
right: 22px;
|
||||||
|
left: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .sensors:before {
|
||||||
|
top: 18px;
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .more-sensors {
|
||||||
|
top: 285px;
|
||||||
|
right: 18px;
|
||||||
|
left: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .more-sensors:before {
|
||||||
|
top: 40px;
|
||||||
|
left: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .sleep {
|
||||||
|
bottom: -2px;
|
||||||
|
top: auto;
|
||||||
|
right: 288px;
|
||||||
|
width: 56px;
|
||||||
|
height: 2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .volume {
|
||||||
|
width: 120px;
|
||||||
|
height: 2px;
|
||||||
|
top: -2px;
|
||||||
|
right: 168px;
|
||||||
|
left: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .volume:before {
|
||||||
|
right: 168px;
|
||||||
|
left: auto;
|
||||||
|
top: 0;
|
||||||
|
width: 56px;
|
||||||
|
height: 2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .inner {
|
||||||
|
height: 100%;
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
left: 2px;
|
||||||
|
top: 0;
|
||||||
|
border-top: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-left: 2px solid #9fa0a2;
|
||||||
|
border-right: 2px solid #9fa0a2
|
||||||
|
}
|
||||||
|
|
||||||
|
.marvel-device.note8.landscape .shadow {
|
||||||
|
width: 101%;
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
left: -0.5%;
|
||||||
|
top: 10px
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<div class="card mb-3 border-left-blue">
|
<div class="card mb-3 border-left-blue">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<p class="font-weight-ultralight h2 mb-0 text-truncate">{{$sys['pixelfed']}}</p>
|
<p class="font-weight-ultralight h2 mb-0 text-truncate" title="{{$sys['pixelfed']}}" data-toggle="tooltip">{{$sys['pixelfed']}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer font-weight-bold py-0 text-center bg-white">Pixelfed</div>
|
<div class="card-footer font-weight-bold py-0 text-center bg-white">Pixelfed</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<div class="card mb-3 border-left-blue">
|
<div class="card mb-3 border-left-blue">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<p class="font-weight-ultralight h2 mb-0 text-truncate">{{$sys['database']['version']}}</p>
|
<p class="font-weight-ultralight h2 mb-0 text-truncate" title="{{$sys['database']['version']}}" data-toggle="tooltip">{{$sys['database']['version']}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer font-weight-bold py-0 text-center bg-white">{{$sys['database']['name']}}</div>
|
<div class="card-footer font-weight-bold py-0 text-center bg-white">{{$sys['database']['name']}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<div class="card mb-3 border-left-blue">
|
<div class="card mb-3 border-left-blue">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<p class="font-weight-ultralight h2 mb-0 text-truncate">{{$sys['php']}}</p>
|
<p class="font-weight-ultralight h2 mb-0 text-truncate" title="{{$sys['php']}}" data-toggle="tooltip">{{$sys['php']}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer font-weight-bold py-0 text-center bg-white">PHP</div>
|
<div class="card-footer font-weight-bold py-0 text-center bg-white">PHP</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<div class="card mb-3 border-left-blue">
|
<div class="card mb-3 border-left-blue">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<p class="font-weight-ultralight h2 mb-0 text-truncate">{{$sys['laravel']}}</p>
|
<p class="font-weight-ultralight h2 mb-0 text-truncate" title="{{$sys['laravel']}}" data-toggle="tooltip">{{$sys['laravel']}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer font-weight-bold py-0 text-center bg-white">Laravel</div>
|
<div class="card-footer font-weight-bold py-0 text-center bg-white">Laravel</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,9 +7,14 @@
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
@auth
|
@auth
|
||||||
<ul class="navbar-nav ml-auto d-none d-md-block">
|
<ul class="navbar-nav mx-auto pr-3">
|
||||||
<form class="form-inline search-form">
|
<form class="form-inline search-bar" method="get" action="/i/results">
|
||||||
<input class="form-control mr-sm-2 search-form-input" placeholder="{{__('navmenu.search')}}" aria-label="search" autocomplete="off">
|
<div class="input-group">
|
||||||
|
<input class="form-control" name="q" placeholder="{{__('navmenu.search')}}" aria-label="search" autocomplete="off">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-primary" type="submit"><i class="fas fa-search"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
@endauth
|
@endauth
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('meta')<meta property="og:description" content="{{$profile->bio}}">
|
@push('meta')<meta property="og:description" content="{{$profile->bio}}">
|
||||||
<meta property="og:image" content="{{$profile->avatarUrl()}}">
|
|
||||||
<link href="{{$profile->permalink('.atom')}}" rel="alternate" title="{{$profile->username}} on PixelFed" type="application/atom+xml">
|
|
||||||
@if(false == $settings['crawlable'] || $profile->remote_url)
|
@if(false == $settings['crawlable'] || $profile->remote_url)
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
@else <meta property="og:image" content="{{$profile->avatarUrl()}}">
|
||||||
|
<link href="{{$profile->permalink('.atom')}}" rel="alternate" title="{{$profile->username}} on PixelFed" type="application/atom+xml">
|
||||||
|
<link href='{{$profile->permalink()}}' rel='alternate' type='application/activity+json'>
|
||||||
@endif
|
@endif
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
|
|
5
resources/views/pxtv/home.blade.php
Normal file
5
resources/views/pxtv/home.blade.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@endsection
|
|
@ -1,10 +1,12 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<search-results></search-results>
|
<search-results query="{{request()->query('q')}}" profile-id="{{Auth::user()->profile->id}}"></search-results>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
|
<script type="text/javascript" src="{{mix('js/compose.js')}}"></script>
|
||||||
|
<script type="text/javascript" src="{{mix('js/search.js')}}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#content'
|
el: '#content'
|
||||||
|
|
|
@ -22,11 +22,157 @@
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2">
|
<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="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2">
|
||||||
<link href="{{ mix('css/app.css') }}" rel="stylesheet" data-stylesheet="light">
|
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="">
|
<body class="">
|
||||||
<main id="content">
|
<main id="content">
|
||||||
<landing-page></landing-page>
|
<section class="container">
|
||||||
|
<div class="row py-5 mb-5">
|
||||||
|
<div class="col-12 col-md-6 d-none d-md-block">
|
||||||
|
<div class="m-md-4" style="position: absolute; transform: scale(0.66)">
|
||||||
|
<div class="marvel-device note8" style="position: absolute;z-index:10;">
|
||||||
|
<div class="inner"></div>
|
||||||
|
<div class="overflow">
|
||||||
|
<div class="shadow"></div>
|
||||||
|
</div>
|
||||||
|
<div class="speaker"></div>
|
||||||
|
<div class="sensors"></div>
|
||||||
|
<div class="more-sensors"></div>
|
||||||
|
<div class="sleep"></div>
|
||||||
|
<div class="volume"></div>
|
||||||
|
<div class="camera"></div>
|
||||||
|
<div class="screen">
|
||||||
|
<img src="/img/landing/android_1.jpg" class="img-fluid">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="marvel-device iphone-x" style="position: absolute;z-index: 20;margin: 99px 0 0 151px;">
|
||||||
|
<div class="notch">
|
||||||
|
<div class="camera"></div>
|
||||||
|
<div class="speaker"></div>
|
||||||
|
</div>
|
||||||
|
<div class="top-bar"></div>
|
||||||
|
<div class="sleep"></div>
|
||||||
|
<div class="bottom-bar"></div>
|
||||||
|
<div class="volume"></div>
|
||||||
|
<div class="overflow">
|
||||||
|
<div class="shadow shadow--tr"></div>
|
||||||
|
<div class="shadow shadow--tl"></div>
|
||||||
|
<div class="shadow shadow--br"></div>
|
||||||
|
<div class="shadow shadow--bl"></div>
|
||||||
|
</div>
|
||||||
|
<div class="inner-shadow"></div>
|
||||||
|
<div class="screen">
|
||||||
|
<div id="iosDevice">
|
||||||
|
<img v-if="!loading" src="/img/landing/ios_4.jpg" class="img-fluid">
|
||||||
|
<img v-if="!loading" src="/img/landing/ios_3.jpg" class="img-fluid">
|
||||||
|
<img v-if="!loading" src="/img/landing/ios_2.jpg" class="img-fluid">
|
||||||
|
<img src="/img/landing/ios_1.jpg" class="img-fluid">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-5 offset-md-1">
|
||||||
|
<div>
|
||||||
|
<div class="card my-4">
|
||||||
|
<div class="card-body px-lg-5">
|
||||||
|
<div class="text-center pt-3">
|
||||||
|
<img src="/img/pixelfed-icon-color.svg">
|
||||||
|
</div>
|
||||||
|
<div class="py-3 text-center">
|
||||||
|
<h3 class="font-weight-bold">Pixelfed</h3>
|
||||||
|
<p class="mb-0 lead">Photo sharing for everyone</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if(true === config('pixelfed.open_registration'))
|
||||||
|
<form class="px-1" method="POST" action="{{ route('register') }}">
|
||||||
|
@csrf
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" placeholder="{{ __('Name') }}" required autofocus>
|
||||||
|
|
||||||
|
@if ($errors->has('name'))
|
||||||
|
<span class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('name') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" placeholder="{{ __('Username') }}" required>
|
||||||
|
|
||||||
|
@if ($errors->has('username'))
|
||||||
|
<span class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('username') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" placeholder="{{ __('E-Mail Address') }}" required>
|
||||||
|
|
||||||
|
@if ($errors->has('email'))
|
||||||
|
<span class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('email') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" placeholder="{{ __('Password') }}" required>
|
||||||
|
|
||||||
|
@if ($errors->has('password'))
|
||||||
|
<span class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('password') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" placeholder="{{ __('Confirm Password') }}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if(config('pixelfed.recaptcha'))
|
||||||
|
<div class="row my-3">
|
||||||
|
{!! Recaptcha::render() !!}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block py-0 font-weight-bold">
|
||||||
|
{{ __('Register') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mb-0 font-weight-bold text-lighter small">By signing up, you agree to our <a href="/site/terms" class="text-muted">Terms of Use</a> and <a href="/site/privacy" class="text-muted">Privacy Policy</a>.</p>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
<div style="min-height: 350px" class="d-flex justify-content-center align-items-center">
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="lead">Registrations are closed.</p>
|
||||||
|
<p class="text-lighter small">You can find a list of other instances on <a href="https://the-federation.info/pixelfed" class="text-muted font-weight-bold">the-federation.info/pixelfed</a> or <a href="https://fediverse.network/pixelfed" class="text-muted font-weight-bold">fediverse.network/pixelfed</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card card-body">
|
||||||
|
<p class="text-center mb-0 font-weight-bold">Have an account? <a href="/login">Log in</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container py-3">
|
<div class="container py-3">
|
||||||
|
@ -38,13 +184,9 @@
|
||||||
<a href="{{route('site.privacy')}}" class="text-primary pr-3">{{__('site.privacy')}}</a>
|
<a href="{{route('site.privacy')}}" class="text-primary pr-3">{{__('site.privacy')}}</a>
|
||||||
<a href="{{route('site.platform')}}" class="text-primary pr-3">API</a>
|
<a href="{{route('site.platform')}}" class="text-primary pr-3">API</a>
|
||||||
<a href="{{route('site.language')}}" class="text-primary pr-3">{{__('site.language')}}</a>
|
<a href="{{route('site.language')}}" class="text-primary pr-3">{{__('site.language')}}</a>
|
||||||
<a href="https://pixelfed.org" class="text-muted float-right" rel="noopener" title="version {{config('pixelfed.version')}}" data-toggle="tooltip">Powered by PixelFed</a>
|
<a href="https://pixelfed.org" class="text-muted float-right" rel="noopener" title="version {{config('pixelfed.version')}}" data-toggle="tooltip">Powered by Pixelfed</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
<script type="text/javascript" src="{{mix('js/app.js')}}"></script>
|
|
||||||
<script type="text/javascript" src="{{mix('js/landing.js')}}"></script>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -130,6 +130,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
|
|
||||||
Route::get('media/preview/{profileId}/{mediaId}', 'ApiController@showTempMedia')->name('temp-media');
|
Route::get('media/preview/{profileId}/{mediaId}', 'ApiController@showTempMedia')->name('temp-media');
|
||||||
|
|
||||||
|
Route::get('results', 'SearchController@results');
|
||||||
|
Route::post('visibility', 'StatusController@toggleVisibility');
|
||||||
|
|
||||||
Route::group(['prefix' => 'report'], function () {
|
Route::group(['prefix' => 'report'], function () {
|
||||||
Route::get('/', 'ReportController@showForm')->name('report.form');
|
Route::get('/', 'ReportController@showForm')->name('report.form');
|
||||||
|
|
|
@ -8,13 +8,6 @@ use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||||
|
|
||||||
class InstalledTest extends TestCase
|
class InstalledTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @test */
|
|
||||||
public function landing_page()
|
|
||||||
{
|
|
||||||
$response = $this->get('/');
|
|
||||||
$response->assertStatus(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function nodeinfo_api()
|
public function nodeinfo_api()
|
||||||
{
|
{
|
||||||
|
|
9
webpack.mix.js
vendored
9
webpack.mix.js
vendored
|
@ -28,16 +28,19 @@ mix.js('resources/assets/js/app.js', 'public/js')
|
||||||
// Timeline component
|
// Timeline component
|
||||||
.js('resources/assets/js/timeline.js', 'public/js')
|
.js('resources/assets/js/timeline.js', 'public/js')
|
||||||
|
|
||||||
// LandingPage component
|
|
||||||
.js('resources/assets/js/landing.js', 'public/js')
|
|
||||||
|
|
||||||
// ComposeModal component
|
// ComposeModal component
|
||||||
.js('resources/assets/js/compose.js', 'public/js')
|
.js('resources/assets/js/compose.js', 'public/js')
|
||||||
|
|
||||||
|
// SearchResults component
|
||||||
|
.js('resources/assets/js/search.js', 'public/js')
|
||||||
|
|
||||||
.sass('resources/assets/sass/app.scss', 'public/css', {
|
.sass('resources/assets/sass/app.scss', 'public/css', {
|
||||||
implementation: require('node-sass')
|
implementation: require('node-sass')
|
||||||
})
|
})
|
||||||
.sass('resources/assets/sass/appdark.scss', 'public/css', {
|
.sass('resources/assets/sass/appdark.scss', 'public/css', {
|
||||||
implementation: require('node-sass')
|
implementation: require('node-sass')
|
||||||
})
|
})
|
||||||
|
.sass('resources/assets/sass/landing.scss', 'public/css', {
|
||||||
|
implementation: require('node-sass')
|
||||||
|
})
|
||||||
.version();
|
.version();
|
||||||
|
|
Loading…
Reference in a new issue