1
0
Fork 0

Merge pull request #1830 from pixelfed/staging

Staging
This commit is contained in:
daniel 2019-11-24 16:19:12 -07:00 committed by GitHub
commit e3f16c8b16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 890 additions and 241 deletions

View File

@ -4,6 +4,8 @@
### Added
- Added drafts API endpoint for Camera Roll ([bad2ecde](https://github.com/pixelfed/pixelfed/commit/bad2ecde))
- Added AccountService ([885a1258](https://github.com/pixelfed/pixelfed/commit/885a1258))
- Added post embeds ([1fecf717](https://github.com/pixelfed/pixelfed/commit/1fecf717))
### Fixed
- Fixed like and share/reblog count on profiles ([86cb7d09](https://github.com/pixelfed/pixelfed/commit/86cb7d09))
@ -45,6 +47,10 @@
- Updated StatusTransformer, added ```local``` attribute ([484bb509](https://github.com/pixelfed/pixelfed/commit/484bb509))
- Updated PostComponent, fix bug affecting MomentUI and non authenticated users ([7b3fe215](https://github.com/pixelfed/pixelfed/commit/7b3fe215))
- Updated FixUsernames command to allow usernames containing ```.``` ([e5d77c6d](https://github.com/pixelfed/pixelfed/commit/e5d77c6d))
- Updated landing page, add age check ([d11e82c3](https://github.com/pixelfed/pixelfed/commit/d11e82c3))
- Updated ApiV1Controller, add ```mobile_apis``` to /api/v1/instance endpoint ([57407463](https://github.com/pixelfed/pixelfed/commit/57407463))
- Updated PublicTimelineService, add video media scopes ([7b00eba3](https://github.com/pixelfed/pixelfed/commit/7b00eba3))
- Updated PublicApiController, add AccountService ([5ebd2c8a](https://github.com/pixelfed/pixelfed/commit/5ebd2c8a))
## Deprecated

View File

@ -906,7 +906,9 @@ class ApiV1Controller extends Controller
'max_avatar_size' => config('pixelfed.max_avatar_size'),
'max_caption_length' => config('pixelfed.max_caption_length'),
'max_bio_length' => config('pixelfed.max_bio_length'),
'max_album_length' => config('pixelfed.max_album_length')
'max_album_length' => config('pixelfed.max_album_length'),
'mobile_apis' => config('pixelfed.oauth_enabled')
]
];
return response()->json($res, 200, [], JSON_PRETTY_PRINT);

View File

@ -63,7 +63,7 @@ class RegisterController extends Controller
'unique:users',
function ($attribute, $value, $fail) {
if (!ctype_alpha($value[0])) {
return $fail('Username is invalid. Username must be alpha-numeric and start with a letter.');
return $fail('Username is invalid. Must start with a letter or number.');
}
$val = str_replace(['_', '-', '.'], '', $value);
if(!ctype_alnum($val)) {
@ -73,6 +73,7 @@ class RegisterController extends Controller
];
$rules = [
'agecheck' => 'required|accepted',
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
'username' => $usernameRules,
'email' => 'required|string|email|max:255|unique:users',

View File

@ -14,6 +14,8 @@ use App\{
};
use Auth, DB, Cache;
use Illuminate\Http\Request;
use App\Transformer\Api\AccountTransformer;
use App\Transformer\Api\AccountWithStatusesTransformer;
use App\Transformer\Api\StatusStatelessTransformer;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
@ -131,7 +133,31 @@ class DiscoverController extends Controller
public function profilesDirectory(Request $request)
{
$profiles = Profile::whereNull('domain')->simplePaginate(48);
return view('discover.profiles.home', compact('profiles'));
return view('discover.profiles.home');
}
public function profilesDirectoryApi(Request $request)
{
$this->validate($request, [
'page' => 'integer|max:10'
]);
$page = $request->input('page') ?? 1;
$key = 'discover:profiles:page:' . $page;
$ttl = now()->addHours(12);
$res = Cache::remember($key, $ttl, function() {
$profiles = Profile::whereNull('domain')
->whereNull('status')
->whereIsPrivate(false)
->has('statuses')
->whereIsSuggestable(true)
// ->inRandomOrder()
->simplePaginate(8);
$resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
return $this->fractal->createData($resource)->toArray();
});
return $res;
}
}

View File

@ -22,7 +22,11 @@ use App\Transformer\Api\{
RelationshipTransformer,
StatusTransformer,
};
use App\Services\UserFilterService;
use App\Services\{
AccountService,
PublicTimelineService,
UserFilterService
};
use App\Jobs\StatusPipeline\NewStatusPipeline;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@ -38,17 +42,12 @@ class PublicApiController extends Controller
$this->fractal->setSerializer(new ArraySerializer());
}
protected function getUserData()
protected function getUserData($user)
{
if(false == Auth::check()) {
if(!$user) {
return [];
} else {
$profile = Auth::user()->profile;
if($profile->status) {
return [];
}
$user = new Fractal\Resource\Item($profile, new AccountTransformer());
return $this->fractal->createData($user)->toArray();
return AccountService::get($user->profile_id);
}
}
@ -90,7 +89,7 @@ class PublicApiController extends Controller
$item = new Fractal\Resource\Item($status, new StatusTransformer());
$res = [
'status' => $this->fractal->createData($item)->toArray(),
'user' => $this->getUserData(),
'user' => $this->getUserData($request->user()),
'likes' => $this->getLikes($status),
'shares' => $this->getShares($status),
'reactions' => [
@ -235,12 +234,13 @@ class PublicApiController extends Controller
$max = $request->input('max_id');
$limit = $request->input('limit') ?? 3;
// $private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
// return Profile::whereIsPrivate(true)
// ->orWhere('unlisted', true)
// ->orWhere('status', '!=', null)
// ->pluck('id');
// });
$private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
return Profile::whereIsPrivate(true)
->orWhere('unlisted', true)
->orWhere('status', '!=', null)
->pluck('id')
->toArray();
});
// if(Auth::check()) {
// // $pid = Auth::user()->profile->id;
@ -255,7 +255,17 @@ class PublicApiController extends Controller
// $filtered = [];
// }
$filtered = Auth::check() ? UserFilterService::filters(Auth::user()->profile_id) : [];
$filtered = Auth::check() ? array_merge($private, UserFilterService::filters(Auth::user()->profile_id)) : [];
// if($max == 0) {
// $res = PublicTimelineService::count();
// if($res == 0) {
// PublicTimelineService::warmCache();
// $res = PublicTimelineService::get(0,4);
// } else {
// $res = PublicTimelineService::get(0,4);
// }
// return response()->json($res);
// }
if($min || $max) {
$dir = $min ? '>' : '<';
@ -321,7 +331,6 @@ class PublicApiController extends Controller
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray();
// $res = $timeline;
return response()->json($res);
}
@ -439,98 +448,7 @@ class PublicApiController extends Controller
public function networkTimelineApi(Request $request)
{
if(!Auth::check()) {
return abort(403);
}
$this->validate($request,[
'page' => 'nullable|integer|max:40',
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'limit' => 'nullable|integer|max:20'
]);
$page = $request->input('page');
$min = $request->input('min_id');
$max = $request->input('max_id');
$limit = $request->input('limit') ?? 3;
// TODO: Use redis for timelines
// $timeline = Timeline::build()->local();
$pid = Auth::user()->profile->id;
$private = Cache::remember('profiles:private', now()->addMinutes(1440), function() {
return Profile::whereIsPrivate(true)
->orWhere('unlisted', true)
->orWhere('status', '!=', null)
->pluck('id');
});
$filters = UserFilter::whereUserId($pid)
->whereFilterableType('App\Profile')
->whereIn('filter_type', ['mute', 'block'])
->pluck('filterable_id')->toArray();
$filtered = array_merge($private->toArray(), $filters);
if($min || $max) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;
$timeline = Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'created_at',
'updated_at'
)->where('id', $dir, $id)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
->whereNotIn('profile_id', $filtered)
->whereNotNull('uri')
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereVisibility('public')
->latest()
->limit($limit)
->get();
} else {
$timeline = Status::select(
'id',
'uri',
'caption',
'rendered',
'profile_id',
'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope',
'local',
'reply_count',
'comments_disabled',
'created_at',
'updated_at'
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
->whereNotIn('profile_id', $filtered)
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereNotNull('uri')
->whereVisibility('public')
->latest()
->simplePaginate($limit);
}
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray();
return response()->json($res);
return response()->json([]);
}
public function relationships(Request $request)
@ -555,10 +473,7 @@ class PublicApiController extends Controller
public function account(Request $request, $id)
{
$profile = Profile::whereNull('status')->findOrFail($id);
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray();
$res = AccountService::get($id);
return response()->json($res);
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
class SeasonalController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function yearInReview()
{
$profile = Auth::user()->profile;
return view('account.yir', compact('profile'));
}
}

View File

@ -10,10 +10,10 @@ use App\Util\Localization\Localization;
class SiteController extends Controller
{
public function home()
public function home(Request $request)
{
if (Auth::check()) {
return $this->homeTimeline();
return $this->homeTimeline($request);
} else {
return $this->homeGuest();
}
@ -24,9 +24,13 @@ class SiteController extends Controller
return view('site.index');
}
public function homeTimeline()
public function homeTimeline(Request $request)
{
return view('timeline.home');
$this->validate($request, [
'layout' => 'nullable|string|in:grid,feed'
]);
$layout = $request->input('layout', 'feed');
return view('timeline.home', compact('layout'));
}
public function changeLocale(Request $request, $locale)

View File

@ -51,6 +51,12 @@ class StatusController extends Controller
}
}
if($status->type == 'archived') {
if(Auth::user()->profile_id !== $status->profile_id) {
abort(404);
}
}
if ($request->wantsJson() && config('federation.activitypub.enabled')) {
return $this->showActivityPub($request, $status);
}
@ -70,13 +76,29 @@ class StatusController extends Controller
public function showEmbed(Request $request, $username, int $id)
{
abort(404);
$profile = Profile::whereNull('status')->whereUsername($username)->first();
$status = Status::whereScope('private')->find($id);
if(!$profile || !$status) {
return view('status.embed-removed');
$profile = Profile::whereNull(['domain','status'])
->whereIsPrivate(false)
->whereUsername($username)
->first();
if(!$profile) {
$content = view('status.embed-removed');
return response($content)->header('X-Frame-Options', 'ALLOWALL');
}
return view('status.embed', compact('status'));
$status = Status::whereProfileId($profile->id)
->whereNull('uri')
->whereScope('public')
->whereIsNsfw(false)
->whereIn('type', ['photo', 'video'])
->find($id);
if(!$status) {
$content = view('status.embed-removed');
return response($content)->header('X-Frame-Options', 'ALLOWALL');
}
$showLikes = $request->filled('likes') && $request->likes == true;
$showCaption = $request->filled('caption') && $request->caption !== false;
$layout = $request->filled('layout') && $request->layout == 'compact' ? 'compact' : 'full';
$content = view('status.embed', compact('status', 'showLikes', 'showCaption', 'layout'));
return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
}
public function showObject(Request $request, $username, int $id)

View File

@ -29,6 +29,7 @@ class Kernel extends HttpKernel
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\App\Http\Middleware\FrameGuard::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
class FrameGuard
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if (!$response->headers->has('X-Frame-Options')) {
$response->headers->set('X-Frame-Options', 'SAMEORIGIN', false);
}
return $response;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Services;
use Cache;
use App\Profile;
use App\Transformer\Api\AccountTransformer;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
class AccountService {
const CACHE_KEY = 'pf:services:account:';
public static function get($id)
{
$key = self::CACHE_KEY . ':' . $id;
$ttl = now()->addHours(12);
return Cache::remember($key, $ttl, function() use($id) {
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$profile = Profile::whereNull('status')->findOrFail($id);
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
return $fractal->createData($resource)->toArray();
});
}
}

View File

@ -52,7 +52,7 @@ class PublicTimelineService {
$ids = Status::whereNull('uri')
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereIn('type', ['photo', 'photo:album'])
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereScope('public')
->latest()
->limit($limit)

View File

@ -33,6 +33,7 @@ class AccountTransformer extends Fractal\TransformerAbstract
'website' => $profile->website,
'local' => (bool) $local,
'is_admin' => (bool) $is_admin,
'created_at' => $profile->created_at->timestamp
];
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Transformer\Api;
use Auth;
use App\Profile;
use League\Fractal;
class AccountWithStatusesTransformer extends Fractal\TransformerAbstract
{
protected $defaultIncludes = [
// 'relationship',
'posts',
];
public function transform(Profile $profile)
{
$local = $profile->domain == null;
$is_admin = !$local ? false : $profile->user->is_admin;
$acct = $local ? $profile->username : substr($profile->username, 1);
$username = $local ? $profile->username : explode('@', $acct)[0];
return [
'id' => (string) $profile->id,
'username' => $username,
'acct' => $acct,
'display_name' => $profile->name,
'locked' => (bool) $profile->is_private,
'followers_count' => $profile->followerCount(),
'following_count' => $profile->followingCount(),
'statuses_count' => (int) $profile->statusCount(),
'note' => $profile->bio ?? '',
'url' => $profile->url(),
'avatar' => $profile->avatarUrl(),
'website' => $profile->website,
'local' => (bool) $local,
'is_admin' => (bool) $is_admin,
'created_at' => $profile->created_at->timestamp
];
}
protected function includePosts(Profile $profile)
{
$posts = $profile
->statuses()
->whereIsNsfw(false)
->whereType('photo')
->whereScope('public')
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->latest()
->take(5)
->get();
return $this->collection($posts, new StatusStatelessTransformer());
}
}

View File

@ -48,6 +48,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
'thread' => false,
'replies' => [],
'parent' => $status->parent() ? $this->transform($status->parent()) : [],
'local' => (bool) $status->local,
];
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/embed.js vendored Normal file
View File

@ -0,0 +1 @@
!function(){var e;e=function(){var e=[];window.addEventListener("message",function(t){var n=t.data||{};"setHeight"===n.type&&e[n.id]&&(e[n.id].height=n.height)}),[].forEach.call(document.querySelectorAll("iframe.pixelfed__embed"),function(t){t.scrolling="no",t.style.overflow="hidden",e.push(t);var n=e.length-1;t.onload=function(){t.contentWindow.postMessage({type:"setHeight",id:n},"*")},t.onload()})},-1!==["interactive","complete"].indexOf(document.readyState)?e():document.addEventListener("DOMContentLoaded",e)}();

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/profile-directory.js vendored Normal file
View File

@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[16],{19:function(t,e,s){t.exports=s("ETg6")},"7wkd":function(t,e,s){"use strict";s.r(e);var o={props:["profileId"],data:function(){return{loaded:!1,showLoadMore:!0,profiles:[],page:1}},beforeMount:function(){this.fetchData()},methods:{fetchData:function(){var t=this;axios.get("/api/pixelfed/v2/discover/profiles",{params:{page:this.page}}).then(function(e){if(0==e.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=e.data,t.showLoadMore=8==t.profiles.length,t.loaded=!0})},prettyCount:function(t){return App.util.format.count(t)},loadMore:function(){this.loaded=!1,this.page++,this.fetchData()},thumbUrl:function(t){return t.media_attachments[0].url},postsPerDay:function(t){var e=t.created_at,s=Date.now(),o=Math.abs(e,s),a=Math.round(o/864e5),n=t.statuses_count,r=this.prettyCount(Math.floor(n/a));return console.log(r),r}}},a=s("KHd+"),n=Object(a.a)(o,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[s("div",{staticClass:"col-12"},[s("p",{staticClass:"font-weight-bold text-lighter text-uppercase"},[t._v("Profiles Directory")]),t._v(" "),t.loaded?s("div",{},[s("div",{staticClass:"row"},[t._l(t.profiles,function(e,o){return s("div",{staticClass:"col-12 col-md-6 p-1"},[s("div",{staticClass:"card card-body border shadow-none py-2"},[s("div",{staticClass:"media"},[s("a",{attrs:{href:e.url}},[s("img",{staticClass:"rounded-circle border mr-3",attrs:{src:e.avatar,alt:"...",width:"40px",height:"40px"}})]),t._v(" "),s("div",{staticClass:"media-body"},[s("p",{staticClass:"mt-0 mb-0 font-weight-bold"},[s("a",{staticClass:"text-dark",attrs:{href:e.url}},[t._v(t._s(e.username))])]),t._v(" "),s("p",{staticClass:"mb-1 small text-lighter d-flex justify-content-between font-weight-bold"},[s("span",[s("span",[t._v(t._s(t.prettyCount(e.statuses_count)))]),t._v(" POSTS\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),s("span",[s("span",[t._v(t._s(t.postsPerDay(e)))]),t._v(" POSTS/DAY\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),s("span",[s("span",[t._v(t._s(t.prettyCount(e.followers_count)))]),t._v(" FOLLOWERS\n\t\t\t\t\t\t\t\t\t")])]),t._v(" "),s("p",{staticClass:"mb-1"},t._l(e.posts,function(e,o){return s("span",{key:"profile_posts_"+o,staticClass:"shadow-sm"},[s("a",{staticClass:"text-decoration-none mr-1",attrs:{href:e.url}},[s("img",{staticClass:"border rounded",attrs:{src:t.thumbUrl(e),width:"62.3px",height:"62.3px"}})])])}),0)])])])])}),t._v(" "),t.showLoadMore?s("div",{staticClass:"col-12"},[s("p",{staticClass:"text-center mb-0 pt-3"},[s("button",{staticClass:"btn btn-outline-secondary btn-sm px-4 py-1 font-weight-bold",on:{click:function(e){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])},[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"row"},[e("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[e("div",{staticClass:"spinner-border",attrs:{role:"status"}},[e("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}],!1,null,"76ea0b14",null);e.default=n.exports},ETg6:function(t,e,s){Vue.component("profile-directory",s("7wkd").default)},"KHd+":function(t,e,s){"use strict";function o(t,e,s,o,a,n,r,i){var c,d="function"==typeof t?t.options:t;if(e&&(d.render=e,d.staticRenderFns=s,d._compiled=!0),o&&(d.functional=!0),n&&(d._scopeId="data-v-"+n),r?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),a&&a.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=c):a&&(c=i?function(){a.call(this,this.$root.$options.shadowRoot)}:a),c)if(d.functional){d._injectStyles=c;var l=d.render;d.render=function(t,e){return c.call(e),l(t,e)}}else{var u=d.beforeCreate;d.beforeCreate=u?[].concat(u,c):[c]}return{exports:t,options:d}}s.d(e,"a",function(){return o})}},[[19,0]]]);

2
public/js/quill.js vendored

File diff suppressed because one or more lines are too long

2
public/js/search.js vendored

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[19],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)}),ace.require(["ace/theme/monokai"],function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)})}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);
(window.webpackJsonp=window.webpackJsonp||[]).push([[20],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)}),ace.require(["ace/theme/monokai"],function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)})}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);

File diff suppressed because one or more lines are too long

2
public/js/vendor.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,27 +1,28 @@
{
"/js/manifest.js": "/js/manifest.js?id=01c8731923a46c30aaed",
"/js/vendor.js": "/js/vendor.js?id=fac92a458473b287c543",
"/js/vendor.js": "/js/vendor.js?id=d8961ac0607442eab9a9",
"/js/ace.js": "/js/ace.js?id=585114d8896dc0c24020",
"/js/activity.js": "/js/activity.js?id=713d9542e71e87fb88c0",
"/js/app.js": "/js/app.js?id=e247f50c24aaed688cc9",
"/css/app.css": "/css/app.css?id=67a82ced484bdb354748",
"/css/appdark.css": "/css/appdark.css?id=b0fdec1caa1ce13301c6",
"/css/landing.css": "/css/landing.css?id=66d18d3f53fa9d41033c",
"/js/app.js": "/js/app.js?id=f41aca7673d153a72c21",
"/css/app.css": "/css/app.css?id=d3f863cc7a1fd51c10db",
"/css/appdark.css": "/css/appdark.css?id=def8c4e280919a0d1453",
"/css/landing.css": "/css/landing.css?id=1a9fb68495fe92338bb2",
"/css/quill.css": "/css/quill.css?id=711b2150d518816d6112",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=b27e524d161917a9e9e1",
"/js/collections.js": "/js/collections.js?id=dcc75eec0b3e0736e5fe",
"/js/components.js": "/js/components.js?id=be8c9e1c6c52db778f29",
"/js/compose.js": "/js/compose.js?id=2d3e96bd3197d49cfe88",
"/js/compose.js": "/js/compose.js?id=3e8264e6f459adeb5b49",
"/js/compose-classic.js": "/js/compose-classic.js?id=e7483681a575c190a43b",
"/js/developers.js": "/js/developers.js?id=9636d4060ca6b359d8a2",
"/js/discover.js": "/js/discover.js?id=fbc49123fc2ce2ff7acf",
"/js/discover.js": "/js/discover.js?id=ec4f53b810977ff041ae",
"/js/hashtag.js": "/js/hashtag.js?id=3fe97ed3f975f0e0baa5",
"/js/loops.js": "/js/loops.js?id=9c31302552d789d5f35b",
"/js/mode-dot.js": "/js/mode-dot.js?id=993d7fee684361edddbc",
"/js/profile.js": "/js/profile.js?id=97bec372a25ffd2e909e",
"/js/quill.js": "/js/quill.js?id=37962cd45a252d2f13c9",
"/js/search.js": "/js/search.js?id=f312959df65e86a307a3",
"/js/status.js": "/js/status.js?id=6fe82a7ab606a7b8740d",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=700e5dc735365e184e41",
"/js/timeline.js": "/js/timeline.js?id=e45ea0de04ac33768c74"
"/js/profile-directory.js": "/js/profile-directory.js?id=d7d5cb12523cd6bac967",
"/js/quill.js": "/js/quill.js?id=d909fb9ece981d98eff1",
"/js/search.js": "/js/search.js?id=45c2ede55c2ac48cf1d6",
"/js/status.js": "/js/status.js?id=5cdd90acab635eb3fe58",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=efa54f8ee94778885eb7",
"/js/timeline.js": "/js/timeline.js?id=cdab37b19c377c04acdc"
}

View File

@ -76,7 +76,21 @@ window.App.util = {
['Walden','filter-walden'],
['Willow','filter-willow'],
['X-Pro II','filter-xpro-ii']
],
emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'
],
],
emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'
],
embed: {
post: (function(url, caption = true, likes = false, layout = 'full') {
let u = url + '/embed?';
u += caption ? 'caption=true&' : 'caption=false&';
u += likes ? 'likes=true&' : 'likes=false&';
u += layout == 'compact' ? 'layout=compact' : 'layout=full';
return '<iframe src="'+u+'" class="pixelfed__embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script async defer src="'+window.location.origin +'/embed.js"><\/script>';
}),
profile: (function(url) {
// placeholder
console.error('This method is not supported yet');
})
}
};

View File

@ -98,6 +98,22 @@
</div>
</a>
<a class="d-none card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" :click="showAddToStoryCard">
<div class="card-body">
<div class="media">
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
<i class="fas fa-history text-white fa-lg"></i>
</div>
<div class="media-body text-left">
<p class="mb-0">
<span class="h5 mt-0 font-weight-bold text-primary">Add to Story</span>
</p>
<p class="mb-0 text-muted">Add a photo or video to your story.</p>
</div>
</div>
</div>
</a>
<a class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/collections/create">
<div class="card-body">
<div class="media">
@ -132,9 +148,8 @@
</div>
</div>
</div>
<hr>
<p>
<a class="font-weight-bold" href="/site/help">Need Help?</a>
<p class="pt-3">
<a class="font-weight-bold" href="/site/help">Help</a>
</p>
</div>
</div>
@ -755,10 +770,6 @@ export default {
this.pageTitle = '';
switch(this.page) {
case 'addToStory':
this.page = 1;
break;
case 'cropPhoto':
case 'editMedia':
this.page = 2;
@ -906,7 +917,8 @@ export default {
.then(res => {
this.cameraRollMedia = res.data;
});
}
},
}
}
</script>

View File

@ -4,10 +4,10 @@
<img src="/img/pixelfed-icon-grey.svg">
</div>
<div v-else>
<div class="d-block d-md-none px-0 border-top-0 mx-n3">
<!-- <div class="d-block d-md-none px-0 border-top-0 mx-n3">
<input class="form-control rounded-0" placeholder="Search" v-model="searchTerm" v-on:keyup.enter="searchSubmit">
</div>
<section class="d-none d-md-flex mb-md-2 pt-5 discover-bar" style="width:auto; overflow: auto hidden;" v-if="categories.length > 0">
</div> -->
<!-- <section class="d-none d-md-flex mb-md-2 pt-5 discover-bar" style="width:auto; overflow: auto hidden;" v-if="categories.length > 0">
<a v-if="config.ab.loops == true" class="text-decoration-none bg-transparent border border-success rounded d-inline-flex align-items-center justify-content-center mr-3 card-disc" href="/discover/loops">
<p class="text-success lead font-weight-bold mb-0">Loops</p>
</a>
@ -15,11 +15,39 @@
<p class="text-white font-weight-bold" style="text-shadow: 3px 3px 16px #272634;">{{category.name}}</p>
</a>
</section>
</section> -->
<section class="mb-5 section-explore">
<div class="profile-timeline">
<div class="row p-0">
<div class="col-4 p-1 p-sm-2 p-md-3" v-for="post in posts">
<div class="row p-0 mt-5">
<div class="col-12 col-md-6">
<div class="">
<a class="card info-overlay card-md-border-0" :href="posts[0].url">
<div class="square">
<span v-if="posts[0].type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
<span v-if="posts[0].type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
<span v-if="posts[0].type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
<div class="square-content" v-bind:style="{ 'background-image': 'url(' + posts[0].thumb + ')' }">
</div>
</div>
</a>
</div>
</div>
<div class="col-12 col-md-6 row p-0 m-0">
<div v-for="(post, index) in posts.slice(1,5)" class="col-6" style="margin-bottom:1.8rem;">
<a class="card info-overlay card-md-border-0" :href="post.url">
<div class="square">
<span v-if="post.type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
<span v-if="post.type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
<span v-if="post.type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
<div class="square-content" v-bind:style="{ 'background-image': 'url(' + post.thumb + ')' }">
</div>
</div>
</a>
</div>
</div>
</div>
<div class="row p-0" style="display: flex;">
<div v-for="(post, index) in posts.slice(5)" class="col-3 p-1 p-sm-2 p-md-3">
<a class="card info-overlay card-md-border-0" :href="post.url">
<div class="square">
<span v-if="post.type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>

View File

@ -234,7 +234,7 @@
</div>
<form v-else class="border-0 rounded-0 align-middle" method="post" action="/i/comment" :data-id="statusId" data-truncate="false">
<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="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="postReply"/>
<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" :disabled="replyText.length == 0" />
</form>
</div>
</div>
@ -351,7 +351,7 @@
</span>
<button
:class="[replyText.length > 1 ? 'btn btn-sm font-weight-bold float-right btn-outline-dark ':'btn btn-sm font-weight-bold float-right btn-outline-lighter']"
:disabled="replyText.length < 2"
:disabled="replyText.length == 0 ? 'disabled':''"
@click="postReply"
>Post</button>
</p>
@ -547,6 +547,10 @@
.momentui .carousel-item {
background: #000 !important;
}
.reply-btn[disabled] {
opacity: .3;
color: #3897f0;
}
</style>
<script>

View File

@ -0,0 +1,127 @@
<template>
<div>
<div class="col-12">
<p class="font-weight-bold text-lighter text-uppercase">Profiles Directory</p>
<div v-if="loaded" class="">
<div class="row">
<div class="col-12 col-md-6 p-1" v-for="(profile, index) in profiles">
<div class="card card-body border shadow-none py-2">
<div class="media">
<a :href="profile.url"><img :src="profile.avatar" class="rounded-circle border mr-3" alt="..." width="40px" height="40px"></a>
<div class="media-body">
<p class="mt-0 mb-0 font-weight-bold">
<a :href="profile.url" class="text-dark">{{profile.username}}</a>
</p>
<p class="mb-1 small text-lighter d-flex justify-content-between font-weight-bold">
<span>
<span>{{prettyCount(profile.statuses_count)}}</span> POSTS
</span>
<span>
<span>{{postsPerDay(profile)}}</span> POSTS/DAY
</span>
<span>
<span>{{prettyCount(profile.followers_count)}}</span> FOLLOWERS
</span>
</p>
<p class="mb-1">
<span v-for="(post, i) in profile.posts" class="shadow-sm" :key="'profile_posts_'+i">
<a :href="post.url" class="text-decoration-none mr-1">
<img :src="thumbUrl(post)" width="62.3px" height="62.3px" class="border rounded">
</a>
</span>
</p>
</div>
</div>
</div>
</div>
<div v-if="showLoadMore" class="col-12">
<p class="text-center mb-0 pt-3">
<button class="btn btn-outline-secondary btn-sm px-4 py-1 font-weight-bold" @click="loadMore()">Load More</button>
</p>
</div>
</div>
</div>
<div v-else>
<div class="row">
<div class="col-12 d-flex justify-content-center align-items-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style type="text/css" scoped></style>
<script type="text/javascript">
export default {
props: ['profileId'],
data() {
return {
loaded: false,
showLoadMore: true,
profiles: [],
page: 1
}
},
beforeMount() {
this.fetchData();
},
methods: {
fetchData() {
axios.get('/api/pixelfed/v2/discover/profiles', {
params: {
page: this.page
}
})
.then(res => {
if(res.data.length == 0) {
this.showLoadMore = false;
this.loaded = true;
return;
}
this.profiles = res.data;
this.showLoadMore = this.profiles.length == 8;
this.loaded = true;
});
},
prettyCount(val) {
return App.util.format.count(val);
},
loadMore() {
this.loaded = false;
this.page++;
this.fetchData();
},
thumbUrl(p) {
return p.media_attachments[0].url;
},
postsPerDay(profile) {
let created = profile.created_at;
let now = Date.now();
let diff = Math.abs(created, now)
let day = 1000 * 60 * 60 * 24;
let days = Math.round(diff / day);
let statuses = profile.statuses_count;
let perDay = this.prettyCount(Math.floor(statuses / days));
console.log(perDay);
return perDay;
}
}
}
</script>

View File

@ -11,31 +11,31 @@
<div v-if="!loading && !networkError" class="mt-5 row">
<div class="col-12 col-md-3 mb-4">
<div class="col-12 col-md-2 mb-4">
<div v-if="results.hashtags || results.profiles || results.statuses">
<p class="font-weight-bold">Filters</p>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="filter1" v-model="filters.hashtags">
<label class="custom-control-label text-muted font-weight-light" for="filter1">Show Hashtags</label>
<label class="custom-control-label text-muted font-weight-light" for="filter1">Hashtags</label>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="filter2" v-model="filters.profiles">
<label class="custom-control-label text-muted font-weight-light" for="filter2">Show Profiles</label>
<label class="custom-control-label text-muted font-weight-light" for="filter2">Profiles</label>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="filter3" v-model="filters.statuses">
<label class="custom-control-label text-muted font-weight-light" for="filter3">Show Statuses</label>
<label class="custom-control-label text-muted font-weight-light" for="filter3">Statuses</label>
</div>
</div>
</div>
<div class="col-12 col-md-9">
<div class="col-12 col-md-10">
<p class="h5 font-weight-bold">Showing results for <i>{{query}}</i></p>
<hr>
<div v-if="filters.hashtags && results.hashtags" class="row mb-4">
<p class="col-12 font-weight-bold text-muted">Hashtags</p>
<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 shadow-none border">
<p class="lead mb-0 text-truncate text-dark" data-toggle="tooltip" :title="hashtag.value">
#{{hashtag.value}}
</p>
@ -49,7 +49,7 @@
<div v-if="filters.profiles && results.profiles" class="row mb-4">
<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 mb-3" style="text-decoration: none;" :href="profile.url">
<div class="card card-body text-center">
<div class="card card-body text-center shadow-none border">
<p class="text-center">
<img :src="profile.entity.thumb" width="32px" height="32px" class="rounded-circle box-shadow">
</p>

View File

@ -1,6 +1,6 @@
<template>
<div class="container" style="">
<div class="row">
<div v-if="layout === 'feed'" class="row">
<div :class="[modes.distractionFree ? 'col-md-8 col-lg-8 offset-md-2 px-0 my-sm-3 timeline order-2 order-md-1':'col-md-8 col-lg-8 px-0 my-sm-3 timeline order-2 order-md-1']">
<div class="d-none" data-id="StoryTimelineComponent"></div>
<div style="padding-top:10px;">
@ -211,13 +211,13 @@
<div v-if="status.id == replyId && !status.comments_disabled" class="card-footer bg-white sticky-md-bottom p-0">
<form class="border-0 rounded-0 align-middle" method="post" action="/i/comment" :data-id="status.id" data-truncate="false">
<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="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="commentSubmit(status, $event)"/>
<input type="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="commentSubmit(status, $event)" :disabled="replyText.length == 0" />
</form>
</div>
</div>
</div>
<div v-if="!loading && feed.length">
<div class="card shadow-none border">
<div class="card shadow-none">
<div class="card-body">
<infinite-loading @infinite="infiniteTimeline" :distance="800">
<div slot="no-more" class="font-weight-bold">No more posts to load</div>
@ -227,7 +227,7 @@
</div>
</div>
<div v-if="!loading && scope == 'home' && feed.length == 0">
<div class="card">
<div class="card shadow-none border">
<div class="card-body text-center">
<p class="h2 font-weight-lighter p-5">Hello, {{profile.acct}}</p>
<p class="text-lighter"><i class="fas fa-camera-retro fa-5x"></i></p>
@ -240,7 +240,7 @@
</div>
<div v-if="!modes.distractionFree" class="col-md-4 col-lg-4 my-3 order-1 order-md-2 d-none d-md-block">
<div class="position-sticky" style="top:68px;">
<div class="position-sticky" style="top:78px;">
<div class="mb-4">
<div class="">
<div class="">
@ -327,11 +327,11 @@
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
<a href="/site/about" class="text-dark pr-2">About Us</a>
<a href="/site/help" class="text-dark pr-2">Help</a>
<a href="/site/open-source" class="text-dark pr-2">Open Source</a>
<a href="/site/language" class="text-dark pr-2">Language</a>
<a href="/site/terms" class="text-dark pr-2">Terms</a>
<a href="/site/privacy" class="text-dark pr-2">Privacy</a>
<a href="/discover/profiles" class="text-dark pr-2">Profiles</a>
<a href="/discover/places" class="text-dark pr-2">Places</a>
<a href="/site/privacy" class="text-dark pr-2">Privacy</a>
<a href="/site/terms" class="text-dark pr-2">Terms</a>
</p>
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
<a href="http://pixelfed.org" class="text-muted" rel="noopener" title="" data-toggle="tooltip">Powered by Pixelfed</a>
@ -341,40 +341,91 @@
</div>
</div>
</div>
<b-modal ref="ctxModal"
id="ctx-modal"
hide-header
hide-footer
centered
rounded
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuReportPost()">Report inappropriate</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id && ctxMenuRelationship && ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id && ctxMenuRelationship && !ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div>
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">Go to post</div>
<!-- <div class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuShare()">Share</div> -->
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
<div v-if="profile && profile.is_admin == true" class="list-group-item rounded cursor-pointer" @click="ctxModMenuShow()">Moderation Tools</div>
<div v-if="ctxMenuStatus && (profile.is_admin || profile.id == ctxMenuStatus.account.id)" class="list-group-item rounded cursor-pointer" @click="deletePost(ctxMenuStatus)">Delete</div>
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
</div>
</b-modal>
<b-modal ref="ctxModModal"
id="ctx-mod-modal"
hide-header
hide-footer
centered
rounded
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div class="list-group-item rounded cursor-pointer" @click="moderatePost(ctxMenuStatus, 'unlist')">Unlist from Timelines</div>
<div class="list-group-item rounded cursor-pointer" @click="">Add Content Warning</div>
<div class="list-group-item rounded cursor-pointer text-lighter" @click="ctxModMenuClose()">Cancel</div>
</div>
<div v-else class="row pt-2">
<div class="col-12">
<div v-if="loading" class="text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<div v-else class="row">
<div class="col-12 col-md-4 p-1 p-md-3 mb-3" v-for="(s, index) in feed" :key="`${index}-${s.id}`">
<div class="card info-overlay card-md-border-0 shadow-sm border border-light" :href="statusUrl(s)">
<div :class="[s.sensitive ? 'square' : 'square ' + s.media_attachments[0].filter_class]">
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
<div class="square-content" v-bind:style="previewBackground(s)">
</div>
<div class="info-overlay-text px-4">
<p class="text-white m-auto text-center">
{{trimCaption(s.content_text)}}
</p>
</div>
</div>
</div>
<div class="py-3 media align-items-center">
<img :src="s.account.avatar" class="mr-3 rounded-circle shadow-sm" :alt="s.account.username + ' \'s avatar'" width="30px" height="30px">
<div class="media-body">
<p class="mb-0 font-weight-bold small">{{s.account.username}}</p>
<p class="mb-0" style="line-height: 0.7;">
<a :href="statusUrl(s)" class="small text-lighter">
<timeago :datetime="s.created_at" :auto-update="60" :converter-options="{includeSeconds:true}" :title="timestampFormat(s.created_at)" v-b-tooltip.hover.bottom></timeago>
</a>
</p>
</div>
<div class="ml-3">
<p class="mb-0">
<span class="font-weight-bold small">{{s.favourites_count == 1 ? '1 like' : s.favourites_count+' likes'}}</span>
<span class="px-2"><i v-bind:class="[s.favourited ? 'fas fa-heart text-danger cursor-pointer' : 'far fa-heart like-btn text-lighter cursor-pointer']" v-on:click="likeStatus(s, $event)"></i></span>
<span class="mr-2 cursor-pointer"><i class="fas fa-ellipsis-v" @click="ctxMenu(s)"></i></span>
</p>
</div>
</div>
</div>
</div>
<div v-if="!loading && feed.length">
<infinite-loading @infinite="infiniteTimeline" :distance="800">
<div slot="no-more" class="font-weight-bold">No more posts to load</div>
<div slot="no-results" class="font-weight-bold">No more posts to load</div>
</infinite-loading>
</div>
</div>
</div>
<b-modal ref="ctxModal"
id="ctx-modal"
hide-header
hide-footer
centered
rounded
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuReportPost()">Report inappropriate</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id && ctxMenuRelationship && ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.account.id != profile.id && ctxMenuRelationship && !ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div>
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">Go to post</div>
<div v-if="ctxMenuStatus && ctxMenuStatus.local == true" class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
<!-- <div class="list-group-item rounded cursor-pointer" @click="ctxMenuShare()">Share</div> -->
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
<div v-if="profile && profile.is_admin == true" class="list-group-item rounded cursor-pointer" @click="ctxModMenuShow()">Moderation Tools</div>
<div v-if="ctxMenuStatus && (profile.is_admin || profile.id == ctxMenuStatus.account.id)" class="list-group-item rounded cursor-pointer" @click="deletePost(ctxMenuStatus)">Delete</div>
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
</div>
</b-modal>
<b-modal ref="ctxModModal"
id="ctx-mod-modal"
hide-header
hide-footer
centered
rounded
size="sm"
body-class="list-group-flush p-0 rounded">
<div class="list-group text-center">
<div class="list-group-item rounded cursor-pointer" @click="moderatePost(ctxMenuStatus, 'unlist')">Unlist from Timelines</div>
<div class="list-group-item rounded cursor-pointer" @click="">Add Content Warning</div>
<div class="list-group-item rounded cursor-pointer text-lighter" @click="ctxModMenuClose()">Cancel</div>
</div>
</b-modal>
<b-modal ref="ctxShareModal"
id="ctx-share-modal"
@ -402,10 +453,10 @@
size="md"
body-class="p-2 rounded">
<div>
<textarea class="form-control disabled" rows="1" style="border: 1px solid #efefef; font-size: 14px; line-height: 17px; min-height: 37px; margin: 0 0 7px; resize: none; white-space: nowrap;" v-model="ctxEmbedPayload"></textarea>
<textarea class="form-control disabled" rows="1" style="border: 1px solid #efefef; font-size: 14px; line-height: 12px; height: 37px; margin: 0 0 7px; resize: none; white-space: nowrap;" v-model="ctxEmbedPayload"></textarea>
<hr>
<button :class="copiedEmbed ? 'btn btn-primary btn-block btn-sm py-1 font-weight-bold disabed': 'btn btn-primary btn-block btn-sm py-1 font-weight-bold'" @click="ctxCopyEmbed" :disabled="copiedEmbed">{{copiedEmbed ? 'Embed Code Copied!' : 'Copy Embed Code'}}</button>
<p class="mb-0 px-2 small text-muted">By using this embed, you agree to our <a href="#">API Terms of Use</a>.</p>
<p class="mb-0 px-2 small text-muted">By using this embed, you agree to our <a href="/site/terms">Terms of Use</a></p>
</div>
</b-modal>
<b-modal
@ -454,11 +505,15 @@
height: 0px;
background: transparent;
}
.reply-btn[disabled] {
opacity: .3;
color: #3897f0;
}
</style>
<script type="text/javascript">
export default {
props: ['scope'],
props: ['scope', 'layout'],
data() {
return {
ids: [],
@ -1177,10 +1232,7 @@
ctxMenu(status) {
this.ctxMenuStatus = status;
// let payload = '<div class="pixlfed-media" data-id="'+ this.ctxMenuStatus.id + '"></div><script ';
// payload += 'src="https://pixelfed.dev/js/embed.js" async><';
// payload += '/script>';
// this.ctxEmbedPayload = payload;
this.ctxEmbedPayload = window.App.util.embed.post(status.url);
if(status.account.id == this.profile.id) {
this.$refs.ctxModal.show();
} else {
@ -1354,6 +1406,21 @@
break;
}
},
previewUrl(status) {
return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url;
},
previewBackground(status) {
let preview = this.previewUrl(status);
return 'background-image: url(' + preview + ');';
},
trimCaption(caption, len = 60) {
return _.truncate(caption, {
length: len
});
}
}
}
</script>

View File

@ -0,0 +1,4 @@
Vue.component(
'profile-directory',
require('./components/ProfileDirectory.vue').default
);

View File

@ -37,10 +37,6 @@ body, button, input, textarea {
color: #212529 !important;
}
.settings-nav .active {
border-left: 2px solid #6c757d !important
}
.settings-nav .active .nav-link{
font-weight: bold;
}

View File

@ -0,0 +1,17 @@
@extends('layouts.app')
@section('content')
<div class="container mt-5">
<div class="col-12">
<profile-directory profile-id="{{Auth::user()->profile_id}}"></profile-directory>
</div>
</div>
@endsection
@push('scripts')
<script type="text/javascript" src="{{mix('js/compose.js')}}"></script>
<script type="text/javascript" src="{{mix('js/profile-directory.js')}}"></script>
<script type="text/javascript">App.boot();</script>
@endpush

View File

@ -75,7 +75,7 @@
</div>
<div class="col-12 col-md-5 offset-md-1">
<div>
<div class="card my-4">
<div class="card my-4 shadow-none border">
<div class="card-body px-lg-5">
<div class="text-center pt-3">
<img src="/img/pixelfed-icon-color.svg">
@ -86,7 +86,7 @@
</div>
<div>
@if(true === config('pixelfed.open_registration'))
<form class="px-1" method="POST" action="{{ route('register') }}">
<form class="px-1" method="POST" action="{{ route('register') }}" id="register_form">
@csrf
<div class="form-group row">
<div class="col-md-12">
@ -102,7 +102,7 @@
<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>
<input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" placeholder="{{ __('Username') }}" required maxlength="15" minlength="2">
@if ($errors->has('username'))
<span class="invalid-feedback">
@ -141,6 +141,16 @@
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" placeholder="{{ __('Confirm Password') }}" required>
</div>
</div>
<div class="form-group row">
<div class="col-md-12">
<div class="form-check">
<input class="form-check-input" name="agecheck" type="checkbox" value="true" id="ageCheck" required>
<label class="form-check-label" for="ageCheck">
I am at least 16 years old
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-md-12">
<button type="submit" class="btn btn-primary btn-block py-0 font-weight-bold">
@ -161,7 +171,7 @@
</div>
</div>
</div>
<div class="card card-body">
<div class="card shadow-none border card-body">
<p class="text-center mb-0 font-weight-bold">Have an account? <a href="/login">Log in</a></p>
</div>
</div>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<title>Pixelfed | 404 Embed Not Found</title>
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
<meta name="medium" content="image">
<meta name="theme-color" content="#10c5f8">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2">
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
<style type="text/css">
body.embed-card {
background: #fff !important;
margin: 0;
padding-bottom: 0;
}
.status-card-embed {
box-shadow: none;
border-radius: 4px;
overflow: hidden;
}
</style>
</head>
<body class="bg-white">
<div class="embed-card">
<div class="card status-card-embed card-md-rounded-0 border card-body border shadow-none rounded-0 d-flex justify-content-center align-items-center">
<div class="text-center p-5">
<img src="/img/pixelfed-icon-color.svg" width="40px" height="40px">
<p class="h2 py-3 font-weight-bold">Pixelfed</p>
<p style="font-size:14px;font-weight: 500;" class="p-2">The link to this photo or video may be broken, or the post may have been removed.</p>
<p><a href="{{config('app.url')}}" class="font-weight-bold" target="_blank">Visit Pixelfed</a></p>
</div>
</div>
</div>
<script type="text/javascript">window.addEventListener("message",e=>{const t=e.data||{};window.parent&&"setHeight"===t.type&&window.parent.postMessage({type:"setHeight",id:t.id,height:document.getElementsByTagName("html")[0].scrollHeight},"*")});</script>
</body>
</html>

View File

@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<title>{{ $title ?? config('app.name', 'Pixelfed') }}</title>
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{$status->url()}}">
<meta name="medium" content="image">
<meta name="theme-color" content="#10c5f8">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2">
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
<style type="text/css">
body.embed-card {
background: #fff !important;
margin: 0;
padding-bottom: 0;
}
.status-card-embed {
box-shadow: none;
border-radius: 4px;
overflow: hidden;
}
</style>
</head>
<body class="bg-white">
<div class="embed-card">
@php($item = $status)
<div class="card status-card-embed card-md-rounded-0 border">
<div class="card-header d-inline-flex align-items-center bg-white">
<img src="{{$item->profile->avatarUrl()}}" width="32px" height="32px" target="_blank" style="border-radius: 32px;">
<a class="username font-weight-bold pl-2 text-dark" target="_blank" href="{{$item->profile->url()}}">
{{$item->profile->username}}
</a>
</div>
<a href="{{$status->url()}}" target="_blank">
@php($status = $item)
@switch($status->viewType())
@case('photo')
@case('image')
@if($status->is_nsfw)
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}" target="_blank">
<img class="card-img-top" src="{{$status->mediaUrl()}}">
</a>
</details>
@else
<div class="{{$status->firstMedia()->filter_class}}">
<img src="{{$status->mediaUrl()}}" width="100%">
</div>
@endif
@break
@case('album')
@if($status->is_nsfw)
@else
<div id="photo-carousel-wrapper-{{$status->id}}" class="carousel slide carousel-fade" data-ride="carousel">
<ol class="carousel-indicators">
@for($i = 0; $i < $status->media_count; $i++)
<li data-target="#photo-carousel-wrapper-{{$status->id}}" data-slide-to="{{$i}}" class="{{$i == 0 ? 'active' : ''}}"></li>
@endfor
</ol>
<div class="carousel-inner">
@foreach($status->media()->orderBy('order')->get() as $media)
<div class="carousel-item {{$loop->iteration == 1 ? 'active' : ''}}">
<figure class="{{$media->filter_class}}">
<span class="float-right mr-3 badge badge-dark" style="position:fixed;top:8px;right:0;margin-bottom:-20px;">{{$loop->iteration}}/{{$loop->count}}</span>
<img class="d-block w-100" src="{{$media->url()}}" alt="{{$status->caption}}">
</figure>
</div>
@endforeach
</div>
<a class="carousel-control-prev" href="#photo-carousel-wrapper-{{$status->id}}" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#photo-carousel-wrapper-{{$status->id}}" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
@endif
@break
@case('video')
@if($status->is_nsfw)
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video>
</div>
</details>
@else
<div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video>
</div>
@endif
@break
@case('video-album')
@if($status->is_nsfw)
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">CW / NSFW / Hidden Media</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video>
</div>
</details>
@else
<div class="embed-responsive embed-responsive-16by9">
<video class="video" preload="none" controls loop>
<source src="{{$status->firstMedia()->url()}}" type="{{$status->firstMedia()->mime}}">
</video>
</div>
@endif
@break
@endswitch
</a>
@if($layout != 'compact')
<div class="card-body">
<div class="view-more mb-2">
<a class="font-weight-bold" href="{{$status->url()}}" target="_blank">View More on Pixelfed</a>
</div>
<hr>
@if($showLikes)
<div class="likes font-weight-bold pb-2">
<span class="like-count">{{$item->likes_count}}</span> likes
</div>
@endif
<div class="caption">
<p class="my-0">
<span class="username font-weight-bold">
<bdi><a class="text-dark" href="{{$item->profile->url()}}" target="_blank">{{$item->profile->username}}</a></bdi>
</span>
@if($showCaption)
<span class="caption-container">{!! $item->rendered ?? e($item->caption) !!}</span>
@endif
</p>
</div>
</div>
@endif
<div class="card-footer bg-white d-inline-flex justify-content-between align-items-center">
<div class="timestamp">
<p class="small text-uppercase mb-0"><a href="{{$item->url()}}" class="text-muted" target="_blank">{{$item->created_at->diffForHumans()}}</a></p>
</div>
<div>
<a class="small font-weight-bold text-muted pr-1" href="{{config('app.url')}}" target="_blank">{{config('pixelfed.domain.app')}}</a>
<a href="https://pixelfed.org" target="_blank"><img src="/img/pixelfed-icon-color.svg" width="26px"></a>
</div>
</div>
</div>
</div>
<script type="text/javascript">window.addEventListener("message",e=>{const t=e.data||{};window.parent&&"setHeight"===t.type&&window.parent.postMessage({type:"setHeight",id:t.id,height:document.getElementsByTagName("html")[0].scrollHeight},"*")});</script>
<script type="text/javascript">document.querySelectorAll('.caption-container a').forEach(function(i) {i.setAttribute('target', '_blank');});</script>
</body>
</html>

View File

@ -2,10 +2,24 @@
@section('content')
<timeline scope="home"></timeline>
<timeline scope="home" layout="feed"></timeline>
@endsection
@if($layout == 'grid')
@push('styles')
<style type="text/css">
body {
background: #fff !important;
}
.navbar.border-bottom {
border-bottom: none !important;
}
</style>
@endpush
@endif
@push('scripts')
<script type="text/javascript" src="{{ mix('js/timeline.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>

View File

@ -2,10 +2,23 @@
@section('content')
<timeline scope="local"></timeline>
<timeline scope="local" layout="feed"></timeline>
@endsection
@if($layout == 'grid')
@push('styles')
<style type="text/css">
body {
background: #fff !important;
}
.navbar.border-bottom {
border-bottom: none !important;
}
</style>
@endpush
@endif
@push('scripts')
<script type="text/javascript" src="{{ mix('js/timeline.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>

View File

@ -71,6 +71,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::redirect('discover/personal', '/discover');
Route::get('discover', 'DiscoverController@home')->name('discover');
Route::get('discover/loops', 'DiscoverController@showLoops');
Route::get('discover/profiles', 'DiscoverController@profilesDirectory')->name('discover.profiles');
Route::group(['prefix' => 'api'], function () {
Route::get('search', 'SearchController@searchAPI');
@ -117,6 +119,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('config', 'ApiController@siteConfiguration');
Route::get('discover', 'InternalApiController@discover');
Route::get('discover/posts', 'InternalApiController@discoverPosts');
Route::get('discover/profiles', 'DiscoverController@profilesDirectoryApi');
Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
Route::get('likes/profile/{username}/status/{id}', 'PublicApiController@statusLikes');
@ -373,6 +376,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('c/{collection}', 'CollectionController@show');
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
Route::get('p/{username}/{id}/embed', 'StatusController@showEmbed');
Route::get('p/{username}/{id}/edit', 'StatusController@edit');
Route::post('p/{username}/{id}/edit', 'StatusController@editStore');
Route::get('p/{username}/{id}.json', 'StatusController@showObject');

8
webpack.mix.js vendored
View File

@ -29,12 +29,14 @@ mix.js('resources/assets/js/app.js', 'public/js')
.js('resources/assets/js/lib/ace/ace.js', 'public/js')
.js('resources/assets/js/lib/ace/mode-dot.js', 'public/js')
.js('resources/assets/js/lib/ace/theme-monokai.js', 'public/js')
// .js('resources/assets/js/embed.js', 'public')
// .js('resources/assets/js/direct.js', 'public/js')
.js('resources/assets/js/hashtag.js', 'public/js')
.js('resources/assets/js/collectioncompose.js', 'public/js')
.js('resources/assets/js/collections.js', 'public/js')
//.js('resources/assets/js/admin.js', 'public/js')
.js('resources/assets/js/profile-directory.js', 'public/js')
// .js('resources/assets/js/embed.js', 'public')
// .js('resources/assets/js/direct.js', 'public/js')
// .js('resources/assets/js/admin.js', 'public/js')
// .js('resources/assets/js/micro.js', 'public/js')
.extract([
'lodash',