31
.dependabot/config.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
update_configs:
|
||||||
|
- package_manager: "php:composer"
|
||||||
|
directory: "/"
|
||||||
|
update_schedule: "daily"
|
||||||
|
# Supported update schedule: live daily weekly monthly
|
||||||
|
target_branch: "staging"
|
||||||
|
version_requirement_updates: "auto"
|
||||||
|
# Supported version requirements: auto widen_ranges increase_versions increase_versions_if_necessary
|
||||||
|
allowed_updates:
|
||||||
|
- match:
|
||||||
|
dependency_type: "all"
|
||||||
|
# Supported dependency types: all indirect direct production development
|
||||||
|
update_type: "all"
|
||||||
|
# Supported update types: all security
|
||||||
|
|
||||||
|
- package_manager: "javascript"
|
||||||
|
directory: "/"
|
||||||
|
update_schedule: "daily"
|
||||||
|
# Supported update schedule: live daily weekly monthly
|
||||||
|
target_branch: "staging"
|
||||||
|
version_requirement_updates: "auto"
|
||||||
|
# Supported version requirements: auto widen_ranges increase_versions increase_versions_if_necessary
|
||||||
|
allowed_updates:
|
||||||
|
- match:
|
||||||
|
dependency_type: "all"
|
||||||
|
# Supported dependency types: all indirect direct production development
|
||||||
|
update_type: "all"
|
||||||
|
# Supported update types: all security
|
||||||
|
|
143
.env.docker
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
## Crypto
|
||||||
|
APP_KEY=
|
||||||
|
|
||||||
|
## General Settings
|
||||||
|
APP_NAME="Pixelfed Prod"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_URL=https://real.domain
|
||||||
|
APP_DOMAIN="real.domain"
|
||||||
|
ADMIN_DOMAIN="real.domain"
|
||||||
|
SESSION_DOMAIN="real.domain"
|
||||||
|
|
||||||
|
OPEN_REGISTRATION=true
|
||||||
|
ENFORCE_EMAIL_VERIFICATION=false
|
||||||
|
PF_MAX_USERS=1000
|
||||||
|
OAUTH_ENABLED=true
|
||||||
|
|
||||||
|
APP_TIMEZONE=UTC
|
||||||
|
APP_LOCALE=en
|
||||||
|
|
||||||
|
## Pixelfed Tweaks
|
||||||
|
LIMIT_ACCOUNT_SIZE=true
|
||||||
|
MAX_ACCOUNT_SIZE=1000000
|
||||||
|
MAX_PHOTO_SIZE=15000
|
||||||
|
MAX_AVATAR_SIZE=2000
|
||||||
|
MAX_CAPTION_LENGTH=500
|
||||||
|
MAX_BIO_LENGTH=125
|
||||||
|
MAX_NAME_LENGTH=30
|
||||||
|
MAX_ALBUM_LENGTH=4
|
||||||
|
IMAGE_QUALITY=80
|
||||||
|
PF_OPTIMIZE_IMAGES=true
|
||||||
|
PF_OPTIMIZE_VIDEOS=true
|
||||||
|
ADMIN_ENV_EDITOR=false
|
||||||
|
ACCOUNT_DELETION=true
|
||||||
|
ACCOUNT_DELETE_AFTER=false
|
||||||
|
MAX_LINKS_PER_POST=0
|
||||||
|
|
||||||
|
## Instance
|
||||||
|
#INSTANCE_DESCRIPTION=
|
||||||
|
INSTANCE_PUBLIC_HASHTAGS=false
|
||||||
|
#INSTANCE_CONTACT_EMAIL=
|
||||||
|
INSTANCE_PUBLIC_LOCAL_TIMELINE=false
|
||||||
|
#BANNED_USERNAMES=
|
||||||
|
STORIES_ENABLED=false
|
||||||
|
RESTRICTED_INSTANCE=false
|
||||||
|
|
||||||
|
## Mail
|
||||||
|
MAIL_DRIVER=log
|
||||||
|
MAIL_HOST=smtp.mailtrap.io
|
||||||
|
MAIL_PORT=2525
|
||||||
|
MAIL_FROM_ADDRESS="pixelfed@example.com"
|
||||||
|
MAIL_FROM_NAME="Pixelfed"
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
|
||||||
|
## Databases (MySQL)
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=pixelfed
|
||||||
|
DB_USERNAME=pixelfed
|
||||||
|
DB_PASSWORD=pixelfed
|
||||||
|
|
||||||
|
## Databases (Postgres)
|
||||||
|
#DB_CONNECTION=pgsql
|
||||||
|
#DB_HOST=postgres
|
||||||
|
#DB_PORT=5432
|
||||||
|
#DB_DATABASE=pixelfed
|
||||||
|
#DB_USERNAME=postgres
|
||||||
|
#DB_PASSWORD=postgres
|
||||||
|
|
||||||
|
## Cache (Redis)
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
REDIS_SCHEME=tcp
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PASSWORD=null
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_DATABASE=0
|
||||||
|
|
||||||
|
## EXPERIMENTS
|
||||||
|
EXP_LC=false
|
||||||
|
EXP_REC=false
|
||||||
|
EXP_LOOPS=false
|
||||||
|
|
||||||
|
## ActivityPub Federation
|
||||||
|
ACTIVITY_PUB=false
|
||||||
|
AP_REMOTE_FOLLOW=false
|
||||||
|
AP_SHAREDINBOX=false
|
||||||
|
AP_INBOX=false
|
||||||
|
AP_OUTBOX=false
|
||||||
|
ATOM_FEEDS=true
|
||||||
|
NODEINFO=true
|
||||||
|
WEBFINGER=true
|
||||||
|
|
||||||
|
## S3
|
||||||
|
FILESYSTEM_DRIVER=local
|
||||||
|
FILESYSTEM_CLOUD=s3
|
||||||
|
PF_ENABLE_CLOUD=false
|
||||||
|
#AWS_ACCESS_KEY_ID=
|
||||||
|
#AWS_SECRET_ACCESS_KEY=
|
||||||
|
#AWS_DEFAULT_REGION=
|
||||||
|
#AWS_BUCKET=
|
||||||
|
#AWS_URL=
|
||||||
|
#AWS_ENDPOINT=
|
||||||
|
#AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
|
## Horizon
|
||||||
|
HORIZON_DARKMODE=false
|
||||||
|
|
||||||
|
## COSTAR - Confirm Object Sentiment Transform and Reduce
|
||||||
|
PF_COSTAR_ENABLED=false
|
||||||
|
|
||||||
|
# Media
|
||||||
|
MEDIA_EXIF_DATABASE=false
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
|
||||||
|
## Image
|
||||||
|
IMAGE_DRIVER=imagick
|
||||||
|
|
||||||
|
## Broadcasting
|
||||||
|
BROADCAST_DRIVER=log # log driver for local development
|
||||||
|
|
||||||
|
## Cache
|
||||||
|
CACHE_DRIVER=redis
|
||||||
|
|
||||||
|
## Purify
|
||||||
|
RESTRICT_HTML_TYPES=true
|
||||||
|
|
||||||
|
## Queue
|
||||||
|
QUEUE_DRIVER=redis
|
||||||
|
|
||||||
|
## Session
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
|
||||||
|
## Trusted Proxy
|
||||||
|
TRUST_PROXIES="*"
|
||||||
|
|
||||||
|
## Passport
|
||||||
|
#PASSPORT_PRIVATE_KEY=
|
||||||
|
#PASSPORT_PUBLIC_KEY=
|
|
@ -1,52 +0,0 @@
|
||||||
APP_NAME="Pixelfed Prod"
|
|
||||||
APP_ENV=production
|
|
||||||
APP_KEY=
|
|
||||||
APP_DEBUG=false
|
|
||||||
|
|
||||||
APP_URL=http://localhost
|
|
||||||
APP_DOMAIN="localhost"
|
|
||||||
ADMIN_DOMAIN="localhost"
|
|
||||||
SESSION_DOMAIN="localhost"
|
|
||||||
TRUST_PROXIES="*"
|
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
|
||||||
DB_HOST=db
|
|
||||||
DB_PORT=3306
|
|
||||||
DB_DATABASE=pixelfed
|
|
||||||
DB_USERNAME=pixelfed
|
|
||||||
DB_PASSWORD=pixelfed
|
|
||||||
|
|
||||||
BROADCAST_DRIVER=log
|
|
||||||
CACHE_DRIVER=redis
|
|
||||||
SESSION_DRIVER=redis
|
|
||||||
QUEUE_DRIVER=redis
|
|
||||||
|
|
||||||
REDIS_SCHEME=tcp
|
|
||||||
REDIS_HOST=redis
|
|
||||||
REDIS_PASSWORD=null
|
|
||||||
REDIS_PORT=6379
|
|
||||||
|
|
||||||
MAIL_DRIVER=log
|
|
||||||
MAIL_HOST=smtp.mailtrap.io
|
|
||||||
MAIL_PORT=2525
|
|
||||||
MAIL_USERNAME=null
|
|
||||||
MAIL_PASSWORD=null
|
|
||||||
MAIL_ENCRYPTION=null
|
|
||||||
MAIL_FROM_ADDRESS="pixelfed@example.com"
|
|
||||||
MAIL_FROM_NAME="Pixelfed"
|
|
||||||
|
|
||||||
OPEN_REGISTRATION=true
|
|
||||||
ENFORCE_EMAIL_VERIFICATION=true
|
|
||||||
PF_MAX_USERS=1000
|
|
||||||
|
|
||||||
MAX_PHOTO_SIZE=15000
|
|
||||||
MAX_CAPTION_LENGTH=150
|
|
||||||
MAX_ALBUM_LENGTH=4
|
|
||||||
|
|
||||||
ACTIVITY_PUB=false
|
|
||||||
AP_REMOTE_FOLLOW=false
|
|
||||||
AP_INBOX=false
|
|
||||||
PF_COSTAR_ENABLED=false
|
|
||||||
|
|
35
CHANGELOG.md
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.8...dev)
|
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.8...dev)
|
||||||
### Added
|
### Added
|
||||||
|
- Added Profile Following Search ([e3280c11](https://github.com/pixelfed/pixelfed/commit/e3280c11))
|
||||||
|
- Added Trusted Devices to Sudo Mode ([0c82c970](https://github.com/pixelfed/pixelfed/commit/0c82c970))
|
||||||
|
- Added reply modal to posts and timelines ([974e6bda](https://github.com/pixelfed/pixelfed/commit/974e6bda))
|
||||||
|
- Added remote posts and profiles ([95bce31e](https://github.com/pixelfed/pixelfed/commit/95bce31e))
|
||||||
|
- Added Labs deprecation page ([9b215001](https://github.com/pixelfed/pixelfed/commit/9b215001))
|
||||||
|
- Added new landing page ([84e203a9](https://github.com/pixelfed/pixelfed/commit/84e203a9))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Stories on postgres instances ([5ffa71da](https://github.com/pixelfed/pixelfed/commit/5ffa71da))
|
- Stories on postgres instances ([5ffa71da](https://github.com/pixelfed/pixelfed/commit/5ffa71da))
|
||||||
|
@ -23,8 +29,34 @@
|
||||||
- Updated AdminUserController, add moderation method ([a4cf21ea](https://github.com/pixelfed/pixelfed/commit/a4cf21ea))
|
- Updated AdminUserController, add moderation method ([a4cf21ea](https://github.com/pixelfed/pixelfed/commit/a4cf21ea))
|
||||||
- Updated BaseApiController, invalidate session after account deletion ([826978ce](https://github.com/pixelfed/pixelfed/commit/826978ce))
|
- Updated BaseApiController, invalidate session after account deletion ([826978ce](https://github.com/pixelfed/pixelfed/commit/826978ce))
|
||||||
- Updated AdminUserController, add account deletion handler ([9be19ad8](https://github.com/pixelfed/pixelfed/commit/9be19ad8))
|
- Updated AdminUserController, add account deletion handler ([9be19ad8](https://github.com/pixelfed/pixelfed/commit/9be19ad8))
|
||||||
- Updated ContactController, fixes #2042 ([c9057e87](https://github.com/pixelfed/pixelfed/commit/c9057e87))
|
- Updated ContactController, fixes [#2042](https://github.com/pixelfed/pixelfed/issues/2042) ([c9057e87](https://github.com/pixelfed/pixelfed/commit/c9057e87))
|
||||||
- Updated Media model, fix remote media preview ([9947050b](https://github.com/pixelfed/pixelfed/commit/9947050b))
|
- Updated Media model, fix remote media preview ([9947050b](https://github.com/pixelfed/pixelfed/commit/9947050b))
|
||||||
|
- Updated PostComponent, improve likes modal ([664fd272](https://github.com/pixelfed/pixelfed/commit/664fd272))
|
||||||
|
- Updated StoryViewer, preload media ([336571d0](https://github.com/pixelfed/pixelfed/commit/336571d0))
|
||||||
|
- Updated StoryCompose, add expand label for lightbox preview ([fdf59753](https://github.com/pixelfed/pixelfed/commit/fdf59753))
|
||||||
|
- Updated session config, increase session timeout from 2 days to 60 days ([b8795271](https://github.com/pixelfed/pixelfed/commit/b8795271))
|
||||||
|
- Updated WebfingerService, cache lookup ([8b9faf31](https://github.com/pixelfed/pixelfed/commit/8b9faf31))
|
||||||
|
- Updated v1 notifications api, fix optional params ([4e3c952c](https://github.com/pixelfed/pixelfed/commit/4e3c952c))
|
||||||
|
- Updated ApiV1Controller, fix unfavourite bug [#2088](https://github.com/pixelfed/pixelfed/issues/2088) ([3a828522](https://github.com/pixelfed/pixelfed/commit/3a828522))
|
||||||
|
- Updated SharePipeline, fix item relation bug ([b5899648](https://github.com/pixelfed/pixelfed/commit/b5899648))
|
||||||
|
- Updated Profile.vue, add v-once to thumbnails to prevent re-render ([a54685f6](https://github.com/pixelfed/pixelfed/commit/a54685f6))
|
||||||
|
- Updated SearchResults.vue, improve layout ([7e41b4ae](https://github.com/pixelfed/pixelfed/commit/7e41b4ae))
|
||||||
|
- Updated PostMenu.vue, fix styling of list-group ([4c3b0b7d](https://github.com/pixelfed/pixelfed/commit/4c3b0b7d))
|
||||||
|
- Updated PostComponent.vue, update styling ([844566b9](https://github.com/pixelfed/pixelfed/commit/844566b9))
|
||||||
|
- Updated NotificationCard.vue, fix share notifications ([3cb676b1](https://github.com/pixelfed/pixelfed/commit/3cb676b1))
|
||||||
|
- Updated PostComponent.vue, remove like count from title, fixes [#2091](https://github.com/pixelfed/pixelfed/issues/2091) ([6026998c](https://github.com/pixelfed/pixelfed/commit/6026998c))
|
||||||
|
- Updated SearchController, add WebfingerService support ([869b4ff7](https://github.com/pixelfed/pixelfed/commit/869b4ff7))
|
||||||
|
- Updated Profile model, use change_count for version ([0eae9f8b](https://github.com/pixelfed/pixelfed/commit/0eae9f8b))
|
||||||
|
- Updated Timeline.vue, add remote post/profile links ([d4147083](https://github.com/pixelfed/pixelfed/commit/d4147083))
|
||||||
|
- Updated StoryTimelineComponent, added list prop for new timeline layout ([1692a95a](https://github.com/pixelfed/pixelfed/commit/1692a95a))
|
||||||
|
- Updated blank layout, add sharedData js ([4a293ed9](https://github.com/pixelfed/pixelfed/commit/4a293ed9))
|
||||||
|
- Updated oauth api, allow multiple redirect_uris. Fixes #[2106](https://github.com/pixelfed/pixelfed/issues/2106) ([0540a28a](https://github.com/pixelfed/pixelfed/commit/0540a28a))
|
||||||
|
- Updated ActivityPub Outbox, fixes #[2100](https://github.com/pixelfed/pixelfed/issues/2100) ([c84cee5a](https://github.com/pixelfed/pixelfed/commit/c84cee5a))
|
||||||
|
- Updated ApiV1Controller, fixes #[2112](https://github.com/pixelfed/pixelfed/issues/2112) ([324ccd0a](https://github.com/pixelfed/pixelfed/commit/324ccd0a))
|
||||||
|
- Updated StatusTransformer, fixes #[2113](https://github.com/pixelfed/pixelfed/issues/2113) ([eefa6e0d](https://github.com/pixelfed/pixelfed/commit/eefa6e0d))
|
||||||
|
- Updated InternalApiController, limit remote profile ui to remote profiles ([d918a68e](https://github.com/pixelfed/pixelfed/commit/d918a68e))
|
||||||
|
- Updated NotificationCard, fix pagination bug #[2019](https://github.com/pixelfed/pixelfed/issues/2019) ([32beaad5](https://github.com/pixelfed/pixelfed/commit/32beaad5))
|
||||||
|
|
||||||
|
|
||||||
## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8)
|
## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8)
|
||||||
### Added
|
### Added
|
||||||
|
@ -39,6 +71,7 @@
|
||||||
- Fixed TRUST_PROXIES configuration ([#1941](https://github.com/pixelfed/pixelfed/pull/1941))
|
- Fixed TRUST_PROXIES configuration ([#1941](https://github.com/pixelfed/pixelfed/pull/1941))
|
||||||
- Fixed settings page default language ([4223a11e](https://github.com/pixelfed/pixelfed/commit/4223a11e))
|
- Fixed settings page default language ([4223a11e](https://github.com/pixelfed/pixelfed/commit/4223a11e))
|
||||||
- Fixed DeleteAccountPipeline bug that did not use proper media paths ([578d2f35](https://github.com/pixelfed/pixelfed/commit/578d2f35))
|
- Fixed DeleteAccountPipeline bug that did not use proper media paths ([578d2f35](https://github.com/pixelfed/pixelfed/commit/578d2f35))
|
||||||
|
- Fixed mastoapi StatusTransformer, fix in_reply_to_id cast to string instead of int ([6ed00c94](https://github.com/pixelfed/pixelfed/commit/6ed00c94))
|
||||||
|
|
||||||
### Updated
|
### Updated
|
||||||
- Updated presenter components, load fallback image on errors ([273170c5](https://github.com/pixelfed/pixelfed/commit/273170c5))
|
- Updated presenter components, load fallback image on errors ([273170c5](https://github.com/pixelfed/pixelfed/commit/273170c5))
|
||||||
|
|
|
@ -54,7 +54,7 @@ class StoryGC extends Command
|
||||||
{
|
{
|
||||||
$day = now()->day;
|
$day = now()->day;
|
||||||
|
|
||||||
if($day !== 3) {
|
if($day != 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,12 +81,12 @@ class StoryGC extends Command
|
||||||
|
|
||||||
protected function deleteViews()
|
protected function deleteViews()
|
||||||
{
|
{
|
||||||
StoryView::where('created_at', '<', now()->subDays(2))->delete();
|
StoryView::where('created_at', '<', now()->subMinutes(1441))->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function deleteStories()
|
protected function deleteStories()
|
||||||
{
|
{
|
||||||
$stories = Story::where('expires_at', '<', now())->take(50)->get();
|
$stories = Story::where('created_at', '<', now()->subMinutes(1441))->take(50)->get();
|
||||||
|
|
||||||
if($stories->count() == 0) {
|
if($stories->count() == 0) {
|
||||||
exit;
|
exit;
|
||||||
|
|
|
@ -374,10 +374,13 @@ class AccountController extends Controller
|
||||||
public function sudoModeVerify(Request $request)
|
public function sudoModeVerify(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'password' => 'required|string|max:500'
|
'password' => 'required|string|max:500',
|
||||||
|
'trustDevice' => 'nullable'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$password = $request->input('password');
|
$password = $request->input('password');
|
||||||
|
$trustDevice = $request->input('trustDevice') == 'on';
|
||||||
$next = $request->session()->get('redirectNext', '/');
|
$next = $request->session()->get('redirectNext', '/');
|
||||||
if($request->session()->has('sudoModeAttempts')) {
|
if($request->session()->has('sudoModeAttempts')) {
|
||||||
$count = (int) $request->session()->get('sudoModeAttempts');
|
$count = (int) $request->session()->get('sudoModeAttempts');
|
||||||
|
@ -387,6 +390,9 @@ class AccountController extends Controller
|
||||||
}
|
}
|
||||||
if(password_verify($password, $user->password) === true) {
|
if(password_verify($password, $user->password) === true) {
|
||||||
$request->session()->put('sudoMode', time());
|
$request->session()->put('sudoMode', time());
|
||||||
|
if($trustDevice == true) {
|
||||||
|
$request->session()->put('sudoTrustDevice', 1);
|
||||||
|
}
|
||||||
return redirect($next);
|
return redirect($next);
|
||||||
} else {
|
} else {
|
||||||
return redirect()
|
return redirect()
|
||||||
|
|
|
@ -70,11 +70,13 @@ class ApiV1Controller extends Controller
|
||||||
'website' => 'nullable'
|
'website' => 'nullable'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$uris = implode(',', explode('\n', $request->redirect_uris));
|
||||||
|
|
||||||
$client = Passport::client()->forceFill([
|
$client = Passport::client()->forceFill([
|
||||||
'user_id' => null,
|
'user_id' => null,
|
||||||
'name' => e($request->client_name),
|
'name' => e($request->client_name),
|
||||||
'secret' => Str::random(40),
|
'secret' => Str::random(40),
|
||||||
'redirect' => $request->redirect_uris,
|
'redirect' => $uris,
|
||||||
'personal_access_client' => false,
|
'personal_access_client' => false,
|
||||||
'password_client' => false,
|
'password_client' => false,
|
||||||
'revoked' => false,
|
'revoked' => false,
|
||||||
|
@ -828,7 +830,7 @@ class ApiV1Controller extends Controller
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if($like) {
|
if($like) {
|
||||||
$like->delete();
|
$like->forceDelete();
|
||||||
$status->likes_count = $status->likes()->count();
|
$status->likes_count = $status->likes()->count();
|
||||||
$status->save();
|
$status->save();
|
||||||
}
|
}
|
||||||
|
@ -1226,7 +1228,9 @@ class ApiV1Controller extends Controller
|
||||||
$min = $request->input('min_id');
|
$min = $request->input('min_id');
|
||||||
$max = $request->input('max_id');
|
$max = $request->input('max_id');
|
||||||
|
|
||||||
abort_if(!$since && !$min && !$max, 400);
|
if(!$since && !$min && !$max) {
|
||||||
|
$min = 1;
|
||||||
|
}
|
||||||
|
|
||||||
$dir = $since ? '>' : ($min ? '>=' : '<');
|
$dir = $since ? '>' : ($min ? '>=' : '<');
|
||||||
$id = $since ?? $min ?? $max;
|
$id = $since ?? $min ?? $max;
|
||||||
|
@ -1238,6 +1242,9 @@ class ApiV1Controller extends Controller
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$minId = $notifications->min('id');
|
||||||
|
$maxId = $notifications->max('id');
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Collection(
|
$resource = new Fractal\Resource\Collection(
|
||||||
$notifications,
|
$notifications,
|
||||||
new NotificationTransformer()
|
new NotificationTransformer()
|
||||||
|
@ -1247,7 +1254,33 @@ class ApiV1Controller extends Controller
|
||||||
->createData($resource)
|
->createData($resource)
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
return response()->json($res);
|
$baseUrl = config('app.url') . '/api/v1/notifications?';
|
||||||
|
|
||||||
|
if($minId == $maxId) {
|
||||||
|
$minId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($maxId) {
|
||||||
|
$link = '<'.$baseUrl.'max_id='.$maxId.'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($minId) {
|
||||||
|
$link = '<'.$baseUrl.'min_id='.$minId.'>; rel="prev"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($maxId && $minId) {
|
||||||
|
$link = '<'.$baseUrl.'max_id='.$maxId.'>; rel="next",<'.$baseUrl.'min_id='.$minId.'>; rel="prev"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = response()->json($res);
|
||||||
|
|
||||||
|
if(isset($link)) {
|
||||||
|
$res->withHeaders([
|
||||||
|
'Link' => $link,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1655,8 +1688,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->caption = strip_tags($request->input('status'));
|
$status->caption = strip_tags($request->input('status'));
|
||||||
$status->scope = $request->input('visibility');
|
$status->scope = $request->input('visibility', 'public');
|
||||||
$status->visibility = $request->input('visibility');
|
$status->visibility = $request->input('visibility', 'public');
|
||||||
$status->profile_id = $user->profile_id;
|
$status->profile_id = $user->profile_id;
|
||||||
$status->is_nsfw = $user->profile->cw == true ? true : $request->input('sensitive', false);
|
$status->is_nsfw = $user->profile->cw == true ? true : $request->input('sensitive', false);
|
||||||
$status->in_reply_to_id = $parent->id;
|
$status->in_reply_to_id = $parent->id;
|
||||||
|
@ -1690,8 +1723,8 @@ class ApiV1Controller extends Controller
|
||||||
abort(500, 'Invalid media ids');
|
abort(500, 'Invalid media ids');
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->scope = $request->input('visibility');
|
$status->scope = $request->input('visibility', 'public');
|
||||||
$status->visibility = $request->input('visibility');
|
$status->visibility = $request->input('visibility', 'public');
|
||||||
$status->type = StatusController::mimeTypeCheck($mimes);
|
$status->type = StatusController::mimeTypeCheck($mimes);
|
||||||
$status->save();
|
$status->save();
|
||||||
}
|
}
|
||||||
|
@ -1756,7 +1789,9 @@ class ApiV1Controller extends Controller
|
||||||
$share = Status::firstOrCreate([
|
$share = Status::firstOrCreate([
|
||||||
'profile_id' => $user->profile_id,
|
'profile_id' => $user->profile_id,
|
||||||
'reblog_of_id' => $status->id,
|
'reblog_of_id' => $status->id,
|
||||||
'in_reply_to_profile_id' => $status->profile_id
|
'in_reply_to_profile_id' => $status->profile_id,
|
||||||
|
'scope' => 'public',
|
||||||
|
'visibility' => 'public'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if($share->wasRecentlyCreated == true) {
|
if($share->wasRecentlyCreated == true) {
|
||||||
|
|
|
@ -80,9 +80,19 @@ class FederationController extends Controller
|
||||||
abort_if(!config('federation.activitypub.enabled'), 404);
|
abort_if(!config('federation.activitypub.enabled'), 404);
|
||||||
abort_if(!config('federation.activitypub.outbox'), 404);
|
abort_if(!config('federation.activitypub.outbox'), 404);
|
||||||
|
|
||||||
$res = Outbox::get($username);
|
$profile = Profile::whereNull('domain')
|
||||||
|
->whereNull('status')
|
||||||
|
->whereIsPrivate(false)
|
||||||
|
->whereUsername($username)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
return response(json_encode($res))->header('Content-Type', 'application/activity+json');
|
$key = 'ap:outbox:latest_10:pid:' . $profile->id;
|
||||||
|
$ttl = now()->addMinutes(15);
|
||||||
|
$res = Cache::remember($key, $ttl, function() use($profile) {
|
||||||
|
return Outbox::get($profile);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userInbox(Request $request, $username)
|
public function userInbox(Request $request, $username)
|
||||||
|
|
|
@ -343,27 +343,6 @@ class InternalApiController extends Controller
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function remoteProfile(Request $request, $id)
|
|
||||||
{
|
|
||||||
$profile = Profile::whereNull('status')
|
|
||||||
->whereNotNull('domain')
|
|
||||||
->findOrFail($id);
|
|
||||||
|
|
||||||
$settings = [
|
|
||||||
'crawlable' => false,
|
|
||||||
'following' => [
|
|
||||||
'count' => true,
|
|
||||||
'list' => false
|
|
||||||
],
|
|
||||||
'followers' => [
|
|
||||||
'count' => true,
|
|
||||||
'list' => false
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('profile.show', compact('profile', 'settings'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function accountStatuses(Request $request, $id)
|
public function accountStatuses(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
@ -440,6 +419,16 @@ class InternalApiController extends Controller
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function remoteProfile(Request $request, $id)
|
||||||
|
{
|
||||||
|
$profile = Profile::whereNull('status')
|
||||||
|
->whereNotNull('domain')
|
||||||
|
->findOrFail($id);
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return view('profile.remote', compact('profile', 'user'));
|
||||||
|
}
|
||||||
|
|
||||||
public function remoteStatus(Request $request, $profileId, $statusId)
|
public function remoteStatus(Request $request, $profileId, $statusId)
|
||||||
{
|
{
|
||||||
$user = Profile::whereNull('status')
|
$user = Profile::whereNull('status')
|
||||||
|
@ -450,7 +439,7 @@ class InternalApiController extends Controller
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
->findOrFail($statusId);
|
->findOrFail($statusId);
|
||||||
$template = $status->in_reply_to_id ? 'status.reply' : 'status.show';
|
$template = $status->in_reply_to_id ? 'status.reply' : 'status.remote';
|
||||||
return view($template, compact('user', 'status'));
|
return view($template, compact('user', 'status'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class LikeController extends Controller
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
$status = Status::withCount('likes')->findOrFail($request->input('item'));
|
$status = Status::findOrFail($request->input('item'));
|
||||||
|
|
||||||
$count = $status->likes_count;
|
$count = $status->likes_count;
|
||||||
|
|
||||||
|
@ -36,14 +36,16 @@ class LikeController extends Controller
|
||||||
$status->likes_count = $count;
|
$status->likes_count = $count;
|
||||||
$status->save();
|
$status->save();
|
||||||
} else {
|
} else {
|
||||||
$like = new Like();
|
$like = Like::firstOrCreate([
|
||||||
$like->profile_id = $profile->id;
|
'profile_id' => $user->profile_id,
|
||||||
$like->status_id = $status->id;
|
'status_id' => $status->id
|
||||||
$like->save();
|
]);
|
||||||
$count++;
|
if($like->wasRecentlyCreated == true) {
|
||||||
$status->likes_count = $count;
|
$count++;
|
||||||
$status->save();
|
$status->likes_count = $count;
|
||||||
LikePipeline::dispatch($like);
|
$status->save();
|
||||||
|
LikePipeline::dispatch($like);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache::forget('status:'.$status->id.':likedby:userid:'.$user->id);
|
Cache::forget('status:'.$status->id.':likedby:userid:'.$user->id);
|
||||||
|
|
|
@ -20,7 +20,7 @@ use League\Fractal;
|
||||||
use App\Transformer\Api\{
|
use App\Transformer\Api\{
|
||||||
AccountTransformer,
|
AccountTransformer,
|
||||||
RelationshipTransformer,
|
RelationshipTransformer,
|
||||||
StatusTransformer,
|
StatusTransformer
|
||||||
};
|
};
|
||||||
use App\Services\{
|
use App\Services\{
|
||||||
AccountService,
|
AccountService,
|
||||||
|
@ -239,39 +239,6 @@ class PublicApiController extends Controller
|
||||||
$max = $request->input('max_id');
|
$max = $request->input('max_id');
|
||||||
$limit = $request->input('limit') ?? 3;
|
$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')
|
|
||||||
->toArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
// if(Auth::check()) {
|
|
||||||
// // $pid = Auth::user()->profile->id;
|
|
||||||
// // $filters = UserFilter::whereUserId($pid)
|
|
||||||
// // ->whereFilterableType('App\Profile')
|
|
||||||
// // ->whereIn('filter_type', ['mute', 'block'])
|
|
||||||
// // ->pluck('filterable_id')->toArray();
|
|
||||||
// // $filtered = array_merge($private->toArray(), $filters);
|
|
||||||
// $filtered = UserFilterService::filters(Auth::user()->profile_id);
|
|
||||||
// } else {
|
|
||||||
// // $filtered = $private->toArray();
|
|
||||||
// $filtered = [];
|
|
||||||
// }
|
|
||||||
|
|
||||||
$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) {
|
if($min || $max) {
|
||||||
$dir = $min ? '>' : '<';
|
$dir = $min ? '>' : '<';
|
||||||
$id = $min ?? $max;
|
$id = $min ?? $max;
|
||||||
|
@ -295,15 +262,12 @@ class PublicApiController extends Controller
|
||||||
'created_at',
|
'created_at',
|
||||||
'updated_at'
|
'updated_at'
|
||||||
)->where('id', $dir, $id)
|
)->where('id', $dir, $id)
|
||||||
->with('profile', 'hashtags', 'mentions')
|
|
||||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||||
->whereLocal(true)
|
->whereLocal(true)
|
||||||
->whereNotIn('profile_id', $filtered)
|
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get();
|
||||||
//->toSql();
|
|
||||||
} else {
|
} else {
|
||||||
$timeline = Status::select(
|
$timeline = Status::select(
|
||||||
'id',
|
'id',
|
||||||
|
@ -327,11 +291,9 @@ class PublicApiController extends Controller
|
||||||
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||||
->with('profile', 'hashtags', 'mentions')
|
->with('profile', 'hashtags', 'mentions')
|
||||||
->whereLocal(true)
|
->whereLocal(true)
|
||||||
->whereNotIn('profile_id', $filtered)
|
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->simplePaginate($limit);
|
->simplePaginate($limit);
|
||||||
//->toSql();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
|
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
|
||||||
|
@ -499,11 +461,31 @@ class PublicApiController extends Controller
|
||||||
public function accountFollowing(Request $request, $id)
|
public function accountFollowing(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_unless(Auth::check(), 403);
|
abort_unless(Auth::check(), 403);
|
||||||
$profile = Profile::with('user')->whereNull('status')->whereNull('domain')->findOrFail($id);
|
|
||||||
if(Auth::id() != $profile->user_id && $profile->is_private || !$profile->user->settings->show_profile_following) {
|
$profile = Profile::with('user')
|
||||||
return response()->json([]);
|
->whereNull('status')
|
||||||
|
->whereNull('domain')
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
// filter by username
|
||||||
|
$search = $request->input('fbu');
|
||||||
|
$owner = Auth::id() == $profile->user_id;
|
||||||
|
$filter = ($owner == true) && ($search != null);
|
||||||
|
|
||||||
|
abort_if($owner == false && $profile->is_private == true && !$profile->followedBy(Auth::user()->profile), 404);
|
||||||
|
abort_if($profile->user->settings->show_profile_following == false && $owner == false, 404);
|
||||||
|
|
||||||
|
if($search) {
|
||||||
|
abort_if(!$owner, 404);
|
||||||
|
$following = $profile->following()
|
||||||
|
->where('profiles.username', 'like', '%'.$search.'%')
|
||||||
|
->orderByDesc('followers.created_at')
|
||||||
|
->paginate(10);
|
||||||
|
} else {
|
||||||
|
$following = $profile->following()
|
||||||
|
->orderByDesc('followers.created_at')
|
||||||
|
->paginate(10);
|
||||||
}
|
}
|
||||||
$following = $profile->following()->orderByDesc('followers.created_at')->paginate(10);
|
|
||||||
$resource = new Fractal\Resource\Collection($following, new AccountTransformer());
|
$resource = new Fractal\Resource\Collection($following, new AccountTransformer());
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
|
@ -574,8 +556,6 @@ class PublicApiController extends Controller
|
||||||
'updated_at'
|
'updated_at'
|
||||||
)->whereProfileId($profile->id)
|
)->whereProfileId($profile->id)
|
||||||
->whereIn('type', $scope)
|
->whereIn('type', $scope)
|
||||||
->whereLocal(true)
|
|
||||||
->whereNull('uri')
|
|
||||||
->where('id', $dir, $id)
|
->where('id', $dir, $id)
|
||||||
->whereIn('visibility', $visibility)
|
->whereIn('visibility', $visibility)
|
||||||
->latest()
|
->latest()
|
||||||
|
|
|
@ -15,9 +15,15 @@ use App\Transformer\Api\{
|
||||||
HashtagTransformer,
|
HashtagTransformer,
|
||||||
StatusTransformer,
|
StatusTransformer,
|
||||||
};
|
};
|
||||||
|
use App\Services\WebfingerService;
|
||||||
|
|
||||||
class SearchController extends Controller
|
class SearchController extends Controller
|
||||||
{
|
{
|
||||||
|
public $tokens = [];
|
||||||
|
public $term = '';
|
||||||
|
public $hash = '';
|
||||||
|
public $cacheKey = 'api:search:tag:';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
|
@ -28,50 +34,98 @@ class SearchController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'q' => 'required|string|min:3|max:120',
|
'q' => 'required|string|min:3|max:120',
|
||||||
'src' => 'required|string|in:metro',
|
'src' => 'required|string|in:metro',
|
||||||
'v' => 'required|integer|in:1'
|
'v' => 'required|integer|in:1',
|
||||||
|
'scope' => 'required|in:all,hashtag,profile,remote,webfinger'
|
||||||
]);
|
]);
|
||||||
$tag = $request->input('q');
|
|
||||||
$tag = e(urldecode($tag));
|
|
||||||
|
|
||||||
|
$scope = $request->input('scope') ?? 'all';
|
||||||
|
$this->term = e(urldecode($request->input('q')));
|
||||||
|
$this->hash = hash('sha256', $this->term);
|
||||||
|
|
||||||
|
switch ($scope) {
|
||||||
|
case 'all':
|
||||||
|
$this->getHashtags();
|
||||||
|
$this->getPosts();
|
||||||
|
$this->getProfiles();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'hashtag':
|
||||||
|
$this->getHashtags();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'profile':
|
||||||
|
$this->getProfiles();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'webfinger':
|
||||||
|
$this->webfingerSearch();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPosts()
|
||||||
|
{
|
||||||
|
$tag = $this->term;
|
||||||
$hash = hash('sha256', $tag);
|
$hash = hash('sha256', $tag);
|
||||||
$tokens = Cache::remember('api:search:tag:'.$hash, now()->addMinutes(5), function () use ($tag) {
|
if( Helpers::validateUrl($tag) != false &&
|
||||||
$tokens = [];
|
Helpers::validateLocalUrl($tag) != true &&
|
||||||
if(Helpers::validateUrl($tag) != false && config('federation.activitypub.enabled') == true && config('federation.activitypub.remoteFollow') == true) {
|
config('federation.activitypub.enabled') == true &&
|
||||||
abort_if(Helpers::validateLocalUrl($tag), 404);
|
config('federation.activitypub.remoteFollow') == true
|
||||||
$remote = Helpers::fetchFromUrl($tag);
|
) {
|
||||||
if(isset($remote['type']) && in_array($remote['type'], ['Note', 'Person']) == true) {
|
$remote = Helpers::fetchFromUrl($tag);
|
||||||
$type = $remote['type'];
|
if( isset($remote['type']) &&
|
||||||
if($type == 'Person') {
|
$remote['type'] == 'Note') {
|
||||||
$item = Helpers::profileFirstOrNew($tag);
|
$item = Helpers::statusFetch($tag);
|
||||||
$tokens['profiles'] = [[
|
$this->tokens['posts'] = [[
|
||||||
'count' => 1,
|
'count' => 0,
|
||||||
'url' => $item->url(),
|
'url' => $item->url(),
|
||||||
'type' => 'profile',
|
'type' => 'status',
|
||||||
'value' => $item->username,
|
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||||
'tokens' => [$item->username],
|
'tokens' => [$item->caption],
|
||||||
'name' => $item->name,
|
'name' => $item->caption,
|
||||||
'entity' => [
|
'thumb' => $item->thumb(),
|
||||||
'id' => (string) $item->id,
|
]];
|
||||||
'following' => $item->followedBy(Auth::user()->profile),
|
|
||||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
|
||||||
'thumb' => $item->avatarUrl(),
|
|
||||||
'local' => (bool) !$item->domain
|
|
||||||
]
|
|
||||||
]];
|
|
||||||
} else if ($type == 'Note') {
|
|
||||||
$item = Helpers::statusFetch($tag);
|
|
||||||
$tokens['posts'] = [[
|
|
||||||
'count' => 0,
|
|
||||||
'url' => $item->url(),
|
|
||||||
'type' => 'status',
|
|
||||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
|
||||||
'tokens' => [$item->caption],
|
|
||||||
'name' => $item->caption,
|
|
||||||
'thumb' => $item->thumb(),
|
|
||||||
]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
||||||
|
->whereHas('media')
|
||||||
|
->whereNull('in_reply_to_id')
|
||||||
|
->whereNull('reblog_of_id')
|
||||||
|
->whereProfileId(Auth::user()->profile_id)
|
||||||
|
->where('caption', 'like', '%'.$tag.'%')
|
||||||
|
->latest()
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if($posts->count() > 0) {
|
||||||
|
$posts = $posts->map(function($item, $key) {
|
||||||
|
return [
|
||||||
|
'count' => 0,
|
||||||
|
'url' => $item->url(),
|
||||||
|
'type' => 'status',
|
||||||
|
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||||
|
'tokens' => [$item->caption],
|
||||||
|
'name' => $item->caption,
|
||||||
|
'thumb' => $item->thumb(),
|
||||||
|
'filter' => $item->firstMedia()->filter_class
|
||||||
|
];
|
||||||
|
});
|
||||||
|
$this->tokens['posts'] = $posts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHashtags()
|
||||||
|
{
|
||||||
|
$tag = $this->term;
|
||||||
|
$key = $this->cacheKey . 'hashtags:' . $this->hash;
|
||||||
|
$ttl = now()->addMinutes(1);
|
||||||
|
$tokens = Cache::remember($key, $ttl, function() use($tag) {
|
||||||
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
||||||
$hashtags = Hashtag::select('id', 'name', 'slug')
|
$hashtags = Hashtag::select('id', 'name', 'slug')
|
||||||
->where('slug', 'like', '%'.$htag.'%')
|
->where('slug', 'like', '%'.$htag.'%')
|
||||||
|
@ -89,72 +143,93 @@ class SearchController extends Controller
|
||||||
'name' => null,
|
'name' => null,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$tokens['hashtags'] = $tags;
|
return $tags;
|
||||||
}
|
}
|
||||||
return $tokens;
|
|
||||||
});
|
});
|
||||||
$users = Profile::select('domain', 'username', 'name', 'id')
|
$this->tokens['hashtags'] = $tokens;
|
||||||
->whereNull('status')
|
}
|
||||||
->whereNull('domain')
|
|
||||||
->where('id', '!=', Auth::user()->profile->id)
|
|
||||||
->where('username', 'like', '%'.$tag.'%')
|
|
||||||
//->orWhere('remote_url', $tag)
|
|
||||||
->limit(20)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if($users->count() > 0) {
|
protected function getProfiles()
|
||||||
$profiles = $users->map(function ($item, $key) {
|
{
|
||||||
return [
|
$tag = $this->term;
|
||||||
'count' => 0,
|
$remoteKey = $this->cacheKey . 'profiles:remote:' . $this->hash;
|
||||||
'url' => $item->url(),
|
$key = $this->cacheKey . 'profiles:' . $this->hash;
|
||||||
'type' => 'profile',
|
$remoteTtl = now()->addMinutes(15);
|
||||||
'value' => $item->username,
|
$ttl = now()->addHours(2);
|
||||||
'tokens' => [$item->username],
|
if( Helpers::validateUrl($tag) != false &&
|
||||||
'name' => $item->name,
|
Helpers::validateLocalUrl($tag) != true &&
|
||||||
'avatar' => $item->avatarUrl(),
|
config('federation.activitypub.enabled') == true &&
|
||||||
'id' => $item->id,
|
config('federation.activitypub.remoteFollow') == true
|
||||||
'entity' => [
|
) {
|
||||||
'id' => (string) $item->id,
|
$remote = Helpers::fetchFromUrl($tag);
|
||||||
'following' => $item->followedBy(Auth::user()->profile),
|
if( isset($remote['type']) &&
|
||||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
$remote['type'] == 'Person'
|
||||||
'thumb' => $item->avatarUrl(),
|
) {
|
||||||
'local' => (bool) !$item->domain
|
$this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function() use($tag) {
|
||||||
]
|
$item = Helpers::profileFirstOrNew($tag);
|
||||||
];
|
$tokens = [[
|
||||||
});
|
'count' => 1,
|
||||||
if(isset($tokens['profiles'])) {
|
'url' => $item->url(),
|
||||||
array_push($tokens['profiles'], $profiles);
|
'type' => 'profile',
|
||||||
} else {
|
'value' => $item->username,
|
||||||
$tokens['profiles'] = $profiles;
|
'tokens' => [$item->username],
|
||||||
|
'name' => $item->name,
|
||||||
|
'entity' => [
|
||||||
|
'id' => (string) $item->id,
|
||||||
|
'following' => $item->followedBy(Auth::user()->profile),
|
||||||
|
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||||
|
'thumb' => $item->avatarUrl(),
|
||||||
|
'local' => (bool) !$item->domain
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
return $tokens;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
// elseif( Str::containsAll($tag, ['@', '.'])
|
||||||
->whereHas('media')
|
// config('federation.activitypub.enabled') == true &&
|
||||||
->whereNull('in_reply_to_id')
|
// config('federation.activitypub.remoteFollow') == true
|
||||||
->whereNull('reblog_of_id')
|
// ) {
|
||||||
->whereProfileId(Auth::user()->profile->id)
|
// if(substr_count($tag, '@') == 2) {
|
||||||
->where('caption', 'like', '%'.$tag.'%')
|
// $domain = last(explode('@', sub_str($u, 1)));
|
||||||
->latest()
|
// } else {
|
||||||
->limit(10)
|
// $domain = last(explode('@', $u));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
else {
|
||||||
|
$this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) {
|
||||||
|
$users = Profile::select('domain', 'username', 'name', 'id')
|
||||||
|
->whereNull('status')
|
||||||
|
->where('id', '!=', Auth::user()->profile->id)
|
||||||
|
->where('username', 'like', '%'.$tag.'%')
|
||||||
|
->limit(20)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
if($posts->count() > 0) {
|
if($users->count() > 0) {
|
||||||
$posts = $posts->map(function($item, $key) {
|
return $users->map(function ($item, $key) {
|
||||||
return [
|
return [
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
'url' => $item->url(),
|
'url' => $item->url(),
|
||||||
'type' => 'status',
|
'type' => 'profile',
|
||||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
'value' => $item->username,
|
||||||
'tokens' => [$item->caption],
|
'tokens' => [$item->username],
|
||||||
'name' => $item->caption,
|
'name' => $item->name,
|
||||||
'thumb' => $item->thumb(),
|
'avatar' => $item->avatarUrl(),
|
||||||
'filter' => $item->firstMedia()->filter_class
|
'id' => (string) $item->id,
|
||||||
];
|
'entity' => [
|
||||||
|
'id' => (string) $item->id,
|
||||||
|
'following' => $item->followedBy(Auth::user()->profile),
|
||||||
|
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||||
|
'thumb' => $item->avatarUrl(),
|
||||||
|
'local' => (bool) !$item->domain,
|
||||||
|
'post_count' => $item->statuses()->count()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
$tokens['posts'] = $posts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($tokens);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function results(Request $request)
|
public function results(Request $request)
|
||||||
|
@ -166,4 +241,31 @@ class SearchController extends Controller
|
||||||
return view('search.results');
|
return view('search.results');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
protected function webfingerSearch()
|
||||||
|
{
|
||||||
|
$wfs = WebfingerService::lookup($this->term);
|
||||||
|
|
||||||
|
if(empty($wfs)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens['profiles'] = [
|
||||||
|
[
|
||||||
|
'count' => 1,
|
||||||
|
'url' => $wfs['url'],
|
||||||
|
'type' => 'profile',
|
||||||
|
'value' => $wfs['username'],
|
||||||
|
'tokens' => [$wfs['username']],
|
||||||
|
'name' => $wfs['display_name'],
|
||||||
|
'entity' => [
|
||||||
|
'id' => (string) $wfs['id'],
|
||||||
|
'following' => null,
|
||||||
|
'follow_request' => null,
|
||||||
|
'thumb' => $wfs['avatar'],
|
||||||
|
'local' => (bool) $wfs['local']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,16 @@ class SiteController extends Controller
|
||||||
|
|
||||||
public function homeGuest()
|
public function homeGuest()
|
||||||
{
|
{
|
||||||
return view('site.index');
|
$data = Cache::remember('site:landing:data', now()->addHours(3), function() {
|
||||||
|
return [
|
||||||
|
'stats' => [
|
||||||
|
'posts' => App\Util\Lexer\PrettyNumber::convert(App\Status::count()),
|
||||||
|
'likes' => App\Util\Lexer\PrettyNumber::convert(App\Like::count()),
|
||||||
|
'hashtags' => App\Util\Lexer\PrettyNumber::convert(App\StatusHashtag::count())
|
||||||
|
],
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return view('site.index', compact('data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function homeTimeline(Request $request)
|
public function homeTimeline(Request $request)
|
||||||
|
@ -105,7 +114,7 @@ class SiteController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'url' => 'required|url'
|
'url' => 'required|url'
|
||||||
]);
|
]);
|
||||||
$url = urldecode(request()->input('url'));
|
$url = request()->input('url');
|
||||||
return view('site.redirect', compact('url'));
|
return view('site.redirect', compact('url'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class DangerZone
|
||||||
if(!Auth::check()) {
|
if(!Auth::check()) {
|
||||||
return redirect(route('login'));
|
return redirect(route('login'));
|
||||||
}
|
}
|
||||||
if(!$request->is('i/auth/sudo')) {
|
if(!$request->is('i/auth/sudo') && $request->session()->get('sudoTrustDevice') != 1) {
|
||||||
if( !$request->session()->has('sudoMode') ) {
|
if( !$request->session()->has('sudoMode') ) {
|
||||||
$request->session()->put('redirectNext', $request->url());
|
$request->session()->put('redirectNext', $request->url());
|
||||||
return redirect('/i/auth/sudo');
|
return redirect('/i/auth/sudo');
|
||||||
|
|
|
@ -80,7 +80,7 @@ class SharePipeline implements ShouldQueue
|
||||||
$notification->action = 'share';
|
$notification->action = 'share';
|
||||||
$notification->message = $status->shareToText();
|
$notification->message = $status->shareToText();
|
||||||
$notification->rendered = $status->shareToHtml();
|
$notification->rendered = $status->shareToHtml();
|
||||||
$notification->item_id = $status->id;
|
$notification->item_id = $status->reblog_of_id ?? $status->id;
|
||||||
$notification->item_type = "App\Status";
|
$notification->item_type = "App\Status";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
||||||
|
|
|
@ -137,8 +137,7 @@ class Profile extends Model
|
||||||
$url = Cache::remember('avatar:'.$this->id, now()->addYears(1), function () {
|
$url = Cache::remember('avatar:'.$this->id, now()->addYears(1), function () {
|
||||||
$avatar = $this->avatar;
|
$avatar = $this->avatar;
|
||||||
$path = $avatar->media_path;
|
$path = $avatar->media_path;
|
||||||
$version = hash('sha256', $avatar->change_count);
|
$path = "{$path}?v={$avatar->change_count}";
|
||||||
$path = "{$path}?v={$version}";
|
|
||||||
|
|
||||||
return config('app.url') . Storage::url($path);
|
return config('app.url') . Storage::url($path);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use App\Profile;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use App\Util\Webfinger\WebfingerUrl;
|
use App\Util\Webfinger\WebfingerUrl;
|
||||||
use Zttp\Zttp;
|
use Zttp\Zttp;
|
||||||
|
@ -21,6 +22,12 @@ class WebfingerService
|
||||||
|
|
||||||
protected function run($query)
|
protected function run($query)
|
||||||
{
|
{
|
||||||
|
if($profile = Profile::whereUsername($query)->first()) {
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||||
|
return $fractal->createData($resource)->toArray();
|
||||||
|
}
|
||||||
$url = WebfingerUrl::generateWebfingerUrl($query);
|
$url = WebfingerUrl::generateWebfingerUrl($query);
|
||||||
if(!Helpers::validateUrl($url)) {
|
if(!Helpers::validateUrl($url)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -33,7 +33,7 @@ class AccountTransformer extends Fractal\TransformerAbstract
|
||||||
'website' => $profile->website,
|
'website' => $profile->website,
|
||||||
'local' => (bool) $local,
|
'local' => (bool) $local,
|
||||||
'is_admin' => (bool) $is_admin,
|
'is_admin' => (bool) $is_admin,
|
||||||
'created_at' => $profile->created_at->timestamp,
|
'created_at' => $profile->created_at->toJSON(),
|
||||||
'header_bg' => $profile->header_bg
|
'header_bg' => $profile->header_bg
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
||||||
'share' => 'reblog',
|
'share' => 'reblog',
|
||||||
'like' => 'favourite',
|
'like' => 'favourite',
|
||||||
'comment' => 'mention',
|
'comment' => 'mention',
|
||||||
|
'admin.user.modlog.comment' => 'modlog'
|
||||||
];
|
];
|
||||||
return $verbs[$verb];
|
return $verbs[$verb];
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,33 +19,34 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => (string) $status->id,
|
'id' => (string) $status->id,
|
||||||
'uri' => $status->url(),
|
|
||||||
'url' => $status->url(),
|
|
||||||
'in_reply_to_id' => $status->in_reply_to_id,
|
|
||||||
'in_reply_to_account_id' => $status->in_reply_to_profile_id,
|
|
||||||
'reblog' => null,
|
|
||||||
'content' => $status->rendered ?? $status->caption ?? '',
|
|
||||||
'created_at' => $status->created_at->toJSON(),
|
'created_at' => $status->created_at->toJSON(),
|
||||||
'emojis' => [],
|
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
|
||||||
'replies_count' => 0,
|
'in_reply_to_account_id' => $status->in_reply_to_profile_id,
|
||||||
'reblogs_count' => $status->reblogs_count ?? 0,
|
|
||||||
'favourites_count' => $status->likes_count ?? 0,
|
|
||||||
'reblogged' => null,
|
|
||||||
'favourited' => null,
|
|
||||||
'muted' => null,
|
|
||||||
'sensitive' => (bool) $status->is_nsfw,
|
'sensitive' => (bool) $status->is_nsfw,
|
||||||
'spoiler_text' => $status->cw_summary ?? '',
|
'spoiler_text' => $status->cw_summary ?? '',
|
||||||
'visibility' => $status->visibility ?? $status->scope,
|
'visibility' => $status->visibility ?? $status->scope,
|
||||||
'mentions' => [],
|
'language' => 'en',
|
||||||
'tags' => [],
|
'uri' => $status->url(),
|
||||||
'card' => null,
|
'url' => $status->url(),
|
||||||
'poll' => null,
|
'replies_count' => 0,
|
||||||
|
'reblogs_count' => $status->reblogs_count ?? 0,
|
||||||
|
'favourites_count' => $status->likes_count ?? 0,
|
||||||
|
'reblogged' => $status->shared(),
|
||||||
|
'favourited' => $status->liked(),
|
||||||
|
'muted' => false,
|
||||||
|
'bookmarked' => false,
|
||||||
|
'pinned' => false,
|
||||||
|
'content' => $status->rendered ?? $status->caption ?? '',
|
||||||
|
'reblog' => null,
|
||||||
'application' => [
|
'application' => [
|
||||||
'name' => 'web',
|
'name' => 'web',
|
||||||
'website' => null
|
'website' => null
|
||||||
],
|
],
|
||||||
'language' => null,
|
'mentions' => [],
|
||||||
'pinned' => null,
|
'tags' => [],
|
||||||
|
'emojis' => [],
|
||||||
|
'card' => null,
|
||||||
|
'poll' => null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,49 @@
|
||||||
namespace App\Util\ActivityPub;
|
namespace App\Util\ActivityPub;
|
||||||
|
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use App\Status;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
use App\Transformer\ActivityPub\ProfileOutbox;
|
use App\Transformer\ActivityPub\ProfileOutbox;
|
||||||
|
use App\Transformer\ActivityPub\Verb\CreateNote;
|
||||||
|
|
||||||
class Outbox {
|
class Outbox {
|
||||||
|
|
||||||
public static function get($username)
|
public static function get($profile)
|
||||||
{
|
{
|
||||||
abort_if(!config('federation.activitypub.enabled'), 404);
|
abort_if(!config('federation.activitypub.enabled'), 404);
|
||||||
abort_if(!config('federation.activitypub.outbox'), 404);
|
abort_if(!config('federation.activitypub.outbox'), 404);
|
||||||
|
|
||||||
$profile = Profile::whereNull('remote_url')->whereUsername($username)->firstOrFail();
|
|
||||||
if($profile->status != null) {
|
if($profile->status != null) {
|
||||||
return ProfileController::accountCheck($profile);
|
return ProfileController::accountCheck($profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($profile->is_private) {
|
if($profile->is_private) {
|
||||||
return response()->json(['error'=>'403', 'msg' => 'private profile'], 403);
|
return ['error'=>'403', 'msg' => 'private profile'];
|
||||||
}
|
}
|
||||||
$timeline = $profile->statuses()->whereVisibility('public')->orderBy('created_at', 'desc')->paginate(10);
|
|
||||||
|
$timeline = $profile
|
||||||
|
->statuses()
|
||||||
|
->whereVisibility('public')
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->take(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$count = Status::whereProfileId($profile->id)->count();
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$resource = new Fractal\Resource\Item($profile, new ProfileOutbox());
|
$resource = new Fractal\Resource\Collection($timeline, new CreateNote());
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $res['data'];
|
$outbox = [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'_debug' => 'Outbox only supports latest 10 objects, pagination is not supported',
|
||||||
|
'id' => $profile->permalink('/outbox'),
|
||||||
|
'type' => 'OrderedCollection',
|
||||||
|
'totalItems' => $count,
|
||||||
|
'orderedItems' => $res['data']
|
||||||
|
];
|
||||||
|
return $outbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
84
composer.lock
generated
|
@ -147,12 +147,12 @@
|
||||||
"version": "v0.11.4",
|
"version": "v0.11.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/barryvdh/laravel-cors.git",
|
"url": "https://github.com/fruitcake/laravel-cors.git",
|
||||||
"reference": "03492f1a3bc74a05de23f93b94ac7cc5c173eec9"
|
"reference": "03492f1a3bc74a05de23f93b94ac7cc5c173eec9"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/barryvdh/laravel-cors/zipball/03492f1a3bc74a05de23f93b94ac7cc5c173eec9",
|
"url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/03492f1a3bc74a05de23f93b94ac7cc5c173eec9",
|
||||||
"reference": "03492f1a3bc74a05de23f93b94ac7cc5c173eec9",
|
"reference": "03492f1a3bc74a05de23f93b94ac7cc5c173eec9",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
|
@ -2706,6 +2706,7 @@
|
||||||
"bcmath",
|
"bcmath",
|
||||||
"math"
|
"math"
|
||||||
],
|
],
|
||||||
|
"abandoned": "brick/math",
|
||||||
"time": "2017-02-16T16:54:46+00:00"
|
"time": "2017-02-16T16:54:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5135,16 +5136,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/http-foundation",
|
"name": "symfony/http-foundation",
|
||||||
"version": "v4.4.1",
|
"version": "v4.4.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/http-foundation.git",
|
"url": "https://github.com/symfony/http-foundation.git",
|
||||||
"reference": "8bccc59e61b41963d14c3dbdb23181e5c932a1d5"
|
"reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/8bccc59e61b41963d14c3dbdb23181e5c932a1d5",
|
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/62f92509c9abfd1f73e17b8cf1b72c0bdac6611b",
|
||||||
"reference": "8bccc59e61b41963d14c3dbdb23181e5c932a1d5",
|
"reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -5186,7 +5187,7 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony HttpFoundation Component",
|
"description": "Symfony HttpFoundation Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2019-11-28T13:33:56+00:00"
|
"time": "2020-03-30T14:07:33+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/http-kernel",
|
"name": "symfony/http-kernel",
|
||||||
|
@ -5280,16 +5281,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/mime",
|
"name": "symfony/mime",
|
||||||
"version": "v5.0.1",
|
"version": "v5.0.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/mime.git",
|
"url": "https://github.com/symfony/mime.git",
|
||||||
"reference": "0e6a4ced216e49d457eddcefb61132173a876d79"
|
"reference": "481b7d6da88922fb1e0d86a943987722b08f3955"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/mime/zipball/0e6a4ced216e49d457eddcefb61132173a876d79",
|
"url": "https://api.github.com/repos/symfony/mime/zipball/481b7d6da88922fb1e0d86a943987722b08f3955",
|
||||||
"reference": "0e6a4ced216e49d457eddcefb61132173a876d79",
|
"reference": "481b7d6da88922fb1e0d86a943987722b08f3955",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -5338,7 +5339,7 @@
|
||||||
"mime",
|
"mime",
|
||||||
"mime-type"
|
"mime-type"
|
||||||
],
|
],
|
||||||
"time": "2019-11-30T14:12:50+00:00"
|
"time": "2020-03-27T16:56:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
|
@ -5459,22 +5460,22 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-idn",
|
"name": "symfony/polyfill-intl-idn",
|
||||||
"version": "v1.13.1",
|
"version": "v1.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||||
"reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46"
|
"reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
|
||||||
"reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46",
|
"reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.3",
|
"php": ">=5.3.3",
|
||||||
"symfony/polyfill-mbstring": "^1.3",
|
"symfony/polyfill-mbstring": "^1.3",
|
||||||
"symfony/polyfill-php72": "^1.9"
|
"symfony/polyfill-php72": "^1.10"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-intl": "For best performance"
|
"ext-intl": "For best performance"
|
||||||
|
@ -5482,7 +5483,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.13-dev"
|
"dev-master": "1.15-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -5517,20 +5518,20 @@
|
||||||
"portable",
|
"portable",
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"time": "2019-11-27T13:56:44+00:00"
|
"time": "2020-03-09T19:04:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.13.1",
|
"version": "v1.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
|
"reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
|
||||||
"reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
|
"reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -5542,7 +5543,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.13-dev"
|
"dev-master": "1.15-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -5576,7 +5577,7 @@
|
||||||
"portable",
|
"portable",
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"time": "2019-11-27T14:18:11+00:00"
|
"time": "2020-03-09T19:04:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php56",
|
"name": "symfony/polyfill-php56",
|
||||||
|
@ -5636,16 +5637,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php72",
|
"name": "symfony/polyfill-php72",
|
||||||
"version": "v1.13.1",
|
"version": "v1.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php72.git",
|
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||||
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038"
|
"reference": "37b0976c78b94856543260ce09b460a7bc852747"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038",
|
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747",
|
||||||
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038",
|
"reference": "37b0976c78b94856543260ce09b460a7bc852747",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -5654,7 +5655,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.13-dev"
|
"dev-master": "1.15-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -5687,7 +5688,7 @@
|
||||||
"portable",
|
"portable",
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"time": "2019-11-27T13:56:44+00:00"
|
"time": "2020-02-27T09:26:54+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php73",
|
"name": "symfony/polyfill-php73",
|
||||||
|
@ -6477,6 +6478,7 @@
|
||||||
"psr",
|
"psr",
|
||||||
"psr-7"
|
"psr-7"
|
||||||
],
|
],
|
||||||
|
"abandoned": "laminas/laminas-diactoros",
|
||||||
"time": "2019-11-13T19:16:13+00:00"
|
"time": "2019-11-13T19:16:13+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -6487,19 +6489,19 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
||||||
"reference": "35638e4f5e714a12dec5ca062e68c625c1309c1c"
|
"reference": "7fa9ff7945f44f10c76d7bc46f508f4cf593f4c5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/35638e4f5e714a12dec5ca062e68c625c1309c1c",
|
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/7fa9ff7945f44f10c76d7bc46f508f4cf593f4c5",
|
||||||
"reference": "35638e4f5e714a12dec5ca062e68c625c1309c1c",
|
"reference": "7fa9ff7945f44f10c76d7bc46f508f4cf593f4c5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/routing": "^5.5|^6",
|
"illuminate/routing": "^5.5|^6|^7",
|
||||||
"illuminate/session": "^5.5|^6",
|
"illuminate/session": "^5.5|^6|^7",
|
||||||
"illuminate/support": "^5.5|^6",
|
"illuminate/support": "^5.5|^6|^7",
|
||||||
"maximebf/debugbar": "^1.15",
|
"maximebf/debugbar": "^1.15.1",
|
||||||
"php": ">=7.0",
|
"php": ">=7.0",
|
||||||
"symfony/debug": "^3|^4|^5",
|
"symfony/debug": "^3|^4|^5",
|
||||||
"symfony/finder": "^3|^4|^5"
|
"symfony/finder": "^3|^4|^5"
|
||||||
|
@ -6547,7 +6549,7 @@
|
||||||
"profiler",
|
"profiler",
|
||||||
"webprofiler"
|
"webprofiler"
|
||||||
],
|
],
|
||||||
"time": "2019-12-07T09:33:13+00:00"
|
"time": "2020-03-24T06:32:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/ca-bundle",
|
"name": "composer/ca-bundle",
|
||||||
|
@ -7127,8 +7129,8 @@
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Filipe Dobreira",
|
"name": "Filipe Dobreira",
|
||||||
"role": "Developer",
|
"homepage": "https://github.com/filp",
|
||||||
"homepage": "https://github.com/filp"
|
"role": "Developer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "php error handling for cool kids",
|
"description": "php error handling for cool kids",
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
// Enable or disable partialcache alltogether
|
|
||||||
'enabled' => true,
|
|
||||||
|
|
||||||
// The name of the blade directive to register
|
|
||||||
'directive' => 'cache',
|
|
||||||
|
|
||||||
// The base key that used for cache items
|
|
||||||
'key' => 'partialcache',
|
|
||||||
|
|
||||||
// The default cache duration in minutes, set null to remember forever
|
|
||||||
'default_duration' => null,
|
|
||||||
|
|
||||||
];
|
|
|
@ -29,7 +29,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'lifetime' => env('SESSION_LIFETIME', 2880),
|
'lifetime' => env('SESSION_LIFETIME', 86400),
|
||||||
|
|
||||||
'expire_on_close' => false,
|
'expire_on_close' => false,
|
||||||
|
|
||||||
|
|
35
contrib/docker-nginx.conf
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
upstream fe {
|
||||||
|
server 127.0.0.1:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name real.domain;
|
||||||
|
listen [::]:443 ssl ipv6only=on;
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/real.domain/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/real.domain/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
|
||||||
|
proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_pass http://fe/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
if ($host = real.domain) {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name real.domain;
|
||||||
|
return 404;
|
||||||
|
}
|
|
@ -1,25 +1,79 @@
|
||||||
FROM php:7.4-apache-buster
|
FROM php:7.4-apache-buster
|
||||||
|
|
||||||
ARG COMPOSER_VERSION="1.9.1"
|
# Use the default production configuration
|
||||||
ARG COMPOSER_CHECKSUM="1f210b9037fcf82670d75892dfc44400f13fe9ada7af9e787f93e50e3b764111"
|
COPY contrib/docker/php.production.ini "$PHP_INI_DIR/php.ini"
|
||||||
|
|
||||||
RUN apt-get update \
|
# Install Composer
|
||||||
&& apt-get install -y --no-install-recommends apt-utils \
|
ENV COMPOSER_VERSION 1.9.2
|
||||||
&& apt-get install -y --no-install-recommends git gosu ffmpeg \
|
ENV COMPOSER_HOME /var/www/.composer
|
||||||
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \
|
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer \
|
||||||
libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-6 \
|
&& curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig \
|
||||||
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev mariadb-client\
|
&& php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" \
|
||||||
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \
|
&& php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --version=${COMPOSER_VERSION} && rm -rf /tmp/composer-setup.php
|
||||||
&& locale-gen && update-locale \
|
|
||||||
&& docker-php-source extract \
|
# Update OS Packages
|
||||||
&& docker-php-ext-configure gd \
|
RUN apt-get update
|
||||||
|
|
||||||
|
# Install OS Packages
|
||||||
|
RUN apt-get install -y --no-install-recommends apt-utils
|
||||||
|
RUN apt-get install -y --no-install-recommends \
|
||||||
|
## Standard
|
||||||
|
locales locales-all \
|
||||||
|
git \
|
||||||
|
gosu \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
libzip-dev \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
## Image Optimization
|
||||||
|
optipng \
|
||||||
|
pngquant \
|
||||||
|
jpegoptim \
|
||||||
|
gifsicle \
|
||||||
|
## Image Processing
|
||||||
|
libjpeg62-turbo-dev \
|
||||||
|
libpng-dev \
|
||||||
|
# Required for GD
|
||||||
|
libxpm4 \
|
||||||
|
libxpm-dev \
|
||||||
|
libwebp6 \
|
||||||
|
libwebp-dev \
|
||||||
|
## Video Processing
|
||||||
|
ffmpeg
|
||||||
|
|
||||||
|
# Update Local data
|
||||||
|
RUN sed -i '/en_US/s/^#//g' /etc/locale.gen && locale-gen && update-locale
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN docker-php-source extract
|
||||||
|
|
||||||
|
#PHP Imagemagick extensions
|
||||||
|
RUN apt-get install -y --no-install-recommends libmagickwand-dev
|
||||||
|
RUN pecl install imagick
|
||||||
|
RUN docker-php-ext-enable imagick
|
||||||
|
|
||||||
|
# PHP GD extensions
|
||||||
|
RUN docker-php-ext-configure gd \
|
||||||
--with-freetype \
|
--with-freetype \
|
||||||
--with-jpeg \
|
--with-jpeg \
|
||||||
--with-webp \
|
--with-webp \
|
||||||
--with-xpm \
|
--with-xpm
|
||||||
&& docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite pcntl gd exif bcmath intl zip curl \
|
RUN docker-php-ext-install "-j$(nproc) gd"
|
||||||
&& docker-php-ext-enable pcntl gd exif zip curl \
|
|
||||||
&& a2enmod rewrite remoteip \
|
#PHP Redis extensions
|
||||||
|
RUN pecl install redis
|
||||||
|
RUN docker-php-ext-enable redis
|
||||||
|
|
||||||
|
#PHP Database extensions
|
||||||
|
RUN apt-get install -y --no-install-recommends libpq-dev libsqlite3-dev
|
||||||
|
RUN docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite
|
||||||
|
|
||||||
|
#PHP extensions (dependencies)
|
||||||
|
RUN docker-php-ext-configure intl
|
||||||
|
RUN docker-php-ext-install "-j$(nproc) intl bcmath zip pcntl exif curl"
|
||||||
|
|
||||||
|
#APACHE Bootstrap
|
||||||
|
RUN a2enmod rewrite remoteip \
|
||||||
&& {\
|
&& {\
|
||||||
echo RemoteIPHeader X-Real-IP ;\
|
echo RemoteIPHeader X-Real-IP ;\
|
||||||
echo RemoteIPTrustedProxy 10.0.0.0/8 ;\
|
echo RemoteIPTrustedProxy 10.0.0.0/8 ;\
|
||||||
|
@ -27,45 +81,26 @@ RUN apt-get update \
|
||||||
echo RemoteIPTrustedProxy 192.168.0.0/16 ;\
|
echo RemoteIPTrustedProxy 192.168.0.0/16 ;\
|
||||||
echo SetEnvIf X-Forwarded-Proto "https" HTTPS=on ;\
|
echo SetEnvIf X-Forwarded-Proto "https" HTTPS=on ;\
|
||||||
} > /etc/apache2/conf-available/remoteip.conf \
|
} > /etc/apache2/conf-available/remoteip.conf \
|
||||||
&& a2enconf remoteip \
|
&& a2enconf remoteip
|
||||||
&& curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /usr/bin/composer \
|
|
||||||
&& echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \
|
#Cleanup
|
||||||
&& chmod 755 /usr/bin/composer \
|
RUN docker-php-source delete
|
||||||
&& apt-get autoremove --purge -y \
|
RUN apt-get autoremove --purge -y
|
||||||
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libvpx-dev libmagickwand-dev \
|
RUN apt-get clean
|
||||||
&& rm -rf /var/cache/apt \
|
RUN rm -rf /var/cache/apt
|
||||||
&& docker-php-source delete
|
RUN rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
|
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
|
||||||
|
|
||||||
COPY . /var/www/
|
COPY . /var/www/
|
||||||
|
|
||||||
WORKDIR /var/www/
|
WORKDIR /var/www/
|
||||||
RUN cp -r storage storage.skel \
|
|
||||||
&& cp contrib/docker/php.ini /usr/local/etc/php/conf.d/pixelfed.ini \
|
RUN cp -r storage storage.skel
|
||||||
&& composer global require hirak/prestissimo --no-interaction --no-suggest --prefer-dist \
|
RUN composer global require hirak/prestissimo --no-interaction --no-suggest --prefer-dist
|
||||||
&& composer install --prefer-dist --no-interaction \
|
RUN composer install --prefer-dist --no-interaction --no-ansi --optimize-autoloader
|
||||||
&& composer global remove hirak/prestissimo \
|
RUN composer global remove hirak/prestissimo
|
||||||
&& rm -rf html && ln -s public html
|
RUN rm -rf html && ln -s public html
|
||||||
|
|
||||||
VOLUME /var/www/storage /var/www/bootstrap
|
VOLUME /var/www/storage /var/www/bootstrap
|
||||||
|
|
||||||
ENV APP_ENV=production \
|
CMD ["/var/www/contrib/docker/start.apache.sh"]
|
||||||
APP_DEBUG=false \
|
|
||||||
LOG_CHANNEL=stderr \
|
|
||||||
DB_CONNECTION=mysql \
|
|
||||||
DB_PORT=3306 \
|
|
||||||
DB_HOST=db \
|
|
||||||
BROADCAST_DRIVER=log \
|
|
||||||
QUEUE_DRIVER=redis \
|
|
||||||
HORIZON_PREFIX=horizon-pixelfed \
|
|
||||||
REDIS_HOST=redis \
|
|
||||||
SESSION_SECURE_COOKIE=true \
|
|
||||||
API_BASE="/api/1/" \
|
|
||||||
API_SEARCH="/api/search" \
|
|
||||||
OPEN_REGISTRATION=true \
|
|
||||||
ENFORCE_EMAIL_VERIFICATION=true \
|
|
||||||
REMOTE_FOLLOW=false \
|
|
||||||
ACTIVITY_PUB=false
|
|
||||||
|
|
||||||
CMD /var/www/contrib/docker/start.sh
|
|
||||||
|
|
|
@ -1,66 +1,94 @@
|
||||||
FROM php:7.4-fpm-buster
|
FROM php:7.4-fpm-buster
|
||||||
ARG COMPOSER_VERSION="1.9.1"
|
|
||||||
ARG COMPOSER_CHECKSUM="1f210b9037fcf82670d75892dfc44400f13fe9ada7af9e787f93e50e3b764111"
|
|
||||||
|
|
||||||
RUN apt-get update \
|
# Use the default production configuration
|
||||||
&& apt-get install -y --no-install-recommends apt-utils \
|
COPY contrib/docker/php.production.ini "$PHP_INI_DIR/php.ini"
|
||||||
&& apt-get install -y --no-install-recommends git gosu ffmpeg \
|
|
||||||
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \
|
# Install Composer
|
||||||
libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-6 \
|
ENV COMPOSER_VERSION 1.9.2
|
||||||
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev mariadb-client\
|
ENV COMPOSER_HOME /var/www/.composer
|
||||||
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \
|
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer \
|
||||||
&& locale-gen && update-locale \
|
&& curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig \
|
||||||
&& docker-php-source extract \
|
&& php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" \
|
||||||
&& docker-php-ext-configure gd \
|
&& php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --version=${COMPOSER_VERSION} && rm -rf /tmp/composer-setup.php
|
||||||
|
|
||||||
|
# Update OS Packages
|
||||||
|
RUN apt-get update
|
||||||
|
|
||||||
|
# Install OS Packages
|
||||||
|
RUN apt-get install -y --no-install-recommends apt-utils
|
||||||
|
RUN apt-get install -y --no-install-recommends \
|
||||||
|
## Standard
|
||||||
|
locales locales-all \
|
||||||
|
git \
|
||||||
|
gosu \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
libzip-dev \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
## Image Optimization
|
||||||
|
optipng \
|
||||||
|
pngquant \
|
||||||
|
jpegoptim \
|
||||||
|
gifsicle \
|
||||||
|
## Image Processing
|
||||||
|
libjpeg62-turbo-dev \
|
||||||
|
libpng-dev \
|
||||||
|
# Required for GD
|
||||||
|
libxpm4 \
|
||||||
|
libxpm-dev \
|
||||||
|
libwebp6 \
|
||||||
|
libwebp-dev \
|
||||||
|
## Video Processing
|
||||||
|
ffmpeg
|
||||||
|
|
||||||
|
# Update Local data
|
||||||
|
RUN sed -i '/en_US/s/^#//g' /etc/locale.gen && locale-gen && update-locale
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN docker-php-source extract
|
||||||
|
|
||||||
|
#PHP Imagemagick extensions
|
||||||
|
RUN apt-get install -y --no-install-recommends libmagickwand-dev
|
||||||
|
RUN pecl install imagick
|
||||||
|
RUN docker-php-ext-enable imagick
|
||||||
|
|
||||||
|
# PHP GD extensions
|
||||||
|
RUN docker-php-ext-configure gd \
|
||||||
--with-freetype \
|
--with-freetype \
|
||||||
--with-jpeg \
|
--with-jpeg \
|
||||||
--with-webp \
|
--with-webp \
|
||||||
--with-xpm \
|
--with-xpm
|
||||||
&& docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite pcntl gd exif bcmath intl zip curl \
|
RUN docker-php-ext-install -j$(nproc) gd
|
||||||
&& docker-php-ext-enable pcntl gd exif zip curl \
|
|
||||||
&& curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /usr/bin/composer \
|
#PHP Redis extensions
|
||||||
&& echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \
|
RUN pecl install redis
|
||||||
&& chmod 755 /usr/bin/composer \
|
RUN docker-php-ext-enable redis
|
||||||
&& apt-get autoremove --purge -y \
|
|
||||||
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libvpx-dev libmagickwand-dev \
|
#PHP Database extensions
|
||||||
&& rm -rf /var/cache/apt \
|
RUN apt-get install -y --no-install-recommends libpq-dev libsqlite3-dev
|
||||||
&& docker-php-source delete
|
RUN docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite
|
||||||
|
|
||||||
|
#PHP extensions (dependencies)
|
||||||
|
RUN docker-php-ext-configure intl
|
||||||
|
RUN docker-php-ext-install -j$(nproc) intl bcmath zip pcntl exif curl
|
||||||
|
|
||||||
|
#Cleanup
|
||||||
|
RUN docker-php-source delete
|
||||||
|
RUN apt-get autoremove --purge -y
|
||||||
|
RUN rm -rf /var/cache/apt
|
||||||
|
RUN rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
|
ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
|
||||||
|
|
||||||
COPY . /var/www/
|
COPY . /var/www/
|
||||||
|
|
||||||
WORKDIR /var/www/
|
WORKDIR /var/www/
|
||||||
RUN cp -r storage storage.skel \
|
|
||||||
&& cp contrib/docker/php.ini /usr/local/etc/php/conf.d/pixelfed.ini \
|
RUN cp -r storage storage.skel
|
||||||
&& composer global require hirak/prestissimo --no-interaction --no-suggest --prefer-dist \
|
RUN composer global require hirak/prestissimo --no-interaction --no-suggest --prefer-dist
|
||||||
&& composer install --prefer-dist --no-interaction \
|
RUN composer install --prefer-dist --no-interaction --no-ansi --optimize-autoloader
|
||||||
&& composer global remove hirak/prestissimo \
|
RUN composer global remove hirak/prestissimo
|
||||||
&& rm -rf html && ln -s public html
|
RUN rm -rf html && ln -s public html
|
||||||
|
|
||||||
VOLUME /var/www/storage /var/www/bootstrap
|
VOLUME /var/www/storage /var/www/bootstrap
|
||||||
|
|
||||||
ENV APP_ENV=production \
|
CMD ["/var/www/contrib/docker/start.fpm.sh"]
|
||||||
APP_DEBUG=false \
|
|
||||||
LOG_CHANNEL=stderr \
|
|
||||||
DB_CONNECTION=mysql \
|
|
||||||
DB_PORT=3306 \
|
|
||||||
DB_HOST=db \
|
|
||||||
BROADCAST_DRIVER=log \
|
|
||||||
QUEUE_DRIVER=redis \
|
|
||||||
HORIZON_PREFIX=horizon-pixelfed \
|
|
||||||
REDIS_HOST=redis \
|
|
||||||
SESSION_SECURE_COOKIE=true \
|
|
||||||
API_BASE="/api/1/" \
|
|
||||||
API_SEARCH="/api/search" \
|
|
||||||
OPEN_REGISTRATION=true \
|
|
||||||
ENFORCE_EMAIL_VERIFICATION=true \
|
|
||||||
REMOTE_FOLLOW=false \
|
|
||||||
ACTIVITY_PUB=false
|
|
||||||
|
|
||||||
CMD cp -r storage.skel/* storage/ \
|
|
||||||
&& chown -R www-data:www-data storage/ \
|
|
||||||
&& php artisan storage:link \
|
|
||||||
&& php artisan migrate --force \
|
|
||||||
&& php artisan update \
|
|
||||||
&& exec php-fpm
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
file_uploads = On
|
|
||||||
memory_limit = 128M
|
|
||||||
upload_max_filesize = 64M
|
|
||||||
post_max_size = 64M
|
|
||||||
max_execution_time = 600
|
|
1916
contrib/docker/php.production.ini
Normal file
15
contrib/docker/start.apache.sh
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create the storage tree if needed and fix permissions
|
||||||
|
cp -r storage.skel/* storage/
|
||||||
|
chown -R www-data:www-data storage/ bootstrap/
|
||||||
|
|
||||||
|
# Refresh the environment
|
||||||
|
php artisan storage:link
|
||||||
|
php artisan horizon:assets
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan config:cache
|
||||||
|
|
||||||
|
# Finally run Apache
|
||||||
|
exec apache2-foreground
|
15
contrib/docker/start.fpm.sh
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create the storage tree if needed and fix permissions
|
||||||
|
cp -r storage.skel/* storage/
|
||||||
|
chown -R www-data:www-data storage/ bootstrap/
|
||||||
|
|
||||||
|
# Refresh the environment
|
||||||
|
php artisan storage:link
|
||||||
|
php artisan horizon:assets
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan config:cache
|
||||||
|
|
||||||
|
# Finally run FPM
|
||||||
|
exec php-fpm
|
|
@ -1,26 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Create the storage tree if needed and fix permissions
|
|
||||||
cp -r storage.skel/* storage/
|
|
||||||
chown -R www-data:www-data storage/ bootstrap/
|
|
||||||
|
|
||||||
# Refresh the environment
|
|
||||||
php artisan storage:link
|
|
||||||
php artisan horizon:assets
|
|
||||||
php artisan route:cache
|
|
||||||
php artisan view:cache
|
|
||||||
php artisan config:cache
|
|
||||||
|
|
||||||
# Migrate database if the app was upgraded
|
|
||||||
# gosu www-data:www-data php artisan migrate --force
|
|
||||||
|
|
||||||
# Run other specific migratins if required
|
|
||||||
# gosu www-data:www-data php artisan update
|
|
||||||
|
|
||||||
# Run a worker if it is set as embedded
|
|
||||||
if [ "$HORIZON_EMBED" = "true" ]; then
|
|
||||||
gosu www-data:www-data php artisan horizon &
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Finally run Apache
|
|
||||||
exec apache2-foreground
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddFetchedAtToProfilesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_fetched_at')->nullable();
|
||||||
|
$table->unsignedInteger('status_count')->default(0)->nullable();
|
||||||
|
$table->unsignedInteger('followers_count')->default(0)->nullable();
|
||||||
|
$table->unsignedInteger('following_count')->default(0)->nullable();
|
||||||
|
$table->string('webfinger')->unique()->nullable()->index();
|
||||||
|
$table->string('avatar_url')->nullable();
|
||||||
|
$table->dropColumn('keybase_proof');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('profiles', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_fetched_at');
|
||||||
|
$table->dropColumn('status_count');
|
||||||
|
$table->dropColumn('followers_count');
|
||||||
|
$table->dropColumn('following_count');
|
||||||
|
$table->dropColumn('webfinger');
|
||||||
|
$table->dropColumn('avatar_url');
|
||||||
|
$table->text('keybase_proof')->nullable()->after('post_layout');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,50 +12,49 @@ version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
|
## App and Worker
|
||||||
app:
|
app:
|
||||||
# Comment to use dockerhub image
|
# Comment to use dockerhub image
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: contrib/docker/Dockerfile.apache
|
dockerfile: contrib/docker/Dockerfile.apache
|
||||||
#dockerfile: contrib/docker/Dockerfile.fpm
|
|
||||||
image: pixelfed
|
image: pixelfed
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
## If you have a traefik running, uncomment this to expose Pixelfed
|
|
||||||
# labels:
|
|
||||||
# - traefik.enable=true
|
|
||||||
# - traefik.frontend.rule=Host:your.url
|
|
||||||
# - traefik.port=80
|
|
||||||
## If you have a standard reverse proxy, uncommit this to expose Pixelfed
|
|
||||||
# ports:
|
|
||||||
# - "127.0.0.1:8080:80"
|
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env.docker
|
||||||
volumes:
|
volumes:
|
||||||
- "app-storage:/var/www/storage"
|
- "app-storage:/var/www/storage"
|
||||||
- "app-bootstrap:/var/www/bootstrap"
|
- "app-bootstrap:/var/www/bootstrap"
|
||||||
- "./.env:/var/www/.env"
|
- "./.env.docker:/var/www/.env"
|
||||||
networks:
|
networks:
|
||||||
- external
|
- external
|
||||||
- internal
|
- internal
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
|
||||||
worker: # Comment this whole block if HORIZON_EMBED is true.
|
worker:
|
||||||
# Comment to use dockerhub image
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: contrib/docker/Dockerfile.apache
|
dockerfile: contrib/docker/Dockerfile.apache
|
||||||
#dockerfile: contrib/docker/Dockerfile.fpm
|
|
||||||
image: pixelfed
|
image: pixelfed
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env.docker
|
||||||
volumes:
|
volumes:
|
||||||
- "app-storage:/var/www/storage"
|
- "app-storage:/var/www/storage"
|
||||||
- "app-bootstrap:/var/www/bootstrap"
|
- "app-bootstrap:/var/www/bootstrap"
|
||||||
networks:
|
networks:
|
||||||
- external # Required for ActivityPub
|
- external
|
||||||
- internal
|
- internal
|
||||||
command: gosu www-data php artisan horizon
|
command: gosu www-data php artisan horizon
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
|
||||||
|
## DB and Cache
|
||||||
db:
|
db:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -78,12 +77,11 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
|
|
||||||
# Adjust your volume data in order to store data where you wish
|
|
||||||
volumes:
|
volumes:
|
||||||
redis-data:
|
|
||||||
db-data:
|
db-data:
|
||||||
|
redis-data:
|
||||||
app-storage:
|
app-storage:
|
||||||
app-bootstrap:
|
app-bootstrap:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
internal:
|
internal:
|
||||||
|
|
40
package-lock.json
generated
|
@ -1080,9 +1080,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
"version": "6.4.0",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
|
||||||
"integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw=="
|
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA=="
|
||||||
},
|
},
|
||||||
"adjust-sourcemap-loader": {
|
"adjust-sourcemap-loader": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
|
@ -1306,7 +1306,7 @@
|
||||||
},
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.3",
|
||||||
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "2.0.1"
|
"inherits": "2.0.1"
|
||||||
|
@ -1430,7 +1430,7 @@
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^2.2.1",
|
"ansi-styles": "^2.2.1",
|
||||||
|
@ -1447,7 +1447,7 @@
|
||||||
},
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2497,12 +2497,12 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsesc": {
|
"jsesc": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
||||||
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
|
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
|
||||||
},
|
},
|
||||||
"regexpu-core": {
|
"regexpu-core": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
|
||||||
"integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
|
"integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerate": "^1.2.1",
|
"regenerate": "^1.2.1",
|
||||||
|
@ -2512,12 +2512,12 @@
|
||||||
},
|
},
|
||||||
"regjsgen": {
|
"regjsgen": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
|
||||||
"integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc="
|
"integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc="
|
||||||
},
|
},
|
||||||
"regjsparser": {
|
"regjsparser": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
|
||||||
"integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
|
"integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsesc": "~0.5.0"
|
"jsesc": "~0.5.0"
|
||||||
|
@ -2758,7 +2758,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"array-union": "^1.0.1",
|
"array-union": "^1.0.1",
|
||||||
|
@ -2770,7 +2770,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
|
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3263,7 +3263,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-flatten": {
|
"array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
|
@ -3606,7 +3606,7 @@
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^2.2.1",
|
"ansi-styles": "^2.2.1",
|
||||||
|
@ -3618,7 +3618,7 @@
|
||||||
},
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4833,7 +4833,7 @@
|
||||||
},
|
},
|
||||||
"is-accessor-descriptor": {
|
"is-accessor-descriptor": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
||||||
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
|
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^3.0.2"
|
"kind-of": "^3.0.2"
|
||||||
|
@ -4887,7 +4887,7 @@
|
||||||
},
|
},
|
||||||
"is-data-descriptor": {
|
"is-data-descriptor": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
||||||
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
|
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^3.0.2"
|
"kind-of": "^3.0.2"
|
||||||
|
@ -7309,7 +7309,7 @@
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
|
@ -7430,7 +7430,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsesc": {
|
"jsesc": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
||||||
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
|
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7611,7 +7611,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"convert-source-map": {
|
"convert-source-map": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
|
||||||
"integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
|
"integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
|
|
BIN
public/_landing/1.jpeg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
public/_landing/2.jpeg
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
public/_landing/3.jpeg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
public/_landing/4.jpeg
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
public/_landing/5.jpeg
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
public/_landing/6.jpeg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
public/_landing/7.jpeg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
public/_landing/8.jpeg
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
public/_landing/9.jpeg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
public/css/landing.css
vendored
BIN
public/js/profile.js
vendored
BIN
public/js/rempos.js
vendored
Normal file
BIN
public/js/rempro.js
vendored
Normal file
BIN
public/js/search.js
vendored
BIN
public/js/status.js
vendored
BIN
public/js/story-compose.js
vendored
BIN
public/js/theme-monokai.js
vendored
BIN
public/js/timeline.js
vendored
BIN
public/js/vendor.js
vendored
|
@ -40,7 +40,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="n.type == 'share'">
|
<div v-else-if="n.type == 'share'">
|
||||||
<p class="my-0">
|
<p class="my-0">
|
||||||
<a :href="n.account.url" class="font-weight-bold text-dark word-break" :title="n.account.username">{{truncate(n.account.username)}}</a> shared your <a class="font-weight-bold" v-bind:href="n.status.reblog.url">post</a>.
|
<a :href="n.account.url" class="font-weight-bold text-dark word-break" :title="n.account.username">{{truncate(n.account.username)}}</a> shared your <a class="font-weight-bold" v-bind:href="n.status.url">post</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="n.type == 'modlog'">
|
<div v-else-if="n.type == 'modlog'">
|
||||||
|
@ -88,14 +88,9 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetchNotifications() {
|
fetchNotifications() {
|
||||||
axios.get('/api/pixelfed/v1/notifications')
|
axios.get('/api/pixelfed/v1/notifications?pg=true')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let data = res.data.filter(n => {
|
let data = res.data;
|
||||||
if(n.type == 'share' && !status) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
let ids = res.data.map(n => n.id);
|
let ids = res.data.map(n => n.id);
|
||||||
this.notificationMaxId = Math.min(...ids);
|
this.notificationMaxId = Math.min(...ids);
|
||||||
this.notifications = data;
|
this.notifications = data;
|
||||||
|
|
|
@ -137,60 +137,70 @@
|
||||||
|
|
||||||
<div v-if="showComments">
|
<div v-if="showComments">
|
||||||
<hr>
|
<hr>
|
||||||
<div class="postCommentsLoader text-center">
|
<div class="postCommentsLoader text-center py-2">
|
||||||
<div class="spinner-border" role="status">
|
<div class="spinner-border" role="status">
|
||||||
<span class="sr-only">Loading...</span>
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="postCommentsContainer d-none">
|
<div class="postCommentsContainer d-none">
|
||||||
<p v-if="status.reply_count > 10"class="mb-1 text-center load-more-link d-none"><a href="#" class="text-muted" v-on:click="loadMore">Load more comments</a></p>
|
<p v-if="status.reply_count > 10" class="mb-1 text-center load-more-link d-none my-3">
|
||||||
|
<a href="#" class="text-dark" v-on:click="loadMore" title="Load more comments" data-toggle="tooltip" data-placement="bottom">
|
||||||
|
<svg class="bi bi-plus-circle" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="font-size:2em;"> <path fill-rule="evenodd" d="M8 3.5a.5.5 0 01.5.5v4a.5.5 0 01-.5.5H4a.5.5 0 010-1h3.5V4a.5.5 0 01.5-.5z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M7.5 8a.5.5 0 01.5-.5h4a.5.5 0 010 1H8.5V12a.5.5 0 01-1 0V8z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M8 15A7 7 0 108 1a7 7 0 000 14zm0 1A8 8 0 108 0a8 8 0 000 16z" clip-rule="evenodd"/></svg>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
<div v-for="(reply, index) in results" class="pb-3" :key="'tl' + reply.id + '_' + index">
|
<div v-for="(reply, index) in results" class="pb-4 media" :key="'tl' + reply.id + '_' + index">
|
||||||
<div v-if="reply.sensitive == true">
|
<img :src="reply.account.avatar" class="rounded-circle border mr-3" width="42px" height="42px">
|
||||||
<span class="py-3">
|
<div class="media-body">
|
||||||
<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>
|
<div v-if="reply.sensitive == true">
|
||||||
<span class="text-break">
|
<span class="py-3">
|
||||||
<span class="font-italic text-muted">This comment may contain sensitive material</span>
|
|
||||||
<span class="text-primary cursor-pointer pl-1" @click="reply.sensitive = false;">Show</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<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>
|
<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 class="text-break">
|
||||||
|
<span class="font-italic text-muted">This comment may contain sensitive material</span>
|
||||||
|
<span class="text-primary cursor-pointer pl-1" @click="reply.sensitive = false;">Show</span>
|
||||||
|
</span>
|
||||||
</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="">
|
|
||||||
<a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(reply.created_at)" :href="reply.url"></a>
|
|
||||||
<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, index)">Reply</span>
|
|
||||||
</p>
|
|
||||||
<div v-if="reply.reply_count > 0" class="cursor-pointer" style="margin-left:30px;" v-on:click="toggleReplies(reply)">
|
|
||||||
<span class="show-reply-bar"></span>
|
|
||||||
<span class="comment-reaction font-weight-bold text-muted">{{reply.thread ? 'Hide' : 'View'}} Replies ({{reply.reply_count}})</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="reply.thread == true" class="comment-thread">
|
<div v-else>
|
||||||
<div v-for="(s, sindex) in reply.replies" class="pb-3" :key="'cr' + s.id + '_' + index">
|
<p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
|
||||||
<p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
|
<span>
|
||||||
<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>
|
||||||
<a class="text-dark font-weight-bold mr-1" :href="s.account.url" :title="s.account.username">{{s.account.username}}</a>
|
<span class="text-break " v-html="reply.content"></span>
|
||||||
<span class="text-break" v-html="s.content"></span>
|
</span>
|
||||||
</span>
|
<span class="pl-2">
|
||||||
<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> -->
|
||||||
<span v-on:click="likeReply(s, $event)"><i v-bind:class="[s.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 px-2" v-on:deletePost="deleteComment(reply.id, index)"></post-menu>
|
||||||
<post-menu :status="s" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteCommentReply(s.id, sindex, index) "></post-menu>
|
</span>
|
||||||
</span>
|
</p>
|
||||||
</p>
|
<p class="">
|
||||||
<p class="">
|
<a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(reply.created_at)" :href="reply.url"></a>
|
||||||
<a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(s.created_at)" :href="s.url"></a>
|
<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 v-if="s.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{s.favourites_count == 1 ? '1 like' : s.favourites_count + ' likes'}}</span>
|
<span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply, index)">Reply</span>
|
||||||
</p>
|
</p>
|
||||||
|
<div v-if="reply.reply_count > 0" class="cursor-pointer" v-on:click="toggleReplies(reply)">
|
||||||
|
<span class="show-reply-bar"></span>
|
||||||
|
<span class="comment-reaction font-weight-bold text-muted">{{reply.thread ? 'Hide' : 'View'}} Replies ({{reply.reply_count}})</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="reply.thread == true" class="comment-thread">
|
||||||
|
<div v-for="(s, sindex) in reply.replies" class="pb-3 media" :key="'cr' + s.id + '_' + index">
|
||||||
|
<img :src="s.account.avatar" class="rounded-circle border mr-3" width="25px" height="25px">
|
||||||
|
<div class="media-body">
|
||||||
|
<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="s.account.url" :title="s.account.username">{{s.account.username}}</a>
|
||||||
|
<span class="text-break" v-html="s.content"></span>
|
||||||
|
</span>
|
||||||
|
<span class="pl-2" style="min-width:38px">
|
||||||
|
<span v-on:click="likeReply(s, $event)"><i v-bind:class="[s.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
|
||||||
|
<post-menu :status="s" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteCommentReply(s.id, sindex, index) "></post-menu>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
<a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(s.created_at)" :href="s.url"></a>
|
||||||
|
<span v-if="s.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{s.favourites_count == 1 ? '1 like' : s.favourites_count + ' likes'}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,7 +235,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showComments && user.length !== 0" class="card-footer bg-white px-2 py-0">
|
<div v-if="showComments && user.length !== 0" class="card-footer bg-white px-2 py-0">
|
||||||
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
||||||
<li class="nav-item" v-on:click="emojiReaction" v-for="e in emoji">{{e}}</li>
|
<li class="nav-item" v-on:click="emojiReaction" v-for="e in emoji">{{e}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -405,9 +415,9 @@
|
||||||
hide-footer
|
hide-footer
|
||||||
centered
|
centered
|
||||||
title="Likes"
|
title="Likes"
|
||||||
body-class="list-group-flush p-0">
|
body-class="list-group-flush py-3 px-0">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<div class="list-group-item border-0" v-for="(user, index) in likes" :key="'modal_likes_'+index">
|
<div class="list-group-item border-0 py-1" v-for="(user, index) in likes" :key="'modal_likes_'+index">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<a :href="user.url">
|
<a :href="user.url">
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px">
|
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px">
|
||||||
|
@ -418,9 +428,11 @@
|
||||||
{{user.username}}
|
{{user.username}}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-muted mb-0" style="font-size: 14px">
|
<p v-if="!user.local" class="text-muted mb-0 text-truncate mr-3" style="font-size: 14px" :title="user.acct" data-toggle="dropdown" data-placement="bottom">
|
||||||
{{user.display_name}}
|
<span class="font-weight-bold">{{user.acct.split('@')[0]}}</span><span class="text-lighter">@{{user.acct.split('@')[1]}}</span>
|
||||||
</a>
|
</p>
|
||||||
|
<p v-else class="text-muted mb-0 text-truncate" style="font-size: 14px">
|
||||||
|
{{user.display_name}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -465,9 +477,8 @@
|
||||||
</infinite-loading>
|
</infinite-loading>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<b-modal
|
<b-modal ref="lightboxModal"
|
||||||
id="lightbox"
|
id="lightbox"
|
||||||
ref="lightboxModal"
|
|
||||||
:hide-header="true"
|
:hide-header="true"
|
||||||
:hide-footer="true"
|
:hide-footer="true"
|
||||||
centered
|
centered
|
||||||
|
@ -478,7 +489,7 @@
|
||||||
<img :src="lightboxMedia.url" :class="lightboxMedia.filter_class + ' img-fluid'" style="min-height: 100%; min-width: 100%">
|
<img :src="lightboxMedia.url" :class="lightboxMedia.filter_class + ' img-fluid'" style="min-height: 100%; min-width: 100%">
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<b-modal ref="embedModal"
|
<b-modal ref="embedModal"
|
||||||
id="ctx-embed-modal"
|
id="ctx-embed-modal"
|
||||||
hide-header
|
hide-header
|
||||||
hide-footer
|
hide-footer
|
||||||
|
@ -542,8 +553,7 @@
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
.comment-thread {
|
.comment-thread {
|
||||||
margin: 4px 0 0 40px;
|
margin-top: 1rem;
|
||||||
width: calc(100% - 40px);
|
|
||||||
}
|
}
|
||||||
.emoji-reactions .nav-item {
|
.emoji-reactions .nav-item {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
|
@ -555,6 +565,12 @@
|
||||||
height: 0px;
|
height: 0px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.container {
|
||||||
|
max-width: 1100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<style type="text/css" scoped>
|
<style type="text/css" scoped>
|
||||||
.momentui .bg-dark {
|
.momentui .bg-dark {
|
||||||
|
@ -645,6 +661,7 @@ export default {
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
$('.carousel').carousel();
|
$('.carousel').carousel();
|
||||||
|
// $('[data-toggle="tooltip"]').tooltip();
|
||||||
if(this.showReadMore == true) {
|
if(this.showReadMore == true) {
|
||||||
window.pixelfed.readmore();
|
window.pixelfed.readmore();
|
||||||
}
|
}
|
||||||
|
@ -694,7 +711,6 @@ export default {
|
||||||
this.fetchComments();
|
this.fetchComments();
|
||||||
}
|
}
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
$('head title').text(this.status.account.username + ' posted a photo: ' + this.status.favourites_count + ' likes');
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
swal('Oops!', 'An error occured, please try refreshing the page.', 'error');
|
swal('Oops!', 'An error occured, please try refreshing the page.', 'error');
|
||||||
});
|
});
|
||||||
|
@ -959,7 +975,6 @@ export default {
|
||||||
this.replyToIndex = index;
|
this.replyToIndex = index;
|
||||||
this.replyingToId = e.id;
|
this.replyingToId = e.id;
|
||||||
this.reply_to_profile_id = e.account.id;
|
this.reply_to_profile_id = e.account.id;
|
||||||
this.replyText = '@' + e.account.username + ' ';
|
|
||||||
$('textarea[name="comment"]').focus();
|
$('textarea[name="comment"]').focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1009,6 +1024,7 @@ export default {
|
||||||
$('.load-more-link').addClass('d-none');
|
$('.load-more-link').addClass('d-none');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$('.load-more-link').addClass('d-none');
|
||||||
$('.postCommentsLoader').removeClass('d-none');
|
$('.postCommentsLoader').removeClass('d-none');
|
||||||
let next = this.pagination.links.next;
|
let next = this.pagination.links.next;
|
||||||
axios.get(next)
|
axios.get(next)
|
||||||
|
@ -1020,6 +1036,7 @@ export default {
|
||||||
this.results.unshift(res[i]);
|
this.results.unshift(res[i]);
|
||||||
}
|
}
|
||||||
this.pagination = response.data.meta.pagination;
|
this.pagination = response.data.meta.pagination;
|
||||||
|
$('.load-more-link').removeClass('d-none');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
<a class="dropdown-item font-weight-bold text-decoration-none" :href="status.url">Go to post</a>
|
<a class="dropdown-item font-weight-bold text-decoration-none" :href="status.url">Go to post</a>
|
||||||
<!-- <a class="dropdown-item font-weight-bold text-decoration-none" href="#">Share</a>
|
<!-- <a class="dropdown-item font-weight-bold text-decoration-none" href="#">Share</a>
|
||||||
<a class="dropdown-item font-weight-bold text-decoration-none" href="#">Embed</a> -->
|
<a class="dropdown-item font-weight-bold text-decoration-none" href="#">Embed</a> -->
|
||||||
<span v-if="statusOwner(status) == false">
|
<span v-if="activeSession == true && statusOwner(status) == false">
|
||||||
<a class="dropdown-item font-weight-bold" :href="reportUrl(status)">Report</a>
|
<a class="dropdown-item font-weight-bold" :href="reportUrl(status)">Report</a>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="statusOwner(status) == true">
|
<span v-if="activeSession == true && statusOwner(status) == true">
|
||||||
<a class="dropdown-item font-weight-bold text-decoration-none" @click.prevent="muteProfile(status)">Mute Profile</a>
|
<a class="dropdown-item font-weight-bold text-decoration-none" @click.prevent="muteProfile(status)">Mute Profile</a>
|
||||||
<a class="dropdown-item font-weight-bold text-decoration-none" @click.prevent="blockProfile(status)">Block Profile</a>
|
<a class="dropdown-item font-weight-bold text-decoration-none" @click.prevent="blockProfile(status)">Block Profile</a>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="profile.is_admin == true">
|
<span v-if="activeSession == true && profile.is_admin == true">
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item font-weight-bold text-danger text-decoration-none" v-on:click="deletePost(status)">Delete</a>
|
<a class="dropdown-item font-weight-bold text-danger text-decoration-none" v-on:click="deletePost(status)">Delete</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
@ -51,38 +51,36 @@
|
||||||
<div class="modal" tabindex="-1" role="dialog" :id="'mt_pid_'+status.id">
|
<div class="modal" tabindex="-1" role="dialog" :id="'mt_pid_'+status.id">
|
||||||
<div class="modal-dialog modal-sm" role="document">
|
<div class="modal-dialog modal-sm" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-body">
|
<div class="modal-body text-center">
|
||||||
<div class="list-group">
|
<div class="list-group text-dark">
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Go to post</a>
|
<a class="list-group-item text-dark text-decoration-none" :href="status.url">Go to post</a>
|
||||||
<!-- <a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Share</a>
|
<!-- a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Share</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Embed</a> -->
|
<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Embed</a> -->
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" href="#" @click="hidePost(status)">Hide</a>
|
<a class="list-group-item text-dark text-decoration-none" href="#" @click="hidePost(status)">Hide</a>
|
||||||
<span v-if="statusOwner(status) == false">
|
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-dark text-decoration-none" :href="reportUrl(status)">Report</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" :href="reportUrl(status)">Report</a>
|
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-dark text-decoration-none" v-on:click="muteProfile(status)" href="#">Mute Profile</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="muteProfile(status)" href="#">Mute Profile</a>
|
<a v-if="activeSession == true && !statusOwner(status)" class="list-group-item text-dark text-decoration-none" v-on:click="blockProfile(status)" href="#">Block Profile</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="blockProfile(status)" href="#">Block Profile</a>
|
<span v-if="activeSession == true && statusOwner(status) == true || profile.is_admin == true">
|
||||||
|
<a class="list-group-item text-danger text-decoration-none" v-on:click="deletePost">Delete</a>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="statusOwner(status) == true || profile.is_admin == true">
|
<span v-if="activeSession == true && profile.is_admin == true">
|
||||||
<a class="list-group-item font-weight-bold text-danger text-decoration-none" v-on:click="deletePost">Delete</a>
|
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'autocw')" href="#">
|
||||||
</span>
|
|
||||||
<span v-if="profile.is_admin == true">
|
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'autocw')" href="#">
|
|
||||||
<p class="mb-0">Enforce CW</p>
|
<p class="mb-0">Enforce CW</p>
|
||||||
<p class="mb-0 small text-muted">Adds a CW to every post <br> made by this account.</p>
|
<p class="mb-0 small text-muted">Adds a CW to every post <br> made by this account.</p>
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'noautolink')" href="#">
|
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'noautolink')" href="#">
|
||||||
<p class="mb-0">No Autolinking</p>
|
<p class="mb-0">No Autolinking</p>
|
||||||
<p class="mb-0 small text-muted">Do not transform mentions, <br> hashtags or urls into HTML.</p>
|
<p class="mb-0 small text-muted">Do not transform mentions, <br> hashtags or urls into HTML.</p>
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'unlisted')" href="#">
|
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'unlisted')" href="#">
|
||||||
<p class="mb-0">Unlisted Posts</p>
|
<p class="mb-0">Unlisted Posts</p>
|
||||||
<p class="mb-0 small text-muted">Removes account from <br> public/network timelines.</p>
|
<p class="mb-0 small text-muted">Removes account from <br> public/network timelines.</p>
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'disable')" href="#">
|
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'disable')" href="#">
|
||||||
<p class="mb-0">Disable Account</p>
|
<p class="mb-0">Disable Account</p>
|
||||||
<p class="mb-0 small text-muted">Temporarily disable account <br> until next time user log in.</p>
|
<p class="mb-0 small text-muted">Temporarily disable account <br> until next time user log in.</p>
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'suspend')" href="#">
|
<a class="list-group-item text-dark text-decoration-none" v-on:click="moderatePost(status, 'suspend')" href="#">
|
||||||
<p class="mb-0">Suspend Account</p>
|
<p class="mb-0">Suspend Account</p>
|
||||||
<p class="mb-0 small text-muted">This prevents any new interactions, <br> without deleting existing data.</p>
|
<p class="mb-0 small text-muted">This prevents any new interactions, <br> without deleting existing data.</p>
|
||||||
</a>
|
</a>
|
||||||
|
@ -110,6 +108,17 @@
|
||||||
export default {
|
export default {
|
||||||
props: ['feed', 'status', 'profile', 'size', 'modal'],
|
props: ['feed', 'status', 'profile', 'size', 'modal'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeSession: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
let el = document.querySelector('body');
|
||||||
|
this.activeSession = el.classList.contains('loggedIn') ? true : false;
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
reportUrl(status) {
|
reportUrl(status) {
|
||||||
let type = status.in_reply_to ? 'comment' : 'post';
|
let type = status.in_reply_to ? 'comment' : 'post';
|
||||||
|
|
|
@ -173,8 +173,8 @@
|
||||||
<div class="container px-0">
|
<div class="container px-0">
|
||||||
<div class="profile-timeline 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-1 p-md-3" v-for="(s, index) in timeline">
|
<div class="col-4 p-1 p-md-3" v-for="(s, index) in timeline" :key="'tlob:'+index">
|
||||||
<a class="card info-overlay card-md-border-0" :href="statusUrl(s)">
|
<a class="card info-overlay card-md-border-0" :href="statusUrl(s)" v-once>
|
||||||
<div :class="[s.sensitive ? 'square' : 'square ' + s.media_attachments[0].filter_class]">
|
<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 == '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'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||||
|
@ -355,26 +355,47 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<b-modal ref="followingModal"
|
<b-modal
|
||||||
|
v-if="profile && following"
|
||||||
|
ref="followingModal"
|
||||||
id="following-modal"
|
id="following-modal"
|
||||||
hide-footer
|
hide-footer
|
||||||
centered
|
centered
|
||||||
title="Following"
|
title="Following"
|
||||||
body-class="list-group-flush py-3 px-0"
|
body-class="list-group-flush py-3 px-0"
|
||||||
dialog-class="follow-modal">
|
dialog-class="follow-modal">
|
||||||
<div class="list-group">
|
<div v-if="!loading" class="list-group" style="min-height: 60vh;">
|
||||||
|
<div v-if="owner == true" class="list-group-item border-0 pt-0 px-0 mt-n2 mb-3">
|
||||||
|
<span class="d-flex px-4 pb-0 align-items-center">
|
||||||
|
<i class="fas fa-search text-lighter"></i>
|
||||||
|
<input type="text" class="form-control border-0 shadow-0 no-focus" placeholder="Search Following..." v-model="followingModalSearch" v-on:keyup="followingModalSearchHandler">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="owner == true" class="btn-group rounded-0 mt-n3 mb-3 border-top" role="group" aria-label="Following">
|
||||||
|
<!-- <button type="button" :class="[followingModalTab == 'following' ? ' btn btn-light py-3 rounded-0 font-weight-bold modal-tab-active' : 'btn btn-light py-3 rounded-0 font-weight-bold']" style="font-size: 12px;">FOLLOWING</button> -->
|
||||||
|
<!-- <button type="button" class="btn btn-light py-3 rounded-0 text-muted font-weight-bold" style="font-size: 12px;">MUTED</button>
|
||||||
|
<button type="button" class="btn btn-light py-3 rounded-0 text-muted font-weight-bold" style="font-size: 12px;">BLOCKED</button> -->
|
||||||
|
</div>
|
||||||
|
<div v-else class="btn-group rounded-0 mt-n3 mb-3" role="group" aria-label="Following">
|
||||||
|
<!-- <button type="button" class="btn btn-light py-3 rounded-0 border-primary border-left-0 border-right-0 border-top-0 font-weight-bold" style="font-size: 12px;" @click="followingModalTab = 'following'">FOLLOWING</button>
|
||||||
|
<button type="button" class="btn btn-light py-3 rounded-0 text-muted font-weight-bold" style="font-size: 12px;" @click="followingModalTab = 'mutual'">MUTUAL</button>
|
||||||
|
<button type="button" class="btn btn-light py-3 rounded-0 text-muted font-weight-bold" style="font-size: 12px;" @click="followingModalTab = 'blocked'">BLOCKED</button> -->
|
||||||
|
</div>
|
||||||
<div class="list-group-item border-0 py-1" v-for="(user, index) in following" :key="'following_'+index">
|
<div class="list-group-item border-0 py-1" v-for="(user, index) in following" :key="'following_'+index">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<a :href="user.url">
|
<a :href="user.url">
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy">
|
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy">
|
||||||
</a>
|
</a>
|
||||||
<div class="media-body">
|
<div class="media-body text-truncate">
|
||||||
<p class="mb-0" style="font-size: 14px">
|
<p class="mb-0" style="font-size: 14px">
|
||||||
<a :href="user.url" class="font-weight-bold text-dark">
|
<a :href="user.url" class="font-weight-bold text-dark">
|
||||||
{{user.username}}
|
{{user.username}}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-muted mb-0" style="font-size: 14px">
|
<p v-if="!user.local" class="text-muted mb-0 text-truncate mr-3" style="font-size: 14px" :title="user.acct" data-toggle="dropdown" data-placement="bottom">
|
||||||
|
<span class="font-weight-bold">{{user.acct.split('@')[0]}}</span><span class="text-lighter">@{{user.acct.split('@')[1]}}</span>
|
||||||
|
</p>
|
||||||
|
<p v-else class="text-muted mb-0 text-truncate" style="font-size: 14px">
|
||||||
{{user.display_name}}
|
{{user.display_name}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -383,12 +404,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="following.length == 0" class="list-group-item border-0">
|
<div v-if="followingModalSearch && following.length == 0" class="list-group-item border-0">
|
||||||
<div class="list-group-item border-0">
|
<div class="list-group-item border-0 pt-5">
|
||||||
<p class="p-3 text-center mb-0 lead"></p>
|
<p class="p-3 text-center mb-0 lead">No Results Found</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="followingMore" class="list-group-item text-center" v-on:click="followingLoadMore()">
|
<div v-if="following.length > 0 && followingMore" class="list-group-item text-center" v-on:click="followingLoadMore()">
|
||||||
<p class="mb-0 small text-muted font-weight-light cursor-pointer">Load more</p>
|
<p class="mb-0 small text-muted font-weight-light cursor-pointer">Load more</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -565,6 +586,14 @@
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background:#fff;
|
background:#fff;
|
||||||
}
|
}
|
||||||
|
.no-focus {
|
||||||
|
border-color: none;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.modal-tab-active {
|
||||||
|
border-bottom: 1px solid #08d;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
import VueMasonry from 'vue-masonry-css'
|
import VueMasonry from 'vue-masonry-css'
|
||||||
|
@ -608,7 +637,10 @@
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
ctxEmbedPayload: null,
|
ctxEmbedPayload: null,
|
||||||
copiedEmbed: false,
|
copiedEmbed: false,
|
||||||
hasStory: null
|
hasStory: null,
|
||||||
|
followingModalSearch: null,
|
||||||
|
followingModalSearchCache: null,
|
||||||
|
followingModalTab: 'following',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
@ -1013,6 +1045,7 @@
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.following = res.data;
|
this.following = res.data;
|
||||||
|
this.followingModalSearchCache = res.data;
|
||||||
this.followingCursor++;
|
this.followingCursor++;
|
||||||
if(res.data.length < 10) {
|
if(res.data.length < 10) {
|
||||||
this.followingMore = false;
|
this.followingMore = false;
|
||||||
|
@ -1059,15 +1092,18 @@
|
||||||
}
|
}
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/following', {
|
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/following', {
|
||||||
params: {
|
params: {
|
||||||
page: this.followingCursor
|
page: this.followingCursor,
|
||||||
|
fbu: this.followingModalSearch
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if(res.data.length > 0) {
|
if(res.data.length > 0) {
|
||||||
this.following.push(...res.data);
|
this.following.push(...res.data);
|
||||||
this.followingCursor++;
|
this.followingCursor++;
|
||||||
|
this.followingModalSearchCache = this.following;
|
||||||
}
|
}
|
||||||
if(res.data.length < 10) {
|
if(res.data.length < 10) {
|
||||||
|
this.followingModalSearchCache = this.following;
|
||||||
this.followingMore = false;
|
this.followingMore = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1186,6 +1222,28 @@
|
||||||
|
|
||||||
storyRedirect() {
|
storyRedirect() {
|
||||||
window.location.href = '/stories/' + this.profileUsername;
|
window.location.href = '/stories/' + this.profileUsername;
|
||||||
|
},
|
||||||
|
|
||||||
|
followingModalSearchHandler() {
|
||||||
|
let self = this;
|
||||||
|
let q = this.followingModalSearch;
|
||||||
|
|
||||||
|
if(q.length == 0) {
|
||||||
|
this.following = this.followingModalSearchCache;
|
||||||
|
this.followingModalSearch = null;
|
||||||
|
}
|
||||||
|
if(q.length > 0) {
|
||||||
|
let url = '/api/pixelfed/v1/accounts/' +
|
||||||
|
self.profileId + '/following?page=1&fbu=' +
|
||||||
|
q;
|
||||||
|
|
||||||
|
axios.get(url).then(res => {
|
||||||
|
this.following = res.data;
|
||||||
|
}).catch(err => {
|
||||||
|
self.following = self.followingModalSearchCache;
|
||||||
|
self.followingModalSearch = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1028
resources/assets/js/components/RemotePost.vue
Normal file
447
resources/assets/js/components/RemoteProfile.vue
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="relationship && relationship.blocking && warning" class="bg-white pt-3 border-bottom">
|
||||||
|
<div class="container">
|
||||||
|
<p class="text-center font-weight-bold">You are blocking this account</p>
|
||||||
|
<p class="text-center font-weight-bold">Click <a href="#" class="cursor-pointer" @click.prevent="warning = false;">here</a> to view profile</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="loading" style="height: 80vh;" class="d-flex justify-content-center align-items-center">
|
||||||
|
<img src="/img/pixelfed-icon-grey.svg" class="">
|
||||||
|
</div>
|
||||||
|
<div v-if="!loading && !warning" class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-4 pt-5">
|
||||||
|
<div class="card shadow-none border">
|
||||||
|
<div class="card-header p-0 m-0">
|
||||||
|
<img v-if="profile.header_bg" :src="profile.header_bg" style="width: 100%; height: 140px; object-fit: cover;">
|
||||||
|
<div v-else class="bg-primary" style="width: 100%;height: 140px;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pb-0">
|
||||||
|
<div class="mt-n5 mb-3">
|
||||||
|
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" :src="profile.avatar" width="90px" height="90px;">
|
||||||
|
<span class="float-right mt-n1">
|
||||||
|
<span>
|
||||||
|
<button v-if="relationship && relationship.following == false" class="btn btn-outline-light py-0 px-3 mt-n1" style="font-size:13px; font-weight: 500;" @click="followProfile();">Follow</button>
|
||||||
|
<button v-if="relationship && relationship.following == true" class="btn btn-outline-light py-0 px-3 mt-n1" style="font-size:13px; font-weight: 500;" @click="unfollowProfile();">Unfollow</button>
|
||||||
|
</span>
|
||||||
|
<span class="ml-3">
|
||||||
|
<button class="btn btn-outline-light btn-sm mt-n1" @click="showCtxMenu()" style="padding-top:2px;padding-bottom:1px;">
|
||||||
|
<i class="fas fa-cog cursor-pointer" style="font-size:13px;"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="pl-2 h4 font-weight-bold mb-1">{{profile.display_name}}</p>
|
||||||
|
<p class="pl-2 font-weight-bold mb-1 text-muted">{{profile.acct}}</p>
|
||||||
|
<p class="pl-2 text-muted small pt-3" v-html="profile.note"></p>
|
||||||
|
<p class="pl-2 text-muted small d-flex justify-content-between">
|
||||||
|
<span>
|
||||||
|
<span class="font-weight-bold text-dark">{{profile.statuses_count}}</span>
|
||||||
|
<span>Posts</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="font-weight-bold text-dark">{{profile.following_count}}</span>
|
||||||
|
<span>Following</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="font-weight-bold text-dark">{{profile.followers_count}}</span>
|
||||||
|
<span>Followers</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-8 pt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-2" v-for="(status, index) in feed" :key="'remprop' + index">
|
||||||
|
<div class="card mb-sm-4 status-card card-md-rounded-0 shadow-none border cursor-pointer">
|
||||||
|
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||||
|
<img v-bind:src="profile.avatar" width="38px" height="38px" style="border-radius: 38px;" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=2'">
|
||||||
|
<div class="pl-2">
|
||||||
|
<span class="username font-weight-bold text-dark">{{profile.username}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-right" style="flex-grow:1;">
|
||||||
|
<button class="btn btn-link text-dark py-0" type="button" @click="ctxMenu(status)">
|
||||||
|
<span class="fas fa-ellipsis-h text-lighter"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<a :href="status.url">
|
||||||
|
<img v-once :src="status.thumb" class="w-100 h-100">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="caption">
|
||||||
|
<p class="mb-2 read-more" style="overflow: hidden;">
|
||||||
|
<span class="username font-weight-bold">
|
||||||
|
<bdi><span class="text-dark">{{profile.username}}</span></bdi>
|
||||||
|
</span>
|
||||||
|
<span class="status-content" v-html="status.caption.html"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="timestamp mt-2">
|
||||||
|
<p class="small text-uppercase mb-0">
|
||||||
|
<a :href="remotePostUrl(status)" class="text-muted">
|
||||||
|
<timeago :datetime="status.timestamp" :auto-update="90" :converter-options="{includeSeconds:true}" :title="timestampFormat(status.timestamp)" v-b-tooltip.hover.bottom></timeago>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="col-12 mt-4">
|
||||||
|
<p class="text-center mb-0 px-0"><button class="btn btn-outline-primary btn-block font-weight-bold">Load More</button></p>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-modal ref="visitorContextMenu"
|
||||||
|
id="visitor-context-menu"
|
||||||
|
hide-footer
|
||||||
|
hide-header
|
||||||
|
centered
|
||||||
|
size="sm"
|
||||||
|
body-class="list-group-flush p-0">
|
||||||
|
<div class="list-group" v-if="relationship">
|
||||||
|
<div class="list-group-item cursor-pointer text-center rounded text-dark" @click="copyProfileLink">
|
||||||
|
Copy Link
|
||||||
|
</div>
|
||||||
|
<div v-if="user && !owner && !relationship.muting" class="list-group-item cursor-pointer text-center rounded" @click="muteProfile">
|
||||||
|
Mute
|
||||||
|
</div>
|
||||||
|
<div v-if="user && !owner && relationship.muting" class="list-group-item cursor-pointer text-center rounded" @click="unmuteProfile">
|
||||||
|
Unmute
|
||||||
|
</div>
|
||||||
|
<div v-if="user && !owner" class="list-group-item cursor-pointer text-center rounded text-dark" @click="reportProfile">
|
||||||
|
Report User
|
||||||
|
</div>
|
||||||
|
<div v-if="user && !owner && !relationship.blocking" class="list-group-item cursor-pointer text-center rounded text-dark" @click="blockProfile">
|
||||||
|
Block
|
||||||
|
</div>
|
||||||
|
<div v-if="user && !owner && relationship.blocking" class="list-group-item cursor-pointer text-center rounded text-dark" @click="unblockProfile">
|
||||||
|
Unblock
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-group-item cursor-pointer text-center rounded text-muted" @click="$refs.visitorContextMenu.hide()">
|
||||||
|
Close
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
<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 && profile.id != profile.id" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuReportPost()">Report inappropriate</div>
|
||||||
|
<div v-if="ctxMenuStatus && profile.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 && profile.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="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 == profile.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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'profile-id',
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: [],
|
||||||
|
user: false,
|
||||||
|
profile: {},
|
||||||
|
feed: [],
|
||||||
|
min_id: null,
|
||||||
|
max_id: null,
|
||||||
|
loading: true,
|
||||||
|
owner: false,
|
||||||
|
layoutType: true,
|
||||||
|
relationship: null,
|
||||||
|
warning: false,
|
||||||
|
ctxMenuStatus: false,
|
||||||
|
ctxMenuRelationship: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeMount() {
|
||||||
|
this.fetchRelationships();
|
||||||
|
this.fetchProfile();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
fetchProfile() {
|
||||||
|
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
||||||
|
this.user = res.data
|
||||||
|
});
|
||||||
|
axios.get('/api/pixelfed/v1/accounts/' + this.profileId)
|
||||||
|
.then(res => {
|
||||||
|
this.profile = res.data;
|
||||||
|
this.fetchPosts();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchPosts() {
|
||||||
|
let apiUrl = '/api/pixelfed/v1/accounts/' + this.profileId + '/statuses';
|
||||||
|
axios.get(apiUrl, {
|
||||||
|
params: {
|
||||||
|
only_media: true,
|
||||||
|
min_id: 1,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
let data = res.data
|
||||||
|
.filter(status => status.media_attachments.length > 0)
|
||||||
|
.map(status => {
|
||||||
|
return {
|
||||||
|
id: status.id,
|
||||||
|
caption: {
|
||||||
|
text: status.content_text,
|
||||||
|
html: status.content
|
||||||
|
},
|
||||||
|
count: {
|
||||||
|
likes: status.favourites_count,
|
||||||
|
shares: status.reblogs_count,
|
||||||
|
comments: status.reply_count
|
||||||
|
},
|
||||||
|
thumb: status.media_attachments[0].preview_url,
|
||||||
|
media: status.media_attachments,
|
||||||
|
timestamp: status.created_at,
|
||||||
|
type: status.pf_type,
|
||||||
|
url: status.url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let ids = data.map(status => status.id);
|
||||||
|
this.ids = ids;
|
||||||
|
this.min_id = Math.max(...ids);
|
||||||
|
this.max_id = Math.min(...ids);
|
||||||
|
this.feed = data;
|
||||||
|
this.loading = false;
|
||||||
|
//this.loadSponsor();
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Oops, something went wrong',
|
||||||
|
'Please release the page.',
|
||||||
|
'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchRelationships() {
|
||||||
|
if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
axios.get('/api/pixelfed/v1/accounts/relationships', {
|
||||||
|
params: {
|
||||||
|
'id[]': this.profileId
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
if(res.data.length) {
|
||||||
|
this.relationship = res.data[0];
|
||||||
|
if(res.data[0].blocking == true) {
|
||||||
|
this.loading = false;
|
||||||
|
this.warning = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
postPreviewUrl(post) {
|
||||||
|
return 'background: url("'+post.thumb+'");background-size:cover';
|
||||||
|
},
|
||||||
|
|
||||||
|
timestampFormat(timestamp) {
|
||||||
|
let ts = new Date(timestamp);
|
||||||
|
return ts.toDateString() + ' ' + ts.toLocaleTimeString();
|
||||||
|
},
|
||||||
|
|
||||||
|
remoteProfileUrl(profile) {
|
||||||
|
return '/i/web/profile/_/' + profile.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
remotePostUrl(status) {
|
||||||
|
return '/i/web/post/_/' + this.profile.id + '/' + status.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
followProfile() {
|
||||||
|
axios.post('/i/follow', {
|
||||||
|
item: this.profileId
|
||||||
|
}).then(res => {
|
||||||
|
swal('Followed', 'You are now following ' + this.profile.username +'!', 'success');
|
||||||
|
this.relationship.following = true;
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Oops!', 'Something went wrong, please try again later.', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unfollowProfile() {
|
||||||
|
axios.post('/i/follow', {
|
||||||
|
item: this.profileId
|
||||||
|
}).then(res => {
|
||||||
|
swal('Unfollowed', 'You are no longer following ' + this.profile.username +'.', 'warning');
|
||||||
|
this.relationship.following = false;
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Oops!', 'Something went wrong, please try again later.', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showCtxMenu() {
|
||||||
|
this.$refs.visitorContextMenu.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
copyProfileLink() {
|
||||||
|
navigator.clipboard.writeText(window.location.href);
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
muteProfile() {
|
||||||
|
let id = this.profileId;
|
||||||
|
axios.post('/i/mute', {
|
||||||
|
type: 'user',
|
||||||
|
item: id
|
||||||
|
}).then(res => {
|
||||||
|
this.fetchRelationships();
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
swal('Success', 'You have successfully muted ' + this.profile.acct, 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||||
|
});
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
unmuteProfile() {
|
||||||
|
let id = this.profileId;
|
||||||
|
axios.post('/i/unmute', {
|
||||||
|
type: 'user',
|
||||||
|
item: id
|
||||||
|
}).then(res => {
|
||||||
|
this.fetchRelationships();
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
swal('Success', 'You have successfully unmuted ' + this.profile.acct, 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||||
|
});
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
blockProfile() {
|
||||||
|
let id = this.profileId;
|
||||||
|
axios.post('/i/block', {
|
||||||
|
type: 'user',
|
||||||
|
item: id
|
||||||
|
}).then(res => {
|
||||||
|
this.warning = true;
|
||||||
|
this.fetchRelationships();
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
swal('Success', 'You have successfully blocked ' + this.profile.acct, 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||||
|
});
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
unblockProfile() {
|
||||||
|
let id = this.profileId;
|
||||||
|
axios.post('/i/unblock', {
|
||||||
|
type: 'user',
|
||||||
|
item: id
|
||||||
|
}).then(res => {
|
||||||
|
this.warning = false;
|
||||||
|
this.fetchRelationships();
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
swal('Success', 'You have successfully unblocked ' + this.profile.acct, 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||||
|
});
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
reportProfile() {
|
||||||
|
window.location.href = '/l/i/report?type=profile&id=' + this.profileId;
|
||||||
|
this.$refs.visitorContextMenu.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
ctxMenu(status) {
|
||||||
|
this.ctxMenuStatus = status;
|
||||||
|
let self = this;
|
||||||
|
axios.get('/api/pixelfed/v1/accounts/relationships', {
|
||||||
|
params: {
|
||||||
|
'id[]': self.profileId
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
self.ctxMenuRelationship = res.data[0];
|
||||||
|
self.$refs.ctxModal.show();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
closeCtxMenu() {
|
||||||
|
this.ctxMenuStatus = false;
|
||||||
|
this.ctxMenuRelationship = false;
|
||||||
|
this.$refs.ctxModal.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
ctxMenuCopyLink() {
|
||||||
|
let status = this.ctxMenuStatus;
|
||||||
|
navigator.clipboard.writeText(status.url);
|
||||||
|
this.closeCtxMenu();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
ctxMenuGoToPost() {
|
||||||
|
let status = this.ctxMenuStatus;
|
||||||
|
window.location.href = this.statusUrl(status);
|
||||||
|
this.closeCtxMenu();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
statusUrl(status) {
|
||||||
|
return '/i/web/post/_/' + this.profile.id + '/' + status.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
deletePost(status) {
|
||||||
|
if(this.user.is_admin == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(window.confirm('Are you sure you want to delete this post?') == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/i/delete', {
|
||||||
|
type: 'status',
|
||||||
|
item: status.id
|
||||||
|
}).then(res => {
|
||||||
|
this.feed = this.feed.filter(s => {
|
||||||
|
return s.id != status.id;
|
||||||
|
});
|
||||||
|
this.$refs.ctxModal.hide();
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style type="text/css" scoped>
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.container {
|
||||||
|
max-width: 1050px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,86 +9,192 @@
|
||||||
<p class="lead font-weight-lighter">An error occured, results could not be loaded.<br> Please try again later.</p>
|
<p class="lead font-weight-lighter">An error occured, results could not be loaded.<br> Please try again later.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!loading && !networkError" class="mt-5 row">
|
<div v-if="!loading && !networkError" class="mt-5">
|
||||||
|
<div v-if="analysis == 'all'" class="row">
|
||||||
<div class="col-12 col-md-2 mb-4">
|
<div class="col-12 mb-5">
|
||||||
<div v-if="results.hashtags || results.profiles || results.statuses">
|
<p class="h5 font-weight-bold text-dark">Showing results for <i>{{query}}</i></p>
|
||||||
<p class="font-weight-bold">Filters</p>
|
<hr>
|
||||||
<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">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">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">Statuses</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-3">
|
||||||
<div class="col-12 col-md-10">
|
<div class="mb-4">
|
||||||
<p class="h5 font-weight-bold">Showing results for <i>{{query}}</i></p>
|
<p class="text-secondary small font-weight-bold">HASHTAGS <span class="pl-1 text-lighter">({{results.hashtags.length}})</span></p>
|
||||||
<hr>
|
</div>
|
||||||
|
<div v-if="results.hashtags.length">
|
||||||
<div v-if="filters.hashtags && results.hashtags" class="row mb-4">
|
<a v-for="(hashtag, index) in results.hashtags" class="mb-2 result-card" :href="buildUrl('hashtag', hashtag)">
|
||||||
<p class="col-12 font-weight-bold text-muted">Hashtags</p>
|
<div class="pb-3">
|
||||||
<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="media align-items-center py-2 pr-3">
|
||||||
<div class="card card-body text-center shadow-none border">
|
<span class="d-inline-flex align-items-center justify-content-center border rounded-circle mr-3" style="width: 50px;height: 50px;">
|
||||||
<p class="lead mb-0 text-truncate text-dark" data-toggle="tooltip" :title="hashtag.value">
|
<i class="fas fa-hashtag text-muted"></i>
|
||||||
#{{hashtag.value}}
|
</span>
|
||||||
</p>
|
<div class="media-body text-truncate">
|
||||||
<p class="lead mb-0 small font-weight-bold text-dark">
|
<p class="mb-0 text-truncate text-dark font-weight-bold" data-toggle="tooltip" :title="hashtag.value">
|
||||||
{{hashtag.count}} posts
|
#{{hashtag.value}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<p v-if="hashtag.count > 2" class="mb-0 small font-weight-bold text-muted text-uppercase">
|
||||||
</a>
|
{{hashtag.count}} posts
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
<div v-if="filters.profiles && results.profiles" class="row mb-4">
|
</div>
|
||||||
<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 shadow-none border">
|
|
||||||
<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}}
|
|
||||||
</p>
|
|
||||||
<p class="mb-0 text-center">
|
|
||||||
<button v-if="profile.entity.follow_request" type="button" class="btn btn-secondary btn-sm py-1 font-weight-bold" disabled>Follow Requested</button>
|
|
||||||
<button v-if="!profile.entity.follow_request && profile.entity.following" type="button" class="btn btn-secondary btn-sm py-1 font-weight-bold" @click.prevent="followProfile(profile, index)">Unfollow</button>
|
|
||||||
<button v-if="!profile.entity.follow_request && !profile.entity.following" type="button" class="btn btn-primary btn-sm py-1 font-weight-bold" @click.prevent="followProfile(profile, index)">Follow</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="filters.statuses && results.statuses" class="row mb-4">
|
|
||||||
<p class="col-12 font-weight-bold text-muted">Statuses</p>
|
|
||||||
<div v-for="(status, index) in results.statuses" class="col-4 p-0 p-sm-2 p-md-3 hashtag-post-square">
|
|
||||||
<a class="card info-overlay card-md-border-0" :href="status.url">
|
|
||||||
<div :class="[status.filter ? 'square ' + status.filter : 'square']">
|
|
||||||
<div class="square-content" :style="'background-image: url('+status.thumb+')'"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="border py-3 text-center font-weight-bold">No results found</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
<div v-if="!results.hashtags && !results.profiles && !results.statuses">
|
<div class="mb-4">
|
||||||
<p class="text-center lead">No results found!</p>
|
<p class="text-secondary small font-weight-bold">PROFILES <span class="pl-1 text-lighter">({{results.profiles.length}})</span></p>
|
||||||
|
</div>
|
||||||
|
<div v-if="results.profiles.length">
|
||||||
|
<a v-for="(profile, index) in results.profiles" class="mb-2 result-card" :href="buildUrl('profile', profile)">
|
||||||
|
<div class="pb-3">
|
||||||
|
<div class="media align-items-center py-2 pr-3">
|
||||||
|
<img class="mr-3 rounded-circle border" :src="profile.avatar" width="50px" height="50px">
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 text-truncate text-dark font-weight-bold" data-toggle="tooltip" :title="profile.value">
|
||||||
|
{{profile.value}}
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 small font-weight-bold text-muted text-uppercase">
|
||||||
|
{{profile.entity.post_count}} Posts
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<a v-if="profile.entity.following" class="btn btn-primary btn-sm font-weight-bold text-uppercase py-0" :href="buildUrl('profile', profile)">Following</a>
|
||||||
|
<a v-else class="btn btn-outline-primary btn-sm font-weight-bold text-uppercase py-0" :href="buildUrl('profile', profile)">View</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="border py-3 text-center font-weight-bold">No results found</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-secondary small font-weight-bold">STATUSES <span class="pl-1 text-lighter">({{results.statuses.length}})</span></p>
|
||||||
|
</div>
|
||||||
|
<div v-if="results.statuses.length">
|
||||||
|
<a v-for="(status, index) in results.statuses" class="mr-2 result-card" :href="buildUrl('status', status)">
|
||||||
|
<img :src="status.thumb" width="90px" height="90px" class="mb-2">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="border py-3 text-center font-weight-bold">No results found</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="analysis == 'hashtag'" class="row">
|
||||||
|
<div class="col-12 mb-5">
|
||||||
|
<p class="h5 font-weight-bold text-dark">Showing results for <i>{{query}}</i></p>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-secondary small font-weight-bold">HASHTAGS <span class="pl-1 text-lighter">({{results.hashtags.length}})</span></p>
|
||||||
|
</div>
|
||||||
|
<div v-if="results.hashtags.length">
|
||||||
|
<a v-for="(hashtag, index) in results.hashtags" class="mb-2 result-card" :href="buildUrl('hashtag', hashtag)">
|
||||||
|
<div class="pb-3">
|
||||||
|
<div class="media align-items-center py-2 pr-3">
|
||||||
|
<span class="d-inline-flex align-items-center justify-content-center border rounded-circle mr-3" style="width: 50px;height: 50px;">
|
||||||
|
<i class="fas fa-hashtag text-muted"></i>
|
||||||
|
</span>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 text-truncate text-dark font-weight-bold" data-toggle="tooltip" :title="hashtag.value">
|
||||||
|
#{{hashtag.value}}
|
||||||
|
</p>
|
||||||
|
<p v-if="hashtag.count > 2" class="mb-0 small font-weight-bold text-muted text-uppercase">
|
||||||
|
{{hashtag.count}} posts
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="border py-3 text-center font-weight-bold">No results found</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="analysis == 'profile'" class="row">
|
||||||
|
<div class="col-12 mb-5">
|
||||||
|
<p class="h5 font-weight-bold text-dark">Showing results for <i>{{query}}</i></p>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-secondary small font-weight-bold">PROFILES <span class="pl-1 text-lighter">({{results.profiles.length}})</span></p>
|
||||||
|
</div>
|
||||||
|
<div v-if="results.profiles.length">
|
||||||
|
<div v-for="(profile, index) in results.profiles" class="card mb-4">
|
||||||
|
<div class="card-header p-0 m-0">
|
||||||
|
<div style="width: 100%;height: 140px;background: #0070b7"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-center mt-n5 mb-4">
|
||||||
|
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" :src="profile.entity.thumb" width="90px" height="90px;" onerror="this.onerror=null;this.src='/storage/avatars/default.png';">
|
||||||
|
</div>
|
||||||
|
<p class="text-center lead font-weight-bold mb-1">{{profile.value}}</p>
|
||||||
|
<p class="text-center text-muted small text-uppercase mb-4"><!-- 2 followers --></p>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<button v-if="profile.entity.following" type="button" class="btn btn-outline-secondary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3" style="font-weight: 500">Following</button>
|
||||||
|
<a class="btn btn-primary btn-sm py-1 px-4 text-uppercase font-weight-bold" :href="buildUrl('profile',profile)" style="font-weight: 500">View Profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="border py-3 text-center font-weight-bold">No results found</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="analysis == 'webfinger'" class="row">
|
||||||
|
<div class="col-12 mb-5">
|
||||||
|
<p class="h5 font-weight-bold text-dark">Showing results for <i>{{query}}</i></p>
|
||||||
|
<hr>
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<div v-for="(profile, index) in results.profiles" class="card mb-2">
|
||||||
|
<div class="card-header p-0 m-0">
|
||||||
|
<div style="width: 100%;height: 140px;background: #0070b7"></div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-center mt-n5 mb-4">
|
||||||
|
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" :src="profile.entity.thumb" width="90px" height="90px;" onerror="this.onerror=null;this.src='/storage/avatars/default.png';">
|
||||||
|
</div>
|
||||||
|
<p class="text-center lead font-weight-bold mb-1">{{profile.value}}</p>
|
||||||
|
<p class="text-center text-muted small text-uppercase mb-4"><!-- 2 followers --></p>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<!-- <button v-if="profile.entity.following" type="button" class="btn btn-outline-secondary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3" style="font-weight: 500">Unfollow</button> -->
|
||||||
|
<!-- <button v-else type="button" class="btn btn-primary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3" style="font-weight: 500">Follow</button> -->
|
||||||
|
<a class="btn btn-primary btn-sm py-1 px-4 text-uppercase font-weight-bold" :href="'/i/web/profile/_/' + profile.entity.id" style="font-weight: 500">View Profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="col-12">
|
||||||
|
<p class="text-center text-muted lead font-weight-bold">No results found</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style type="text/css" scoped>
|
<style type="text/css" scoped>
|
||||||
|
.result-card {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.result-card .media:hover {
|
||||||
|
background: #EDF2F7;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.container {
|
||||||
|
max-width: 995px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -108,36 +214,25 @@ export default {
|
||||||
hashtags: true,
|
hashtags: true,
|
||||||
profiles: true,
|
profiles: true,
|
||||||
statuses: true
|
statuses: true
|
||||||
}
|
},
|
||||||
|
analysis: 'profile',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.fetchSearchResults();
|
this.bootSearch();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
$('.search-bar input').val(this.query);
|
$('.search-bar input').val(this.query);
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
bootSearch() {
|
||||||
|
let lexer = this.searchLexer();
|
||||||
|
this.analysis = lexer;
|
||||||
|
this.fetchSearchResults();
|
||||||
|
},
|
||||||
|
|
||||||
fetchSearchResults() {
|
fetchSearchResults() {
|
||||||
axios.get('/api/search', {
|
this.searchContext(this.analysis);
|
||||||
params: {
|
|
||||||
'q': this.query,
|
|
||||||
'src': 'metro',
|
|
||||||
'v': 1
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
let results = res.data;
|
|
||||||
this.results.hashtags = results.hashtags;
|
|
||||||
this.results.profiles = results.profiles;
|
|
||||||
this.results.statuses = results.posts;
|
|
||||||
this.loading = false;
|
|
||||||
}).catch(err => {
|
|
||||||
this.loading = false;
|
|
||||||
// this.networkError = true;
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
followProfile(profile, index) {
|
followProfile(profile, index) {
|
||||||
|
@ -159,6 +254,141 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
searchLexer() {
|
||||||
|
let q = this.query;
|
||||||
|
|
||||||
|
if(q.startsWith('#')) {
|
||||||
|
return 'hashtag';
|
||||||
|
}
|
||||||
|
|
||||||
|
if((q.match(/@/g) || []).length == 2) {
|
||||||
|
return 'webfinger';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(q.startsWith('@') || q.search('@') != -1) {
|
||||||
|
return 'profile';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(q.startsWith('https://')) {
|
||||||
|
return 'remote';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'all';
|
||||||
|
},
|
||||||
|
|
||||||
|
buildUrl(type = 'hashtag', obj) {
|
||||||
|
switch(type) {
|
||||||
|
case 'hashtag':
|
||||||
|
return obj.url + '?src=search';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'profile':
|
||||||
|
if(obj.entity.local == true) {
|
||||||
|
return obj.url;
|
||||||
|
}
|
||||||
|
return '/i/web/profile/_/' + obj.entity.id;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return obj.url + '?src=search';
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
searchContext(type) {
|
||||||
|
switch(type) {
|
||||||
|
case 'all':
|
||||||
|
axios.get('/api/search', {
|
||||||
|
params: {
|
||||||
|
'q': this.query,
|
||||||
|
'src': 'metro',
|
||||||
|
'v': 1,
|
||||||
|
'scope': 'all'
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
let results = res.data;
|
||||||
|
this.results.hashtags = results.hashtags ? results.hashtags : [];
|
||||||
|
this.results.profiles = results.profiles ? results.profiles : [];
|
||||||
|
this.results.statuses = results.posts ? results.posts : [];
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(err => {
|
||||||
|
this.loading = false;
|
||||||
|
console.log(err);
|
||||||
|
this.networkError = true;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'hashtag':
|
||||||
|
axios.get('/api/search', {
|
||||||
|
params: {
|
||||||
|
'q': this.query.slice(1),
|
||||||
|
'src': 'metro',
|
||||||
|
'v': 1,
|
||||||
|
'scope': 'hashtag'
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
let results = res.data;
|
||||||
|
this.results.hashtags = results.hashtags ? results.hashtags : [];
|
||||||
|
this.results.profiles = results.profiles ? results.profiles : [];
|
||||||
|
this.results.statuses = results.posts ? results.posts : [];
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(err => {
|
||||||
|
this.loading = false;
|
||||||
|
console.log(err);
|
||||||
|
this.networkError = true;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'profile':
|
||||||
|
axios.get('/api/search', {
|
||||||
|
params: {
|
||||||
|
'q': this.query,
|
||||||
|
'src': 'metro',
|
||||||
|
'v': 1,
|
||||||
|
'scope': 'profile'
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
let results = res.data;
|
||||||
|
this.results.hashtags = results.hashtags ? results.hashtags : [];
|
||||||
|
this.results.profiles = results.profiles ? results.profiles : [];
|
||||||
|
this.results.statuses = results.posts ? results.posts : [];
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(err => {
|
||||||
|
this.loading = false;
|
||||||
|
console.log(err);
|
||||||
|
this.networkError = true;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'webfinger':
|
||||||
|
axios.get('/api/search', {
|
||||||
|
params: {
|
||||||
|
'q': this.query,
|
||||||
|
'src': 'metro',
|
||||||
|
'v': 1,
|
||||||
|
'scope': 'webfinger'
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
let results = res.data;
|
||||||
|
this.results.hashtags = [];
|
||||||
|
this.results.profiles = results.profiles;
|
||||||
|
this.results.statuses = [];
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(err => {
|
||||||
|
this.loading = false;
|
||||||
|
console.log(err);
|
||||||
|
this.networkError = true;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.loading = false;
|
||||||
|
this.networkError = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,10 @@
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<div v-for="(story, index) in stories" class="list-group-item text-center text-dark" href="#">
|
<div v-for="(story, index) in stories" class="list-group-item text-center text-dark" href="#">
|
||||||
<div class="media align-items-center">
|
<div class="media align-items-center">
|
||||||
<img :src="story.src" class="img-fluid mr-3 cursor-pointer" width="70px" height="70px" @click="showLightbox(story)">
|
<div class="mr-3 cursor-pointer" @click="showLightbox(story)">
|
||||||
|
<img :src="story.src" class="img-fluid" width="70px" height="70px">
|
||||||
|
<p class="small text-muted text-center mb-0">(expand)</p>
|
||||||
|
</div>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<p class="mb-0">Expires</p>
|
<p class="mb-0">Expires</p>
|
||||||
<p class="mb-0 text-muted small"><span>{{expiresTimestamp(story.expires_at)}}</span></p>
|
<p class="mb-0 text-muted small"><span>{{expiresTimestamp(story.expires_at)}}</span></p>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="stories.length != 0">
|
<div v-if="stories.length != 0">
|
||||||
<div id="storyContainer" class="m-3"></div>
|
<div id="storyContainer" :class="[list == true ? 'mt-1 mr-3 mb-0 ml-1':'m-3']"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
let Zuck = require('zuck.js');
|
let Zuck = require('zuck.js');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: ['list'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
stories: {},
|
stories: {},
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let data = res.data;
|
let data = res.data;
|
||||||
let stories = new Zuck('storyContainer', {
|
let stories = new Zuck('storyContainer', {
|
||||||
|
list: this.list == true ? true : false,
|
||||||
stories: data,
|
stories: data,
|
||||||
localStorage: true,
|
localStorage: true,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
stories: {},
|
stories: {},
|
||||||
|
preloadIndex: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -36,14 +37,43 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetchStories() {
|
fetchStories() {
|
||||||
|
let self = this;
|
||||||
axios.get('/api/stories/v0/profile/' + this.pid)
|
axios.get('/api/stories/v0/profile/' + this.pid)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let data = res.data;
|
self.stories = res.data;
|
||||||
if(data.length == 0) {
|
if(res.data.length == 0) {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window._storyData = data;
|
self.preloadImages();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
// window.location.href = '/';
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
preloadImages() {
|
||||||
|
let self = this;
|
||||||
|
for (var i = 0; i < this.stories[0].items.length; i++) {
|
||||||
|
var preload = new Image();
|
||||||
|
$(preload).on('load', function() {
|
||||||
|
|
||||||
|
self.preloadIndex = i;
|
||||||
|
if(i == self.stories[0].items.length) {
|
||||||
|
self.loadViewer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
preload.src = self.stories[0].items[i].src;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadViewer() {
|
||||||
|
let data = this.stories;
|
||||||
|
|
||||||
|
if(!window.stories) {
|
||||||
window.stories = new Zuck('storyContainer', {
|
window.stories = new Zuck('storyContainer', {
|
||||||
stories: data,
|
stories: data,
|
||||||
localStorage: false,
|
localStorage: false,
|
||||||
|
@ -67,15 +97,12 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.loading = false;
|
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
// todo: refactor this mess
|
// todo: refactor this mess
|
||||||
document.querySelectorAll('#storyContainer .story')[0].click()
|
document.querySelectorAll('#storyContainer .story')[0].click();
|
||||||
})
|
}
|
||||||
.catch(err => {
|
return;
|
||||||
window.location.href = '/';
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,18 +180,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="status.id == replyId && !status.comments_disabled" class="card-footer bg-white px-2 py-0">
|
<!--<div v-if="status.id == replyId && !status.comments_disabled" class="card-footer bg-white px-2 py-0">
|
||||||
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
||||||
<li class="nav-item" v-on:click="emojiReaction(status)" v-for="e in emoji">{{e}}</li>
|
<li class="nav-item" v-on:click="emojiReaction(status)" v-for="e in emoji">{{e}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>-->
|
||||||
|
|
||||||
<div v-if="status.id == replyId && !status.comments_disabled" class="card-footer bg-white sticky-md-bottom p-0">
|
<!--<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">
|
<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>
|
<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)" :disabled="replyText.length == 0" />
|
<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>
|
</form>
|
||||||
</div>
|
</div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!loading && feed.length">
|
<div v-if="!loading && feed.length">
|
||||||
|
@ -448,6 +448,44 @@
|
||||||
<img :src="lightboxMedia.url" style="max-height: 100%; max-width: 100%">
|
<img :src="lightboxMedia.url" style="max-height: 100%; max-width: 100%">
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
<b-modal ref="replyModal"
|
||||||
|
id="ctx-reply-modal"
|
||||||
|
hide-footer
|
||||||
|
centered
|
||||||
|
rounded
|
||||||
|
:title-html="replyStatus.account ? 'Reply to <span class=text-dark>' + replyStatus.account.username + '</span>' : ''"
|
||||||
|
title-tag="p"
|
||||||
|
title-class="font-weight-bold text-muted"
|
||||||
|
size="md"
|
||||||
|
body-class="p-2 rounded">
|
||||||
|
<div>
|
||||||
|
<textarea class="form-control" rows="4" style="border: none; font-size: 18px; resize: none; white-space: nowrap;outline: none;" placeholder="Reply here ..." v-model="replyText">
|
||||||
|
</textarea>
|
||||||
|
|
||||||
|
<div class="border-top border-bottom my-2">
|
||||||
|
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
||||||
|
<li class="nav-item" v-on:click="emojiReaction(status)" v-for="e in emoji">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<span class="pl-2 small text-muted font-weight-bold text-monospace">
|
||||||
|
{{replyText.length}}/600
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<!-- <select class="custom-select custom-select-sm my-0 mr-2">
|
||||||
|
<option value="public" selected="">Public</option>
|
||||||
|
<option value="unlisted">Unlisted</option>
|
||||||
|
<option value="followers">Followers Only</option>
|
||||||
|
</select> -->
|
||||||
|
<button class="btn btn-primary btn-sm py-2 px-4 lead text-uppercase font-weight-bold" v-on:click.prevent="commentSubmit(status, $event)" :disabled="replyText.length == 0">
|
||||||
|
{{replySending == true ? 'POSTING' : 'POST'}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -513,6 +551,11 @@
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
#ctx-reply-modal .form-control:focus {
|
||||||
|
border: none;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -558,6 +601,7 @@
|
||||||
copiedEmbed: false,
|
copiedEmbed: false,
|
||||||
showTips: true,
|
showTips: true,
|
||||||
userStory: false,
|
userStory: false,
|
||||||
|
replySending: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -736,15 +780,20 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
commentFocus(status, $event) {
|
commentFocus(status, $event) {
|
||||||
if(this.replyId == status.id || status.comments_disabled) {
|
if(status.comments_disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.status = status;
|
||||||
this.replies = {};
|
this.replies = {};
|
||||||
this.replyStatus = {};
|
this.replyStatus = {};
|
||||||
this.replyText = '';
|
this.replyText = '';
|
||||||
this.replyId = status.id;
|
this.replyId = status.id;
|
||||||
this.replyStatus = status;
|
this.replyStatus = status;
|
||||||
|
this.$refs.replyModal.show();
|
||||||
this.fetchStatusComments(status, '');
|
this.fetchStatusComments(status, '');
|
||||||
|
return;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
likeStatus(status) {
|
likeStatus(status) {
|
||||||
|
@ -864,6 +913,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
commentSubmit(status, $event) {
|
commentSubmit(status, $event) {
|
||||||
|
this.replySending = true;
|
||||||
let id = status.id;
|
let id = status.id;
|
||||||
let comment = this.replyText;
|
let comment = this.replyText;
|
||||||
axios.post('/i/comment', {
|
axios.post('/i/comment', {
|
||||||
|
@ -871,8 +921,10 @@
|
||||||
comment: comment
|
comment: comment
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
this.replyText = '';
|
this.replyText = '';
|
||||||
this.replies.push(res.data.entity);
|
this.replies.unshift(res.data.entity);
|
||||||
|
this.$refs.replyModal.hide();
|
||||||
});
|
});
|
||||||
|
this.replySending = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
moderatePost(status, action, $event) {
|
moderatePost(status, action, $event) {
|
||||||
|
@ -1359,22 +1411,19 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
statusUrl(status) {
|
statusUrl(status) {
|
||||||
return status.url;
|
if(status.local == true) {
|
||||||
|
return status.url;
|
||||||
|
}
|
||||||
|
|
||||||
// if(status.local == true) {
|
return '/i/web/post/_/' + status.account.id + '/' + status.id;
|
||||||
// return status.url;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return '/i/web/post/_/' + status.account.id + '/' + status.id;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
profileUrl(status) {
|
profileUrl(status) {
|
||||||
return status.account.url;
|
if(status.local == true) {
|
||||||
// if(status.local == true) {
|
return status.account.url;
|
||||||
// return status.account.url;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// return '/i/web/profile/_/' + status.account.id;
|
return '/i/web/profile/_/' + status.account.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
statusCardUsernameFormat(status) {
|
statusCardUsernameFormat(status) {
|
||||||
|
|
34
resources/assets/js/rempos.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
Vue.component(
|
||||||
|
'photo-presenter',
|
||||||
|
require('./components/presenter/PhotoPresenter.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'video-presenter',
|
||||||
|
require('./components/presenter/VideoPresenter.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'photo-album-presenter',
|
||||||
|
require('./components/presenter/PhotoAlbumPresenter.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'video-album-presenter',
|
||||||
|
require('./components/presenter/VideoAlbumPresenter.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'mixed-album-presenter',
|
||||||
|
require('./components/presenter/MixedAlbumPresenter.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'post-menu',
|
||||||
|
require('./components/PostMenu.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'remote-post',
|
||||||
|
require('./components/RemotePost.vue').default
|
||||||
|
);
|
4
resources/assets/js/rempro.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Vue.component(
|
||||||
|
'remote-profile',
|
||||||
|
require('./components/RemoteProfile.vue').default
|
||||||
|
);
|
4
resources/assets/sass/landing.scss
vendored
|
@ -1,10 +1,10 @@
|
||||||
// Landing Page bundle
|
// Landing Page bundle
|
||||||
|
|
||||||
|
@import "fonts";
|
||||||
|
@import "lib/fontawesome";
|
||||||
@import 'variables';
|
@import 'variables';
|
||||||
@import '~bootstrap/scss/bootstrap';
|
@import '~bootstrap/scss/bootstrap';
|
||||||
@import 'custom';
|
@import 'custom';
|
||||||
@import 'landing/carousel';
|
|
||||||
@import 'landing/devices';
|
|
||||||
|
|
||||||
.container.slim {
|
.container.slim {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
11
resources/lang/pl/exception.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'compose' => [
|
||||||
|
'invalid' => [
|
||||||
|
'album' => 'Musi zawierać jedno zdjęcie lub film, lub wiele zdjęć.',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
26
resources/lang/pl/helpcenter.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'helpcenter' => 'Centrum pomocy',
|
||||||
|
'whatsnew' => 'Co nowego',
|
||||||
|
|
||||||
|
'gettingStarted' => 'Rozpocznij',
|
||||||
|
'sharingMedia' => 'Wstawianie multimediów',
|
||||||
|
'profile' => 'Profil',
|
||||||
|
'stories' => 'Relacje',
|
||||||
|
'hashtags' => 'Hashtagi',
|
||||||
|
'discover' => 'Odkrywanie',
|
||||||
|
'directMessages' => 'Wiadomości bezpośrednie',
|
||||||
|
'timelines' => 'Osie czasu',
|
||||||
|
'embed' => 'Embed',
|
||||||
|
|
||||||
|
'communityGuidelines' => 'Wytyczne dla społeczności',
|
||||||
|
'whatIsTheFediverse' => 'Czym jest Fediwersum?',
|
||||||
|
'controllingVisibility' => 'Kontrolowanie widoczności',
|
||||||
|
'blockingAccounts' => 'Blokowanie kont',
|
||||||
|
'safetyTips' => 'Wskazówki dot. bezpieczeństwa',
|
||||||
|
'reportSomething' => 'Zgłaszanie treści',
|
||||||
|
'dataPolicy' => 'Polityka przechowywania danych'
|
||||||
|
|
||||||
|
];
|
|
@ -1,18 +1,19 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'search' => 'Szukaj',
|
||||||
'search' => 'Szukaj',
|
'home' => 'Strona główna',
|
||||||
'home' => 'Strona główna',
|
'local' => 'Lokalne',
|
||||||
'local' => 'Lokalne',
|
'network' => 'Sieć',
|
||||||
'discover' => 'Odkrywaj',
|
'discover' => 'Odkrywaj',
|
||||||
'viewMyProfile' => 'Pokaż mój profil',
|
'viewMyProfile' => 'Pokaż mój profil',
|
||||||
'myTimeline' => 'Moja oś czasu',
|
'myProfile' => 'Mój profil',
|
||||||
'publicTimeline' => 'Publiczna oś czasu',
|
'myTimeline' => 'Moja oś czasu',
|
||||||
'remoteFollow' => 'Zdalne śledzenie',
|
'publicTimeline' => 'Publiczna oś czasu',
|
||||||
'settings' => 'Ustawienia',
|
'remoteFollow' => 'Zdalne śledzenie',
|
||||||
'admin' => 'Administracja',
|
'settings' => 'Ustawienia',
|
||||||
'logout' => 'Wyloguj się',
|
'admin' => 'Administracja',
|
||||||
'directMessages' => 'Wiadomości bezpośrednie',
|
'logout' => 'Wyloguj się',
|
||||||
|
'directMessages' => 'Wiadomości bezpośrednie',
|
||||||
|
'composePost' => 'Uwtórz wpis',
|
||||||
];
|
];
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
return [
|
return [
|
||||||
|
|
||||||
'likedPhoto' => 'polubił(a) Twoje zdjęcie.',
|
'likedPhoto' => 'polubił(a) Twoje zdjęcie.',
|
||||||
|
'likedComment' => 'polubił(a) Twój komentarz.',
|
||||||
'startedFollowingYou' => 'zaczął(-ęła) Cię obserwować.',
|
'startedFollowingYou' => 'zaczął(-ęła) Cię obserwować.',
|
||||||
'commented' => 'skomentował(a) Twój wpis',
|
'commented' => 'skomentował(a) Twój wpis',
|
||||||
'mentionedYou' => 'wspomniał(a) o Tobie.',
|
'mentionedYou' => 'wspomniał(a) o Tobie.',
|
||||||
|
|
|
@ -9,4 +9,7 @@ return [
|
||||||
'privateProfileWarning' => 'To konto jest prywatne',
|
'privateProfileWarning' => 'To konto jest prywatne',
|
||||||
'alreadyFollow' => 'Już obserwujesz :username?',
|
'alreadyFollow' => 'Już obserwujesz :username?',
|
||||||
'loginToSeeProfile' => 'aby zobaczyć zdjęcia i filmy tego użytkownika.',
|
'loginToSeeProfile' => 'aby zobaczyć zdjęcia i filmy tego użytkownika.',
|
||||||
|
|
||||||
|
'status.disabled.header' => 'Profil jest niedostępny',
|
||||||
|
'status.disabled.body' => 'Przepraszamy, ten profil nie jest obecnie dostępny. Spróbuj ponownie za jakiś czas.',
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,5 +12,9 @@ return [
|
||||||
'l10nWip' => 'Wciąż pracujemy nad obsługą wielu języków',
|
'l10nWip' => 'Wciąż pracujemy nad obsługą wielu języków',
|
||||||
'currentLocale' => 'Obecny język',
|
'currentLocale' => 'Obecny język',
|
||||||
'selectLocale' => 'Wybierz jeden z dostępnych języków',
|
'selectLocale' => 'Wybierz jeden z dostępnych języków',
|
||||||
|
'contact' => 'Kontakt',
|
||||||
|
'contact-us' => 'Skontaktuj się z naim',
|
||||||
|
'places' => 'Miejsca',
|
||||||
|
'profiles' => 'Profile',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -13,19 +13,23 @@
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="form-group row">
|
<div class="form-group">
|
||||||
|
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" placeholder="{{__('Password')}}" required>
|
||||||
|
|
||||||
<div class="col-md-12">
|
@if ($errors->has('password'))
|
||||||
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" placeholder="{{__('Password')}}" required>
|
<span class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('password') }}</strong>
|
||||||
@if ($errors->has('password'))
|
</span>
|
||||||
<span class="invalid-feedback">
|
@endif
|
||||||
<strong>{{ $errors->first('password') }}</strong>
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="trusted-device" name="trustDevice">
|
||||||
|
<label class="custom-control-label text-muted" for="trusted-device">Don't ask me again, trust this device</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group row mb-0">
|
<div class="form-group row mb-0">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<button type="submit" class="btn btn-success btn-block font-weight-bold">
|
<button type="submit" class="btn btn-success btn-block font-weight-bold">
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
<link href="{{ mix('css/app.css') }}" rel="stylesheet" data-stylesheet="light">
|
<link href="{{ mix('css/app.css') }}" rel="stylesheet" data-stylesheet="light">
|
||||||
@stack('styles')
|
@stack('styles')
|
||||||
|
|
||||||
<script type="text/javascript">window.App = {}; window.App.config = {!!App\Util\Site\Config::json()!!}</script>
|
<script type="text/javascript">window._sharedData = {curUser: {}, version: 0}; window.App = {config: {!!App\Util\Site\Config::json()!!}};</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class="">
|
<body class="w-100 h-100">
|
||||||
<main id="content">
|
<main id="content" class="w-100 h-100">
|
||||||
@yield('content')
|
@yield('content')
|
||||||
</main>
|
</main>
|
||||||
<script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
|
<script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
|
||||||
|
|
16
resources/views/profile/remote.blade.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<remote-profile profile-id="{{$profile->id}}"></remote-profile>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('meta')
|
||||||
|
<meta name="robots" content="noindex, noimageindex, nofollow, nosnippet, noarchive">
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript" src="{{mix('js/rempro.js')}}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
App.boot();
|
||||||
|
</script>
|
||||||
|
@endpush
|
|
@ -1,7 +1,7 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<story-viewer pid="{{$pid}}"></story-viewer>
|
<story-viewer pid="{{$pid}}" redirect="{{$profile->url()}}"></story-viewer>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
<search-results query="{{request()->query('q')}}" profile-id="{{Auth::user()->profile->id}}"></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/compose.js')}}"></script>
|
||||||
<script type="text/javascript" src="{{mix('js/search.js')}}"></script>
|
<script type="text/javascript" src="{{mix('js/search.js')}}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">App.boot();</script>
|
||||||
new Vue({
|
|
||||||
el: '#content'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@endpush
|
@endpush
|
|
@ -9,6 +9,9 @@
|
||||||
<div class="alert alert-primary px-3 h6 text-center">
|
<div class="alert alert-primary px-3 h6 text-center">
|
||||||
<strong>Warning:</strong> Some experimental features may contain bugs or missing functionality
|
<strong>Warning:</strong> Some experimental features may contain bugs or missing functionality
|
||||||
</div>
|
</div>
|
||||||
|
<div class="alert alert-warning px-3 h6">
|
||||||
|
We are deprecating Labs in a future version. Some features will no longer be supported. For more information, click <a href="{{route('help.labs-deprecation')}}" class="font-weight-bold">here</a>.
|
||||||
|
</div>
|
||||||
<div class="py-3">
|
<div class="py-3">
|
||||||
<p class="font-weight-bold text-muted text-center">UI</p>
|
<p class="font-weight-bold text-muted text-center">UI</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.muted-users')}}">Muted Users</a>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-users')}}">Blocked Users</a>
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-keywords')}}">Blocked keywords</a>
|
|
||||||
<a class="btn btn-outline-secondary py-0 font-weight-bold" href="{{route('settings.privacy.blocked-instances')}}">Blocked instances</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
|
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
|
||||||
<div class="card-header text-light font-weight-bold h4 p-4">Discover Tips</div>
|
<div class="card-header text-light font-weight-bold h4 p-4 bg-primary">Discover Tips</div>
|
||||||
<div class="card-body bg-white p-3">
|
<div class="card-body bg-white p-3">
|
||||||
<ul class="pt-3">
|
<ul class="pt-3">
|
||||||
<li class="lead mb-4">To make your posts more discoverable, add hashtags to your posts.</li>
|
<li class="lead mb-4">To make your posts more discoverable, add hashtags to your posts.</li>
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
|
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
|
||||||
<div class="card-header text-light font-weight-bold h4 p-4">Hashtag Tips</div>
|
<div class="card-header text-light font-weight-bold h4 p-4 bg-primary">Hashtag Tips</div>
|
||||||
<div class="card-body bg-white p-3">
|
<div class="card-body bg-white p-3">
|
||||||
<ul class="pt-3">
|
<ul class="pt-3">
|
||||||
<li class="lead mb-4">You cannot add spaces or punctuation in a hashtag, or it will not work properly.</li>
|
<li class="lead mb-4">You cannot add spaces or punctuation in a hashtag, or it will not work properly.</li>
|
||||||
|
|
21
resources/views/site/help/labs-deprecation.blade.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
@extends('site.help.partial.template', ['breadcrumb'=>'Labs Deprecation'])
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<h3 class="font-weight-bold">Labs Deprecation</h3>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<p class="lead">We are deprecating Labs in a future version. No new experiments will be released. We will give 6 months notice before removing Labs.</p>
|
||||||
|
<hr>
|
||||||
|
<h4 class="font-weight-bold">When will labs be deprecated?</h4>
|
||||||
|
<p>TBA</p>
|
||||||
|
<hr>
|
||||||
|
<h4 class="font-weight-bold">What features will be deprecated?</h4>
|
||||||
|
<p>TBA</p>
|
||||||
|
<hr>
|
||||||
|
<h4 class="font-weight-bold">Why is Labs being deprecated?</h4>
|
||||||
|
<p>Labs was started in early 2019 to test experimental designs and features. Now the project is more mature, we are outgrowing some of these experiments.</p>
|
||||||
|
<hr>
|
||||||
|
<p class="small">Last Updated: April 10/2020</p>
|
||||||
|
@endsection
|
|
@ -3,12 +3,6 @@
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||||
<div class="col-12">
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<p class="lead mb-0">Some sections may contain out of date information</p>
|
|
||||||
<p class="mb-0">We apologize for any inconvenience, we are working on updating the Help Center.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 px-0">
|
<div class="col-12 px-0">
|
||||||
<div class="card mt-md-5 px-0 mx-md-3">
|
<div class="card mt-md-5 px-0 mx-md-3">
|
||||||
<div class="card-header font-weight-bold text-muted bg-white py-4">
|
<div class="card-header font-weight-bold text-muted bg-white py-4">
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
<div class="py-3"></div>
|
<div class="py-3"></div>
|
||||||
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
|
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
|
||||||
<div class="card-header text-light font-weight-bold h4 p-4">Timeline Tips</div>
|
<div class="card-header text-light font-weight-bold h4 p-4 bg-primary">Timeline Tips</div>
|
||||||
<div class="card-body bg-white p-3">
|
<div class="card-body bg-white p-3">
|
||||||
<ul class="pt-3">
|
<ul class="pt-3">
|
||||||
<li class="lead mb-4">You can mute or block accounts to prevent them from appearing in timelines.</li>
|
<li class="lead mb-4">You can mute or block accounts to prevent them from appearing in timelines.</li>
|
||||||
|
|
|
@ -23,161 +23,281 @@
|
||||||
<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/landing.css') }}" rel="stylesheet">
|
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||||
<script type="text/javascript">window.App = {}; window.App.config = {!!App\Util\Site\Config::json()!!}</script>
|
<style type="text/css">
|
||||||
|
.feature-circle {
|
||||||
|
display: flex !important;
|
||||||
|
-webkit-box-pack: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
-webkit-box-align: center !important;
|
||||||
|
align-items: center !important;
|
||||||
|
margin-right: 1rem !important;
|
||||||
|
background-color: #08d !important;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
width: 60px;
|
||||||
|
height:60px;
|
||||||
|
}
|
||||||
|
.section-spacer {
|
||||||
|
height: 13vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="">
|
<body class="">
|
||||||
<main id="content">
|
<main id="content">
|
||||||
<section class="container">
|
<section class="container">
|
||||||
<div class="row py-5 mb-5">
|
<div class="section-spacer"></div>
|
||||||
|
<div class="row pt-md-5 mt-5">
|
||||||
<div class="col-12 col-md-6 d-none d-md-block">
|
<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="m-my-4">
|
||||||
<div class="marvel-device note8" style="position: absolute;z-index:10;">
|
<p class="display-2 font-weight-bold">Photo Sharing</p>
|
||||||
<div class="inner"></div>
|
<p class="h1 font-weight-bold">For Everyone.</p>
|
||||||
<div class="overflow">
|
</div>
|
||||||
<div class="shadow"></div>
|
</div>
|
||||||
</div>
|
<div class="col-12 col-md-5 offset-md-1">
|
||||||
<div class="speaker"></div>
|
<div>
|
||||||
<div class="sensors"></div>
|
<div class="pt-md-3 d-flex justify-content-center align-items-center">
|
||||||
<div class="more-sensors"></div>
|
<img src="/img/pixelfed-icon-color.svg" loading="lazy" width="50px" height="50px">
|
||||||
<div class="sleep"></div>
|
<span class="font-weight-bold h3 ml-2 pt-2">Pixelfed</span>
|
||||||
<div class="volume"></div>
|
</div>
|
||||||
<div class="camera"></div>
|
<div class="d-block d-md-none">
|
||||||
<div class="screen">
|
<p class="font-weight-bold mb-0 text-center">Photo Sharing. For Everyone</p>
|
||||||
<img src="/img/landing/android_1.jpg" class="img-fluid" loading="lazy">
|
</div>
|
||||||
|
<div class="card my-4 shadow-none border">
|
||||||
|
<div class="card-body px-lg-5">
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="small text-uppercase font-weight-bold text-muted">Account Login</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form class="px-1" method="POST" action="{{ route('login') }}" id="login_form">
|
||||||
|
@csrf
|
||||||
|
<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="{{__('Email')}}" required autofocus>
|
||||||
|
|
||||||
|
@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">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}>
|
||||||
|
<span class="font-weight-bold small ml-1 text-muted">
|
||||||
|
{{ __('Remember Me') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-0">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block py-0 font-weight-bold text-uppercase">
|
||||||
|
{{ __('Login') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="marvel-device iphone-x" style="position: absolute;z-index: 20;margin: 99px 0 0 151px;">
|
<div class="card shadow-none border card-body">
|
||||||
<div class="notch">
|
<p class="text-center mb-0 font-weight-bold small">
|
||||||
<div class="camera"></div>
|
@if(config('pixelfed.open_registration'))
|
||||||
<div class="speaker"></div>
|
<a href="/register">Register</a>
|
||||||
|
<span class="px-1">·</span>
|
||||||
|
@endif
|
||||||
|
<a href="/password/reset">Password Reset</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section-spacer"></div>
|
||||||
|
<div class="row py-5 mt-5 mb-5">
|
||||||
|
<div class="col-12 col-md-6 d-none d-md-block">
|
||||||
|
<div>
|
||||||
|
<div class="row mt-4 mb-1">
|
||||||
|
<div class="col-4 mt-2 px-0">
|
||||||
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/1.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="top-bar"></div>
|
<div class="col-4 mt-2 px-0">
|
||||||
<div class="sleep"></div>
|
<div class="px-1 shadow-none">
|
||||||
<div class="bottom-bar"></div>
|
<img src="/_landing/2.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
<div class="volume"></div>
|
</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>
|
||||||
<div class="inner-shadow"></div>
|
<div class="col-4 mt-2 px-0">
|
||||||
<div class="screen">
|
<div class="px-1 shadow-none">
|
||||||
<div id="iosDevice">
|
<img src="/_landing/3.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
<img src="/img/landing/ios_4.jpg" class="img-fluid" loading="lazy">
|
</div>
|
||||||
<img src="/img/landing/ios_3.jpg" class="img-fluid" loading="lazy">
|
</div>
|
||||||
<img src="/img/landing/ios_2.jpg" class="img-fluid" loading="lazy">
|
<div class="col-4 mt-2 px-0">
|
||||||
<img src="/img/landing/ios_1.jpg" class="img-fluid" loading="lazy">
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/4.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 mt-2 px-0">
|
||||||
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/5.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 mt-2 px-0">
|
||||||
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/6.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 mt-2 px-0">
|
||||||
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/7.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 mt-2 px-0">
|
||||||
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/8.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 mt-2 px-0">
|
||||||
|
<div class="px-1 shadow-none">
|
||||||
|
<img src="/_landing/9.jpeg" class="img-fluid" loading="lazy" width="640px" height="640px">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-5 offset-md-1">
|
<div class="col-12 col-md-5 offset-md-1">
|
||||||
<div>
|
<div class="section-spacer"></div>
|
||||||
<div class="card my-4 shadow-none border">
|
<div class="mt-5">
|
||||||
<div class="card-body px-lg-5">
|
<p class="text-center h1 font-weight-bold">Simple. Powerful.</p>
|
||||||
<div class="text-center pt-3">
|
</div>
|
||||||
<img src="/img/pixelfed-icon-color.svg">
|
<div class="mt-5">
|
||||||
</div>
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="py-3 text-center">
|
<span class="text-center">
|
||||||
<h3 class="font-weight-bold">Pixelfed</h3>
|
<span class="font-weight-bold h1">{{$data['stats']['posts']}}</span>
|
||||||
<p class="mb-0 lead">Photo sharing for everyone</p>
|
<span class="d-block text-muted text-uppercase">Posts</span>
|
||||||
</div>
|
</span>
|
||||||
<div>
|
<span class="text-center">
|
||||||
@if(true === config('pixelfed.open_registration'))
|
<span class="font-weight-bold h1">{{$data['stats']['likes']}}</span>
|
||||||
<form class="px-1" method="POST" action="{{ route('register') }}" id="register_form">
|
<span class="d-block text-muted text-uppercase">Likes</span>
|
||||||
@csrf
|
</span>
|
||||||
<div class="form-group row">
|
<span class="text-center">
|
||||||
<div class="col-md-12">
|
<span class="font-weight-bold h1">{{$data['stats']['hashtags']}}</span>
|
||||||
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" placeholder="{{ __('Name') }}" required autofocus>
|
<span class="d-block text-muted text-uppercase">Hashtags Used</span>
|
||||||
|
</span>
|
||||||
@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 maxlength="15" minlength="2">
|
|
||||||
|
|
||||||
@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>
|
|
||||||
<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">
|
|
||||||
{{ __('Register') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="mb-0 font-weight-bold text-lighter small">By signing up, you agree to our <a href="{{route('site.terms')}}" class="text-muted">Terms of Use</a> and <a href="{{route('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 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>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<p class="lead text-muted text-center">A free and ethical photo sharing platform.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row py-5 mb-5">
|
||||||
|
<div class="col-12 col-md-8 offset-md-2">
|
||||||
|
<div class="section-spacer"></div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<p class="text-center display-4 font-weight-bold">Feature Packed.</p>
|
||||||
|
</div>
|
||||||
|
<div class="my-2">
|
||||||
|
<p class="h4 font-weight-light text-muted text-center">The best for the brightest.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row pb-5 mb-5">
|
||||||
|
<div class="col-12 col-md-5 offset-md-1">
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="media">
|
||||||
|
<div class="feature-circle">
|
||||||
|
<i class="far fa-images fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h5 font-weight-bold mt-2 mb-0">Albums</p>
|
||||||
|
Create an album with up to <span class="font-weight-bold">{{config('pixelfed.max_album_length')}}</span> photos
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="media">
|
||||||
|
<div class="feature-circle">
|
||||||
|
<i class="far fa-folder fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h5 font-weight-bold mt-2 mb-0">Collections</p>
|
||||||
|
Organize your posts
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="media">
|
||||||
|
<div class="feature-circle">
|
||||||
|
<i class="fas fa-image fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h5 font-weight-bold mt-2 mb-0">Filters</p>
|
||||||
|
Add a filter to your photos
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-5 offset-md-1">
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="media">
|
||||||
|
<div class="feature-circle">
|
||||||
|
<i class="far fa-comment fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h5 font-weight-bold mt-2 mb-0">Comments</p>
|
||||||
|
Comment on a post, or send a reply
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="media">
|
||||||
|
<div class="feature-circle">
|
||||||
|
<i class="far fa-list-alt fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h5 font-weight-bold mt-2 mb-0">Discover</p>
|
||||||
|
Explore categories, hashtags and topics
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if(config('instance.stories.enabled'))
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="media">
|
||||||
|
<div class="feature-circle">
|
||||||
|
<i class="fas fa-history fa-lg"></i>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="h5 font-weight-bold mt-2 mb-0">Stories</p>
|
||||||
|
Share posts that disappear after 24h
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@include('layouts.partial.footer')
|
@include('layouts.partial.footer')
|
||||||
|
|
17
resources/views/status/remote.blade.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="mt-md-4"></div>
|
||||||
|
<remote-post status-template="{{$status->viewType()}}" status-id="{{$status->id}}" status-username="{{$status->profile->username}}" status-url="{{$status->url()}}" status-profile-url="{{$status->profile->url()}}" status-avatar="{{$status->profile->avatarUrl()}}" status-profile-id="{{$status->profile_id}}" profile-layout="metro"></remote-post>
|
||||||
|
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('meta')
|
||||||
|
<meta name="robots" content="noindex, noimageindex, nofollow, nosnippet, noarchive">
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript" src="{{ mix('js/rempos.js') }}"></script>
|
||||||
|
<script type="text/javascript">App.boot()</script>
|
||||||
|
@endpush
|
|
@ -1,4 +1,4 @@
|
||||||
@extends('layouts.app',['title' => "A post by " . $user->username])
|
@extends('layouts.app',['title' => "{$user->username} shared a post"])
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<noscript>
|
<noscript>
|
||||||
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript" src="{{ mix('js/status.js') }}"></script>
|
<script type="text/javascript" src="{{ mix('js/status.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
new Vue({
|
new Vue({
|
||||||
|
|
|
@ -171,7 +171,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
|
|
||||||
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
|
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
|
||||||
Route::get('exp/rec', 'ApiController@userRecommendations');
|
Route::get('exp/rec', 'ApiController@userRecommendations');
|
||||||
Route::post('discover/tag/subscribe', 'HashtagFollowController@store')->middleware('throttle:maxHashtagFollowsPerHour,60')->middleware('throttle:maxHashtagFollowsPerDay,1440');;
|
Route::post('discover/tag/subscribe', 'HashtagFollowController@store')->middleware('throttle:maxHashtagFollowsPerHour,60')->middleware('throttle:maxHashtagFollowsPerDay,1440');
|
||||||
Route::get('discover/tag/list', 'HashtagFollowController@getTags');
|
Route::get('discover/tag/list', 'HashtagFollowController@getTags');
|
||||||
// Route::get('profile/sponsor/{id}', 'ProfileSponsorController@get');
|
// Route::get('profile/sponsor/{id}', 'ProfileSponsorController@get');
|
||||||
Route::get('bookmarks', 'InternalApiController@bookmarks');
|
Route::get('bookmarks', 'InternalApiController@bookmarks');
|
||||||
|
@ -263,6 +263,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::post('stories/viewed', 'StoryController@apiV1Viewed');
|
Route::post('stories/viewed', 'StoryController@apiV1Viewed');
|
||||||
Route::get('stories/new', 'StoryController@compose');
|
Route::get('stories/new', 'StoryController@compose');
|
||||||
Route::get('my/story', 'StoryController@iRedirect');
|
Route::get('my/story', 'StoryController@iRedirect');
|
||||||
|
Route::get('web/profile/_/{id}', 'InternalApiController@remoteProfile');
|
||||||
|
Route::get('web/post/_/{profileId}/{statusid}', 'InternalApiController@remoteStatus');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'account'], function () {
|
Route::group(['prefix' => 'account'], function () {
|
||||||
|
@ -393,6 +395,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::view('blocking-accounts', 'site.help.blocking-accounts')->name('help.blocking-accounts');
|
Route::view('blocking-accounts', 'site.help.blocking-accounts')->name('help.blocking-accounts');
|
||||||
Route::view('report-something', 'site.help.report-something')->name('help.report-something');
|
Route::view('report-something', 'site.help.report-something')->name('help.report-something');
|
||||||
Route::view('data-policy', 'site.help.data-policy')->name('help.data-policy');
|
Route::view('data-policy', 'site.help.data-policy')->name('help.data-policy');
|
||||||
|
Route::view('labs-deprecation', 'site.help.labs-deprecation')->name('help.labs-deprecation');
|
||||||
|
|
||||||
});
|
});
|
||||||
Route::get('newsroom/{year}/{month}/{slug}', 'NewsroomController@show');
|
Route::get('newsroom/{year}/{month}/{slug}', 'NewsroomController@show');
|
||||||
Route::get('newsroom/archive', 'NewsroomController@archive');
|
Route::get('newsroom/archive', 'NewsroomController@archive');
|
||||||
|
|
3
webpack.mix.js
vendored
|
@ -38,6 +38,9 @@ mix.js('resources/assets/js/app.js', 'public/js')
|
||||||
// .js('resources/assets/js/direct.js', 'public/js')
|
// .js('resources/assets/js/direct.js', 'public/js')
|
||||||
// .js('resources/assets/js/admin.js', 'public/js')
|
// .js('resources/assets/js/admin.js', 'public/js')
|
||||||
// .js('resources/assets/js/micro.js', 'public/js')
|
// .js('resources/assets/js/micro.js', 'public/js')
|
||||||
|
.js('resources/assets/js/rempro.js', 'public/js')
|
||||||
|
.js('resources/assets/js/rempos.js', 'public/js')
|
||||||
|
//.js('resources/assets/js/timeline_next.js', 'public/js')
|
||||||
|
|
||||||
.extract([
|
.extract([
|
||||||
'lodash',
|
'lodash',
|
||||||
|
|