1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/pixelfed_ynh.git synced 2024-09-03 20:06:04 +02:00

Merge pull request #1781 from pixelfed/staging

ComposeUI v4
This commit is contained in:
daniel 2019-10-15 22:01:38 -06:00 committed by GitHub
commit d99e79aa60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 733 additions and 355 deletions

View file

@ -3,10 +3,36 @@
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.6...dev) ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.6...dev)
### Added ### Added
- Added drafts API endpoint for Camera Roll ([bad2ecde](https://github.com/pixelfed/pixelfed/commit/bad2ecde))
### Fixed ### Fixed
- Fixed like and share/reblog count on profiles ([86cb7d09](https://github.com/pixelfed/pixelfed/commit/86cb7d09))
- Fixed non federating self boosts ([0c59a55e](https://github.com/pixelfed/pixelfed/commit/0c59a55e))
- Fixed CORS issues with API endpoints ([6d6f517d](https://github.com/pixelfed/pixelfed/commit/6d6f517d))
- Fixed mixed albums not appearing on timelines ([e01dff45](https://github.com/pixelfed/pixelfed/commit/e01dff45))
### Changed ### Changed
- Removed ```relationship``` from ```AccountTransformer``` ([4d084ac5](https://github.com/pixelfed/pixelfed/commit/4d084ac5))
- Updated ```notification``` api endpoint to use ```NotificationService``` ([f4039ce2](https://github.com/pixelfed/pixelfed/commit/f4039ce2)) ([6ef7597](https://github.com/pixelfed/pixelfed/commit/6ef7597))
- Update footer to use localization for the ```Places``` link ([39712714](https://github.com/pixelfed/pixelfed/commit/39712714))
- Updated ComposeModal.vue, added a caption counter. Fixes [#1722](https://github.com/pixelfed/pixelfed/issues/1722). ([009c6ee8](https://github.com/pixelfed/pixelfed/commit/009c6ee8))
- Updated Notifications to use the NotificationService ([f4039ce2](https://github.com/pixelfed/pixelfed/commit/f4039ce218f93a5578225dfdba66f0359c8fc72c))
- Updated PrivacySettings controller, clear cache after updating ([d8d11d7b](https://github.com/pixelfed/pixelfed/commit/d8d11d7b))
- Updated BaseApiController, add timestamp to signed media previews for client side cache invalidation ([73c08987](https://github.com/pixelfed/pixelfed/commit/73c08987))
- Updated AdminInstanceController, remove db transaction from instance scan ([5773434a](https://github.com/pixelfed/pixelfed/commit/5773434a))
- Updated Help Center view, added outdated warning ([0e611d00](https://github.com/pixelfed/pixelfed/commit/0e611d00))
- Updated language view, added English version of language names ([ebb998d2](https://github.com/pixelfed/pixelfed/commit/ebb998d2))
- Updated app.js, added App.utils like ```.format.count```, ```.filters``` and ```.emoji``` ([34c13b6e](https://github.com/pixelfed/pixelfed/commit/34c13b6e))
- Updated CollectionCompose.vue component, fix api namespace change ([71ed965c](https://github.com/pixelfed/pixelfed/commit/71ed965c))
- Updated PostComponent, mark caption sensitive if post is and use util.emoji ([35d51215](https://github.com/pixelfed/pixelfed/commit/35d51215))
- Updated Profile.vue component, use formatted counts ([30f14961](https://github.com/pixelfed/pixelfed/commit/30f14961))
- Updated Timeline.vue component, use formatted counts, util.emoji and increase pagination limit to 5 ([abfc9fe7](https://github.com/pixelfed/pixelfed/commit/abfc9fe7))
- Updated album presenters, use better carousel ([31b114cc](https://github.com/pixelfed/pixelfed/commit/31b114cc)) ([0617fada](https://github.com/pixelfed/pixelfed/commit/0617fada)) ([767fc887](https://github.com/pixelfed/pixelfed/commit/767fc887))
- Updated Timeline.vue component, remove tap for lightbox as it conflicts with new carousel ([96e25ad2](https://github.com/pixelfed/pixelfed/commit/96e25ad2))
- Updated ComposeModal.vue, added album support, editing and UI tweaks ([3aaad81e](https://github.com/pixelfed/pixelfed/commit/3aaad81e))
- Updated InternalApiController, increase license limit to 140 to match UI counter ([b3c18aec](https://github.com/pixelfed/pixelfed/commit/b3c18aec))
## Deprecated
## [v0.10.6 (2019-09-30)](https://github.com/pixelfed/pixelfed/compare/v0.10.5...v0.10.6) ## [v0.10.6 (2019-09-30)](https://github.com/pixelfed/pixelfed/compare/v0.10.5...v0.10.6)

View file

@ -42,18 +42,18 @@ trait AdminInstanceController
public function instanceScan(Request $request) public function instanceScan(Request $request)
{ {
DB::transaction(function() { Profile::whereNotNull('domain')
Profile::select('domain')->whereNotNull('domain') ->latest()
->groupBy('id') ->groupBy('domain')
->groupBy('domain') ->where('created_at', '>', now()->subMonths(2))
->chunk(50, function($domains) { ->chunk(100, function($domains) {
foreach($domains as $domain) { foreach($domains as $domain) {
Instance::firstOrCreate([ Instance::firstOrCreate([
'domain' => $domain->domain 'domain' => $domain->domain
]); ]);
} }
});
}); });
return redirect()->back(); return redirect()->back();
} }

View file

@ -20,6 +20,7 @@ use App\Transformer\Api\{
AccountTransformer, AccountTransformer,
NotificationTransformer, NotificationTransformer,
MediaTransformer, MediaTransformer,
MediaDraftTransformer,
StatusTransformer StatusTransformer
}; };
use League\Fractal; use League\Fractal;
@ -192,7 +193,7 @@ class BaseApiController extends Controller
]); ]);
} }
public function showTempMedia(Request $request, int $profileId, $mediaId) public function showTempMedia(Request $request, int $profileId, $mediaId, $timestamp)
{ {
abort_if(!$request->user(), 403); abort_if(!$request->user(), 403);
abort_if(!$request->hasValidSignature(), 404); abort_if(!$request->hasValidSignature(), 404);
@ -257,10 +258,9 @@ class BaseApiController extends Controller
$media->save(); $media->save();
$url = URL::temporarySignedRoute( $url = URL::temporarySignedRoute(
'temp-media', now()->addHours(1), ['profileId' => $profile->id, 'mediaId' => $media->id] 'temp-media', now()->addHours(1), ['profileId' => $profile->id, 'mediaId' => $media->id, 'timestamp' => time()]
); );
$preview_url = $url;
switch ($media->mime) { switch ($media->mime) {
case 'image/jpeg': case 'image/jpeg':
case 'image/png': case 'image/png':
@ -279,7 +279,7 @@ class BaseApiController extends Controller
$resource = new Fractal\Resource\Item($media, new MediaTransformer()); $resource = new Fractal\Resource\Item($media, new MediaTransformer());
$res = $this->fractal->createData($resource)->toArray(); $res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = $preview_url; $res['preview_url'] = $url;
$res['url'] = $url; $res['url'] = $url;
return response()->json($res); return response()->json($res);
} }
@ -308,8 +308,9 @@ class BaseApiController extends Controller
public function verifyCredentials(Request $request) public function verifyCredentials(Request $request)
{ {
abort_if(!$request->user(), 403); $user = $request->user();
$id = Auth::id(); abort_if(!$user, 403);
$id = $user->id;
$res = Cache::remember('user:account:id:'.$id, now()->addHours(6), function() use($id) { $res = Cache::remember('user:account:id:'.$id, now()->addHours(6), function() use($id) {
$profile = Profile::whereNull('status')->whereUserId($id)->firstOrFail(); $profile = Profile::whereNull('status')->whereUserId($id)->firstOrFail();
@ -320,4 +321,19 @@ class BaseApiController extends Controller
return response()->json($res); return response()->json($res);
} }
public function drafts(Request $request)
{
$user = $request->user();
abort_if(!$request->user(), 403);
$medias = Media::whereUserId($user->id)
->whereNull('status_id')
->latest()
->take(13)
->get();
$resource = new Fractal\Resource\Collection($medias, new MediaDraftTransformer());
$res = $this->fractal->createData($resource)->toArray();
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
} }

View file

@ -237,7 +237,8 @@ class InternalApiController extends Controller
'media.*' => 'required', 'media.*' => 'required',
'media.*.id' => 'required|integer|min:1', 'media.*.id' => 'required|integer|min:1',
'media.*.filter_class' => 'nullable|alpha_dash|max:30', 'media.*.filter_class' => 'nullable|alpha_dash|max:30',
'media.*.license' => 'nullable|string|max:80', 'media.*.license' => 'nullable|string|max:140',
'media.*.alt' => 'nullable|string|max:140',
'cw' => 'nullable|boolean', 'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10', 'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable', 'place' => 'nullable',

View file

@ -281,7 +281,7 @@ class PublicApiController extends Controller
'updated_at' 'updated_at'
)->where('id', $dir, $id) )->where('id', $dir, $id)
->with('profile', 'hashtags', 'mentions') ->with('profile', 'hashtags', 'mentions')
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereLocal(true) ->whereLocal(true)
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->whereVisibility('public') ->whereVisibility('public')
@ -309,7 +309,7 @@ class PublicApiController extends Controller
'likes_count', 'likes_count',
'reblogs_count', 'reblogs_count',
'updated_at' 'updated_at'
)->whereIn('type', ['photo', 'photo:album', 'video', '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) ->whereNotIn('profile_id', $filtered)
@ -392,7 +392,7 @@ class PublicApiController extends Controller
'reblogs_count', 'reblogs_count',
'created_at', 'created_at',
'updated_at' 'updated_at'
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->with('profile', 'hashtags', 'mentions') ->with('profile', 'hashtags', 'mentions')
->where('id', $dir, $id) ->where('id', $dir, $id)
->whereIn('profile_id', $following) ->whereIn('profile_id', $following)
@ -421,7 +421,7 @@ class PublicApiController extends Controller
'reblogs_count', 'reblogs_count',
'created_at', 'created_at',
'updated_at' 'updated_at'
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->with('profile', 'hashtags', 'mentions') ->with('profile', 'hashtags', 'mentions')
->whereIn('profile_id', $following) ->whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)

View file

@ -66,6 +66,7 @@ trait PrivacySettings
$settings->save(); $settings->save();
} }
Cache::forget('profile:settings:' . $profile->id); Cache::forget('profile:settings:' . $profile->id);
Cache::forget('user:account:id:' . $profile->user_id);
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!'); return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
} }

View file

@ -61,7 +61,12 @@ class SharePipeline implements ShouldQueue
->whereItemType('App\Status') ->whereItemType('App\Status')
->count(); ->count();
if ($target->id === $status->profile_id || $exists !== 0) { if ($target->id === $status->profile_id) {
$this->remoteAnnounceDeliver();
return true;
}
if( $exists !== 0) {
return true; return true;
} }
@ -88,6 +93,9 @@ class SharePipeline implements ShouldQueue
public function remoteAnnounceDeliver() public function remoteAnnounceDeliver()
{ {
if(config('federation.activitypub.enabled') == false) {
return true;
}
$status = $this->status; $status = $this->status;
$profile = $status->profile; $profile = $status->profile;

View file

@ -0,0 +1,38 @@
<?php
namespace App\Transformer\Api;
use App\Media;
use League\Fractal;
use URL;
class MediaDraftTransformer extends Fractal\TransformerAbstract
{
public function transform(Media $media)
{
$url = URL::temporarySignedRoute(
'temp-media', now()->addHours(1), ['profileId' => $media->profile_id, 'mediaId' => $media->id, 'timestamp' => time()]
);
//$url = $media->thumbnailUrl();
//$url = $media->url();
return [
'id' => (string) $media->id,
'type' => $media->activityVerb(),
'url' => $url,
'remote_url' => null,
'preview_url' => $url,
'text_url' => null,
'meta' => null,
'description' => $media->caption,
'license' => $media->license,
'is_nsfw' => $media->is_nsfw,
'orientation' => $media->orientation,
'filter_name' => $media->filter_name,
'filter_class' => $media->filter_class,
'mime' => $media->mime,
];
}
}

View file

@ -44,7 +44,7 @@ COPY . /var/www/
WORKDIR /var/www/ WORKDIR /var/www/
RUN cp -r storage storage.skel \ RUN cp -r storage storage.skel \
&& cp contrib/docker/php.ini /usr/local/etc/php/conf.d/pixelfed.ini \ && cp contrib/docker/php.ini /usr/local/etc/php/conf.d/pixelfed.ini \
&& composer install --prefer-source --no-interaction \ && composer install --prefer-dist --no-interaction \
&& rm -rf html && ln -s public html && rm -rf html && ln -s public html
VOLUME /var/www/storage /var/www/bootstrap VOLUME /var/www/storage /var/www/bootstrap

51
package-lock.json generated
View file

@ -1,4 +1,5 @@
{ {
"name": "pixelfed",
"requires": true, "requires": true,
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
@ -3106,6 +3107,11 @@
"entities": "^1.1.1" "entities": "^1.1.1"
} }
}, },
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
},
"domain-browser": { "domain-browser": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@ -4621,6 +4627,15 @@
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
"integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs="
}, },
"global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
"requires": {
"min-document": "^2.19.0",
"process": "^0.11.10"
}
},
"global-modules": { "global-modules": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@ -6085,6 +6100,14 @@
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
}, },
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"requires": {
"dom-walk": "^0.1.0"
}
},
"minimalistic-assert": { "minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@ -7787,14 +7810,14 @@
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
}, },
"quill": { "quill": {
"version": "1.3.6", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.6.tgz", "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==", "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"requires": { "requires": {
"clone": "^2.1.1", "clone": "^2.1.1",
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3", "eventemitter3": "^2.0.3",
"extend": "^3.0.1", "extend": "^3.0.2",
"parchment": "^1.1.4", "parchment": "^1.1.4",
"quill-delta": "^3.6.2" "quill-delta": "^3.6.2"
}, },
@ -9856,8 +9879,24 @@
"vue": { "vue": {
"version": "2.6.10", "version": "2.6.10",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
"integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==", "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
"dev": true },
"vue-carousel": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/vue-carousel/-/vue-carousel-0.18.0.tgz",
"integrity": "sha512-a2zxh7QJioDxNMguqcuJ7TPbfgK5bGDaAXIia7NWxPAWsEvNE4ZtHgsGu40L5Aha4uyjmNKXvleB14QAXFoKig==",
"requires": {
"global": "^4.3.2",
"regenerator-runtime": "^0.12.1",
"vue": "^2.5.17"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
}, },
"vue-content-loader": { "vue-content-loader": {
"version": "0.2.2", "version": "0.2.2",

View file

@ -45,6 +45,7 @@
"socket.io-client": "^2.2.0", "socket.io-client": "^2.2.0",
"sweetalert": "^2.1.2", "sweetalert": "^2.1.2",
"twitter-text": "^2.0.5", "twitter-text": "^2.0.5",
"vue-carousel": "^0.18.0",
"vue-content-loader": "^0.2.2", "vue-content-loader": "^0.2.2",
"vue-cropperjs": "^4.0.0", "vue-cropperjs": "^4.0.0",
"vue-infinite-loading": "^2.4.4", "vue-infinite-loading": "^2.4.4",

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/*! /*!
* Quill Editor v1.3.6 * Quill Editor v1.3.7
* https://quilljs.com/ * https://quilljs.com/
* Copyright (c) 2014, Jason Chen * Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com * Copyright (c) 2013, salesforce.com

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/quill.js vendored

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3,25 +3,25 @@
"/js/vendor.js": "/js/vendor.js?id=fac92a458473b287c543", "/js/vendor.js": "/js/vendor.js?id=fac92a458473b287c543",
"/js/ace.js": "/js/ace.js?id=585114d8896dc0c24020", "/js/ace.js": "/js/ace.js?id=585114d8896dc0c24020",
"/js/activity.js": "/js/activity.js?id=713d9542e71e87fb88c0", "/js/activity.js": "/js/activity.js?id=713d9542e71e87fb88c0",
"/js/app.js": "/js/app.js?id=e44ab72ffa230a7a56c1", "/js/app.js": "/js/app.js?id=16017d5f3472ac2d6ee3",
"/css/app.css": "/css/app.css?id=67710dadf879498b096a", "/css/app.css": "/css/app.css?id=56379e7c98ba2945e829",
"/css/appdark.css": "/css/appdark.css?id=6c52e68ca396700f0836", "/css/appdark.css": "/css/appdark.css?id=83064c1642bdd3457156",
"/css/landing.css": "/css/landing.css?id=6164f7c333f0ab25b7f3", "/css/landing.css": "/css/landing.css?id=221a8ef5424fab0875d9",
"/css/quill.css": "/css/quill.css?id=81604d62610b0dbffad6", "/css/quill.css": "/css/quill.css?id=711b2150d518816d6112",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=e79d31b6c4b4a16a03f9", "/js/collectioncompose.js": "/js/collectioncompose.js?id=b27e524d161917a9e9e1",
"/js/collections.js": "/js/collections.js?id=5e47d9f51d8eaddc4d24", "/js/collections.js": "/js/collections.js?id=5e47d9f51d8eaddc4d24",
"/js/components.js": "/js/components.js?id=b981ec12e26469676c4e", "/js/components.js": "/js/components.js?id=be8c9e1c6c52db778f29",
"/js/compose.js": "/js/compose.js?id=fc646e27d5b83448ad7b", "/js/compose.js": "/js/compose.js?id=2d3e96bd3197d49cfe88",
"/js/compose-classic.js": "/js/compose-classic.js?id=e7483681a575c190a43b", "/js/compose-classic.js": "/js/compose-classic.js?id=e7483681a575c190a43b",
"/js/developers.js": "/js/developers.js?id=9636d4060ca6b359d8a2", "/js/developers.js": "/js/developers.js?id=9636d4060ca6b359d8a2",
"/js/discover.js": "/js/discover.js?id=fbc49123fc2ce2ff7acf", "/js/discover.js": "/js/discover.js?id=fbc49123fc2ce2ff7acf",
"/js/hashtag.js": "/js/hashtag.js?id=1e5990f89b6bfe7e037b", "/js/hashtag.js": "/js/hashtag.js?id=1e5990f89b6bfe7e037b",
"/js/loops.js": "/js/loops.js?id=9c31302552d789d5f35b", "/js/loops.js": "/js/loops.js?id=9c31302552d789d5f35b",
"/js/mode-dot.js": "/js/mode-dot.js?id=993d7fee684361edddbc", "/js/mode-dot.js": "/js/mode-dot.js?id=993d7fee684361edddbc",
"/js/profile.js": "/js/profile.js?id=38978bde6d5f41308b57", "/js/profile.js": "/js/profile.js?id=ceaba398bcab1d77dd98",
"/js/quill.js": "/js/quill.js?id=1ab4119a62cc484034d9", "/js/quill.js": "/js/quill.js?id=37962cd45a252d2f13c9",
"/js/search.js": "/js/search.js?id=f312959df65e86a307a3", "/js/search.js": "/js/search.js?id=f312959df65e86a307a3",
"/js/status.js": "/js/status.js?id=27f13302db3fec70f5de", "/js/status.js": "/js/status.js?id=bee233d32d3984c05267",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=700e5dc735365e184e41", "/js/theme-monokai.js": "/js/theme-monokai.js?id=700e5dc735365e184e41",
"/js/timeline.js": "/js/timeline.js?id=5c63858925484f87064f" "/js/timeline.js": "/js/timeline.js?id=f3aca9b1b3a8986a81db"
} }

View file

@ -19,3 +19,64 @@ window.App = window.App || {};
window.App.boot = function() { window.App.boot = function() {
new Vue({ el: '#content'}); new Vue({ el: '#content'});
} }
window.App.util = {
time: (function() {
return new Date;
}),
version: (function() {
return 1;
}),
format: {
count: (function(count = 0) {
if(count < 1) {
return 0;
}
return new Intl.NumberFormat('en-GB', { notation: "compact" , compactDisplay: "short" }).format(count);
})
},
filters: [
['1977','filter-1977'],
['Aden','filter-aden'],
['Amaro','filter-amaro'],
['Ashby','filter-ashby'],
['Brannan','filter-brannan'],
['Brooklyn','filter-brooklyn'],
['Charmes','filter-charmes'],
['Clarendon','filter-clarendon'],
['Crema','filter-crema'],
['Dogpatch','filter-dogpatch'],
['Earlybird','filter-earlybird'],
['Gingham','filter-gingham'],
['Ginza','filter-ginza'],
['Hefe','filter-hefe'],
['Helena','filter-helena'],
['Hudson','filter-hudson'],
['Inkwell','filter-inkwell'],
['Kelvin','filter-kelvin'],
['Kuno','filter-juno'],
['Lark','filter-lark'],
['Lo-Fi','filter-lofi'],
['Ludwig','filter-ludwig'],
['Maven','filter-maven'],
['Mayfair','filter-mayfair'],
['Moon','filter-moon'],
['Nashville','filter-nashville'],
['Perpetua','filter-perpetua'],
['Poprocket','filter-poprocket'],
['Reyes','filter-reyes'],
['Rise','filter-rise'],
['Sierra','filter-sierra'],
['Skyline','filter-skyline'],
['Slumber','filter-slumber'],
['Stinson','filter-stinson'],
['Sutro','filter-sutro'],
['Toaster','filter-toaster'],
['Valencia','filter-valencia'],
['Vesper','filter-vesper'],
['Walden','filter-walden'],
['Willow','filter-willow'],
['X-Pro II','filter-xpro-ii']
],
emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'],
};

View file

@ -3,7 +3,9 @@ import BootstrapVue from 'bootstrap-vue'
import InfiniteLoading from 'vue-infinite-loading'; import InfiniteLoading from 'vue-infinite-loading';
import Loading from 'vue-loading-overlay'; import Loading from 'vue-loading-overlay';
import VueTimeago from 'vue-timeago'; import VueTimeago from 'vue-timeago';
import VueCarousel from 'vue-carousel';
Vue.use(VueCarousel);
Vue.use(BootstrapVue); Vue.use(BootstrapVue);
Vue.use(InfiniteLoading); Vue.use(InfiniteLoading);
Vue.use(Loading); Vue.use(Loading);
@ -37,11 +39,7 @@ try {
window.filesize = require('filesize'); window.filesize = require('filesize');
import swal from 'sweetalert'; import swal from 'sweetalert';
$(document).ready(function() { $('[data-toggle="tooltip"]').tooltip()
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
});
const warningTitleCSS = 'color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;'; const warningTitleCSS = 'color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;';
const warningDescCSS = 'font-size: 18px;'; const warningDescCSS = 'font-size: 18px;';

View file

@ -201,7 +201,7 @@ export default {
}, },
fetchRecentPosts() { fetchRecentPosts() {
axios.get('/api/local/accounts/' + this.profileId + '/statuses', { axios.get('/api/pixelfed/v1/accounts/' + this.profileId + '/statuses', {
params: { params: {
only_media: true, only_media: true,
min_id: 1, min_id: 1,

View file

@ -1,15 +1,45 @@
<template> <template>
<div> <div>
<input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" draggable="true" v-bind:accept="config.uploader.media_types"> <input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" draggable="true" v-bind:accept="config.uploader.media_types" multiple="">
<div class="timeline"> <div class="timeline">
<div v-if="uploading"> <div v-if="uploading">
<div class="card status-card card-md-rounded-0 w-100 h-100 bg-light py-5" style="border-bottom: 1px solid #f1f1f1"> <div class="card status-card card-md-rounded-0 w-100 h-100 bg-light py-3" style="border-bottom: 1px solid #f1f1f1">
<div class="p-5 mt-2"> <div class="p-5 mt-2">
<b-progress :value="uploadProgress" :max="100" striped :animated="true"></b-progress> <b-progress :value="uploadProgress" :max="100" striped :animated="true"></b-progress>
<p class="text-center mb-0 font-weight-bold">Uploading ... ({{uploadProgress}}%)</p> <p class="text-center mb-0 font-weight-bold">Uploading ... ({{uploadProgress}}%)</p>
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="page == 'cameraRoll'">
<div class="card status-card card-md-rounded-0" style="display:flex;">
<div class="card-header d-inline-flex align-items-center justify-content-between bg-white">
<span class="pr-3">
<i class="fas fa-cog fa-lg text-muted"></i>
</span>
<span class="font-weight-bold">
Camera Roll
</span>
<span class="text-primary font-weight-bold">Upload</span>
</div>
<div class="h-100 card-body p-0 border-top" style="width:100%; min-height: 400px;">
<div v-if="cameraRollMedia.length > 0" class="row p-0 m-0">
<div v-for="(m, index) in cameraRollMedia" :class="[index == 0 ? 'col-12 p-0' : 'col-3 p-0']">
<div class="card info-overlay p-0 rounded-0 shadow-none border">
<div class="square">
<img class="square-content" :src="m.preview_url"></img>
</div>
</div>
</div>
</div>
<div v-else class="w-100 h-100 d-flex justify-content-center align-items-center">
<span class="w-100 h-100">
<button type="button" class="btn btn-primary">Upload</button>
<button type="button" class="btn btn-primary" @click="fetchCameraRollDrafts()">Load Camera Roll</button>
</span>
</div>
</div>
</div>
</div>
<div v-else> <div v-else>
<div class="card status-card card-md-rounded-0 w-100 h-100" style="display:flex;"> <div class="card status-card card-md-rounded-0 w-100 h-100" style="display:flex;">
<div class="card-header d-inline-flex align-items-center justify-content-between bg-white"> <div class="card-header d-inline-flex align-items-center justify-content-between bg-white">
@ -18,15 +48,25 @@
<i class="fas fa-times fa-lg"></i> <i class="fas fa-times fa-lg"></i>
<span class="font-weight-bold mb-0">{{pageTitle}}</span> <span class="font-weight-bold mb-0">{{pageTitle}}</span>
</a> </a>
<span v-else> <span v-else-if="page == 2">
<span> <button v-if="config.uploader.album_limit > media.length" class="btn btn-outline-primary btn-sm font-weight-bold" @click.prevent="addMedia" data-toggle="tooltip" data-placement="bottom" title="Upload another photo or video" ><i class="fas fa-plus"></i></button>
<a class="text-lighter text-decoration-none mr-3" href="#" @click.prevent="goBack()"><i class="fas fa-long-arrow-alt-left fa-lg"></i></a> <!-- <button v-if="config.uploader.album_limit > media.length" class="btn btn-outline-primary btn-sm font-weight-bold" @click.prevent="page = 'cameraRoll'" data-toggle="tooltip" data-placement="bottom" title="Upload another photo or video" ><i class="fas fa-chevron-left"></i> Camera Roll</button> -->
</span> <button v-else class="btn btn-outline-secondary btn-sm font-weight-bold" disabled><i class="fas fa-plus"></i></button>
</span>
<span v-else-if="page == 3">
<a class="text-lighter text-decoration-none mr-3 d-flex align-items-center" href="#" @click.prevent="goBack()">
<i class="fas fa-long-arrow-alt-left fa-lg mr-2"></i>
<span class="btn btn-outline-secondary btn-sm px-2 py-0 disabled" disabled="">{{media.length}}</span>
</a>
<span class="font-weight-bold mb-0">{{pageTitle}}</span> <span class="font-weight-bold mb-0">{{pageTitle}}</span>
</span> </span>
<span v-else>
<a class="text-lighter text-decoration-none mr-3" href="#" @click.prevent="goBack()"><i class="fas fa-long-arrow-alt-left fa-lg"></i></a>
</span>
<span class="font-weight-bold mb-0">{{pageTitle}}</span>
</div> </div>
<div v-if="page == 2"> <div v-if="page == 2">
<a href="#" class="text-center text-dark" @click.prevent="showCropPhotoCard"><i class="fas fa-magic fa-lg"></i></a> <a v-if="media.length == 1" href="#" class="text-center text-dark" @click.prevent="showCropPhotoCard"><i class="fas fa-magic fa-lg"></i></a>
</div> </div>
<div> <div>
<!-- <a v-if="page > 1" class="font-weight-bold text-decoration-none" href="#" @click.prevent="page--">Back</a> --> <!-- <a v-if="page > 1" class="font-weight-bold text-decoration-none" href="#" @click.prevent="page--">Back</a> -->
@ -37,31 +77,65 @@
</span> </span>
<span v-else> <span v-else>
<a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a> <a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a>
<a v-if="!pageLoading && page == 3" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose">Post</a> <a v-if="!pageLoading && page == 3" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
</span> </span>
</div> </div>
</div> </div>
<div class="card-body p-0 border-top"> <div class="card-body p-0 border-top">
<div v-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;"> <div v-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;">
<div class="text-center"> <div class="text-center">
<p> <a class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/compose">
<a class="btn btn-primary font-weight-bold" href="/i/compose">Compose Post</a> <div class="card-body">
</p> <div class="media">
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
<i class="far fa-image text-white fa-lg"></i>
</div>
<div class="media-body text-left">
<h5 class="mt-0 font-weight-bold text-primary">New Post</h5>
<p class="mb-0 text-muted">Share up to {{config.uploader.album_limit}} photos or videos.</p>
</div>
</div>
</div>
</a>
<a class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/collections/create">
<div class="card-body">
<div class="media">
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
<i class="fas fa-images text-white fa-lg"></i>
</div>
<div class="media-body text-left">
<p class="mb-0">
<span class="h5 mt-0 font-weight-bold text-primary">New Collection</span>
</p>
<p class="mb-0 text-muted">Create a curated collection of photos.</p>
</div>
</div>
</div>
</a>
<div v-if="media.length == 0" class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark">
<div @click.prevent="addMedia" class="card-body">
<div class="media">
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;background-color: #008DF5">
<i class="fas fa-bolt text-white fa-lg"></i>
</div>
<div class="media-body text-left">
<p class="mb-0">
<span class="h5 mt-0 font-weight-bold text-primary">Try ComposeUI v4</span>
<sup>
<span class="badge badge-primary pb-1">BETA</span>
</sup>
</p>
<p class="mb-0 text-muted">The next generation compose experience.</p>
</div>
</div>
</div>
</div>
<hr> <hr>
<p v-if="media.length == 0">
<button type="button" class="btn btn-outline-primary font-weight-bold" @click.prevent="addMedia">Compose Post <sup>BETA</sup></button>
</p>
<p>
<button class="btn btn-outline-primary font-weight-bold" @click.prevent="createCollection">New Collection</button>
</p>
<!-- <p>
<button class="btn btn-outline-primary font-weight-bold" @click.prevent="showAddToStoryCard()">Add To My Story</button>
</p> -->
<p> <p>
<a class="font-weight-bold" href="/site/help">Need Help?</a> <a class="font-weight-bold" href="/site/help">Need Help?</a>
</p> </p>
<p class="text-muted mb-0 small text-center">Formats: <b>{{acceptedFormats()}}</b> up to <b>{{maxSize()}}</b></p>
<p class="text-muted mb-0 small text-center">Albums can contain up to <b>{{config.uploader.album_limit}}</b> photos or videos</p>
</div> </div>
</div> </div>
@ -74,32 +148,74 @@
:viewMode="cropper.viewMode" :viewMode="cropper.viewMode"
:zoomable="cropper.zoomable" :zoomable="cropper.zoomable"
:rotatable="true" :rotatable="true"
:src="media[0].url" :src="media[carouselCursor].url"
> >
</vue-cropper> </vue-cropper>
</div> </div>
</div> </div>
<div v-if="page == 2" class="w-100 h-100"> <div v-if="page == 2" class="w-100 h-100">
<div slot="img" style="display:flex;min-height: 420px;align-items: center;"> <div v-if="media.length == 1">
<img :class="'d-block img-fluid w-100 ' + [media[carouselCursor].filter_class?media[carouselCursor].filter_class:'']" :src="media[carouselCursor].url" :alt="media[carouselCursor].description" :title="media[carouselCursor].description"> <div slot="img" style="display:flex;min-height: 420px;align-items: center;">
<img :class="'d-block img-fluid w-100 ' + [media[carouselCursor].filter_class?media[carouselCursor].filter_class:'']" :src="media[carouselCursor].url" :alt="media[carouselCursor].description" :title="media[carouselCursor].description">
</div>
<hr>
<div v-if="ids.length > 0 && media[carouselCursor].type == 'Image'" class="align-items-center px-2 pt-2">
<ul class="nav media-drawer-filters text-center">
<li class="nav-item">
<div class="p-1 pt-3">
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
</div>
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
</li>
<li class="nav-item" v-for="(filter, index) in filters">
<div class="p-1 pt-3">
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
</div>
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
</li>
</ul>
</div>
</div> </div>
<hr> <div v-else-if="media.length > 1" class="d-flex-inline px-2 pt-2">
<div v-if="ids.length > 0 && media[carouselCursor].type == 'Image'" class="align-items-center px-2 pt-2">
<ul class="nav media-drawer-filters text-center"> <ul class="nav media-drawer-filters text-center">
<li class="nav-item"> <li class="nav-item mx-md-4">&nbsp;</li>
<div class="p-1 pt-3"> <li v-for="(m, i) in media" class="nav-item mx-md-4">
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer"> <div class="nav-link" style="display:block;width:300px;height:300px;" @click="carouselCursor = i">
</div> <!-- <img :class="'d-block img-fluid w-100 ' + [m.filter_class?m.filter_class:'']" :src="m.url" :alt="m.description" :title="m.description"> -->
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a> <span :class="[m.filter_class?m.filter_class:'']">
</li>
<li class="nav-item" v-for="(filter, index) in filters"> <span :class="'rounded border ' + [i == carouselCursor ? ' border-primary shadow':'']" :style="'display:block;padding:5px;width:100%;height:100%;background-image: url(' + m.url + ');background-size:cover;border-width:3px !important;'"></span>
<div class="p-1 pt-3"> </span>
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])"> </div>
</div> <div v-if="i == carouselCursor" class="text-center mb-0 small text-lighter font-weight-bold pt-2">
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a> <span class="cursor-pointer" @click.prevent="showCropPhotoCard">Crop</span>
<span class="cursor-pointer px-3" @click.prevent="showEditMediaCard()">Edit</span>
<span class="cursor-pointer" @click="deleteMedia()">Delete</span>
</div>
</li> </li>
<li class="nav-item mx-md-4">&nbsp;</li>
</ul> </ul>
<hr>
<div v-if="ids.length > 0 && media[carouselCursor].type == 'Image'" class="align-items-center px-2 pt-2">
<ul class="nav media-drawer-filters text-center">
<li class="nav-item">
<div class="p-1 pt-3">
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
</div>
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
</li>
<li class="nav-item" v-for="(filter, index) in filters">
<div class="p-1 pt-3">
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
</div>
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
</li>
</ul>
</div>
</div>
<div v-else>
<p class="mb-0 p-5 text-center font-weight-bold">An error occured, please refresh the page.</p>
</div> </div>
</div> </div>
@ -116,16 +232,27 @@
</div> </div>
</div> </div>
</div> </div>
<div class="border-bottom"> <div class="border-bottom d-flex justify-content-between px-4 mb-0 py-2 ">
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showTagCard()">Tag people</p> <div>
<div class="text-dark ">Contains NSFW Media</div>
</div>
<div>
<div class="custom-control custom-switch" style="z-index: 9999;">
<input type="checkbox" class="custom-control-input" id="asnsfw" v-model="nsfw">
<label class="custom-control-label" for="asnsfw"></label>
</div>
</div>
</div> </div>
<!-- <div class="border-bottom">
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showTagCard()">Tag people</p>
</div> -->
<div class="border-bottom"> <div class="border-bottom">
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showLocationCard()" v-if="!place">Add location</p> <p class="px-4 mb-0 py-2 cursor-pointer" @click="showLocationCard()" v-if="!place">Add location</p>
<p v-else class="px-4 mb-0 py-2"> <p v-else class="px-4 mb-0 py-2">
<span class="text-lighter">Location:</span> {{place.name}}, {{place.country}} <span class="text-lighter">Location:</span> {{place.name}}, {{place.country}}
<span class="float-right"> <span class="float-right">
<a href="#" @click.prevent="showLocationCard()" class="text-muted font-weight-bold small mr-2">Change</a> <a href="#" @click.prevent="showLocationCard()" class="btn btn-outline-secondary btn-sm small mr-2" style="font-size:10px;padding:3px;text-transform: uppercase">Edit</a>
<a href="#" @click.prevent="place = false" class="text-muted font-weight-bold small">Remove</a> <a href="#" @click.prevent="place = false" class="btn btn-outline-secondary btn-sm small" style="font-size:10px;padding:3px;text-transform: uppercase">Remove</a>
</span> </span>
</p> </p>
</div> </div>
@ -133,10 +260,22 @@
<p class="px-4 mb-0 py-2"> <p class="px-4 mb-0 py-2">
<span class="text-lighter">Visibility:</span> {{visibilityTag}} <span class="text-lighter">Visibility:</span> {{visibilityTag}}
<span class="float-right"> <span class="float-right">
<a href="#" @click.prevent="showVisibilityCard()" class="text-muted font-weight-bold small mr-2">Change</a> <a v-if="profile.locked == false" href="#" @click.prevent="showVisibilityCard()" class="btn btn-outline-secondary btn-sm small mr-2" style="font-size:10px;padding:3px;text-transform: uppercase">Edit</a>
</span> </span>
</p> </p>
</div> </div>
<!-- <div class="cursor-pointer border-bottom px-4 mb-0 py-2" @click.prevent="showMediaDescriptionsCard()">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="text-dark">Media Descriptions</div>
<p class="text-muted small mb-0">Describe your photos for people with visual impairments.</p>
</div>
<div>
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
</div>
</div>
</div> -->
<div style="min-height: 200px;"> <div style="min-height: 200px;">
<p class="px-4 mb-0 py-2 small font-weight-bold text-muted cursor-pointer" @click="showAdvancedSettingsCard()">Advanced settings</p> <p class="px-4 mb-0 py-2 small font-weight-bold text-muted cursor-pointer" @click="showAdvancedSettingsCard()">Advanced settings</p>
</div> </div>
@ -172,33 +311,50 @@
</div> </div>
</div> </div>
</div> </div>
<div class="list-group-item d-flex justify-content-between"> <a href="#" class="list-group-item" @click.prevent="showMediaDescriptionsCard()">
<div> <div class="d-flex justify-content-between align-items-center">
<div class="text-dark ">Contains NSFW Media</div> <div>
</div> <div class="text-dark">Media Descriptions</div>
<div> <p class="text-muted small mb-0">Describe your photos for people with visual impairments.</p>
<div class="custom-control custom-switch" style="z-index: 9999;"> </div>
<input type="checkbox" class="custom-control-input" id="asnsfw" v-model="nsfw"> <div>
<label class="custom-control-label" for="asnsfw"></label> <i class="fas fa-chevron-right fa-lg text-lighter"></i>
</div> </div>
</div> </div>
</div>
<a href="#" class="list-group-item" @click.prevent="page = 'altText'">
<div class="text-dark">Write alt text</div>
<p class="text-muted small mb-0">Alt text describes your photos for people with visual impairments.</p>
</a> </a>
<a href="#" class="list-group-item" @click.prevent="page = 'addToCollection'"> <!-- <a href="#" class="list-group-item" @click.prevent="showAddToCollectionsCard()">
<div class="text-dark">Add to Collection</div> <div class="d-flex justify-content-between align-items-center">
<p class="text-muted small mb-0">Add this post to a collection.</p> <div>
<div class="text-dark">Add to Collection</div>
<p class="text-muted small mb-0">Add this post to a collection.</p>
</div>
<div>
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
</div>
</div>
</a> </a>
<a href="#" class="list-group-item" @click.prevent="page = 'schedulePost'"> <a href="#" class="list-group-item" @click.prevent="page = 'schedulePost'">
<div class="text-dark">Schedule</div> <div class="d-flex justify-content-between align-items-center">
<p class="text-muted small mb-0">Schedule post for a future date.</p> <div>
<div class="text-dark">Schedule</div>
<p class="text-muted small mb-0">Schedule post for a future date.</p>
</div>
<div>
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
</div>
</div>
</a> </a>
<a href="#" class="list-group-item" @click.prevent="page = 'mediaMetadata'"> <a href="#" class="list-group-item" @click.prevent="page = 'mediaMetadata'">
<div class="text-dark">Metadata</div> <div class="d-flex justify-content-between align-items-center">
<p class="text-muted small mb-0">Manage media exif and metadata.</p> <div>
</a> <div class="text-dark">Metadata</div>
<p class="text-muted small mb-0">Manage media exif and metadata.</p>
</div>
<div>
<i class="fas fa-chevron-right fa-lg text-lighter"></i>
</div>
</div>
</a> -->
</div> </div>
</div> </div>
@ -211,11 +367,38 @@
</div> </div>
<div v-if="page == 'altText'" class="w-100 h-100 p-3"> <div v-if="page == 'altText'" class="w-100 h-100 p-3">
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p> <div v-for="(m, index) in media">
<div class="media">
<img :src="m.preview_url" class="mr-3" width="50px" height="50px">
<div class="media-body">
<textarea class="form-control" v-model="m.alt" placeholder="Add a media description here..."></textarea>
<p class="help-text small text-right text-muted mb-0">{{m.alt ? m.alt.length : 0}}/140</p>
</div>
</div>
<hr>
</div>
<p class="d-flex justify-content-between mb-0">
<button type="button" @click="goBack()" class="btn btn-link text-muted font-weight-bold text-decoration-none">Cancel</button>
<button type="button" @click="goBack()" class="btn btn-primary font-weight-bold">Save</button>
</p>
</div> </div>
<div v-if="page == 'addToCollection'" class="w-100 h-100 p-3"> <div v-if="page == 'addToCollection'" class="w-100 h-100 p-3">
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p> <div class="list-group mb-3">
<div class="list-group-item cursor-pointer compose-action border" @click="goBack()">
<div class="media">
<img src="" class="mr-3" alt="" width="50px" height="50px">
<div class="media-body">
<h5 class="mt-0">collection title</h5>
<p class="mb-0 text-muted small">3 Photos - Created 2h ago</p>
</div>
</div>
</div>
</div>
<p class="d-flex justify-content-between mb-0">
<button type="button" @click="goBack()" class="btn btn-link text-muted font-weight-bold text-decoration-none">Cancel</button>
<button type="button" @click="goBack()" class="btn btn-primary font-weight-bold">Save</button>
</p>
</div> </div>
<div v-if="page == 'schedulePost'" class="w-100 h-100 p-3"> <div v-if="page == 'schedulePost'" class="w-100 h-100 p-3">
@ -230,6 +413,35 @@
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p> <p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
</div> </div>
<div v-if="page == 'editMedia'" class="w-100 h-100 p-3">
<div class="media">
<img :src="media[carouselCursor].preview_url" class="mr-3" width="50px" height="50px">
<div class="media-body">
<div class="form-group">
<label class="font-weight-bold text-muted small">Media Description</label>
<textarea class="form-control" v-model="media[carouselCursor].alt" placeholder="Add a media description here..."></textarea>
<p class="help-text small text-muted mb-0 d-flex justify-content-between">
<span>Describe your photo for people with visual impairments.</span>
<span>{{media[carouselCursor].alt ? media[carouselCursor].alt.length : 0}}/140</span>
</p>
</div>
<div class="form-group">
<label class="font-weight-bold text-muted small">License</label>
<input type="text" class="form-control" v-model="media[carouselCursor].license" placeholder="All Rights Reserved (Default license)">
<p class="help-text small text-muted mb-0 d-flex justify-content-between">
<span></span>
<span>{{media[carouselCursor].license ? media[carouselCursor].license.length : 0}}/140</span>
</p>
</div>
</div>
</div>
<hr>
<p class="d-flex justify-content-between mb-0">
<button type="button" @click="goBack()" class="btn btn-link text-muted font-weight-bold text-decoration-none">Cancel</button>
<button type="button" @click="goBack()" class="btn btn-primary font-weight-bold">Save</button>
</p>
</div>
</div> </div>
<!-- card-footers --> <!-- card-footers -->
@ -258,6 +470,10 @@
overflow-x: scroll; overflow-x: scroll;
flex-wrap:unset; flex-wrap:unset;
} }
.media-drawer-filters::-webkit-scrollbar {
width: 0px;
background: transparent;
}
.media-drawer-filters .nav-link { .media-drawer-filters .nav-link {
min-width:100px; min-width:100px;
padding-top: 1rem; padding-top: 1rem;
@ -284,6 +500,10 @@
text-decoration: none; text-decoration: none;
background-color: #f8f9fa !important; background-color: #f8f9fa !important;
} }
.compose-action:hover {
cursor: pointer;
background-color: #f8f9fa !important;
}
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
@ -340,8 +560,11 @@ export default {
'addToCollection', 'addToCollection',
'schedulePost', 'schedulePost',
'mediaMetadata', 'mediaMetadata',
'addToStory' 'addToStory',
] 'editMedia',
'cameraRoll'
],
cameraRollMedia: []
} }
}, },
@ -350,53 +573,17 @@ export default {
if(this.config.uploader.media_types.includes('video/mp4') == false) { if(this.config.uploader.media_types.includes('video/mp4') == false) {
this.composeType = 'post' this.composeType = 'post'
} }
this.filters = window.App.util.filters;
}, },
mounted() { mounted() {
this.mediaWatcher(); this.mediaWatcher();
this.filters = [ },
['1977','filter-1977'],
['Aden','filter-aden'], updated() {
['Amaro','filter-amaro'], if(this.page == 2) {
['Ashby','filter-ashby'], $('[data-toggle="tooltip"]').tooltip();
['Brannan','filter-brannan'], }
['Brooklyn','filter-brooklyn'],
['Charmes','filter-charmes'],
['Clarendon','filter-clarendon'],
['Crema','filter-crema'],
['Dogpatch','filter-dogpatch'],
['Earlybird','filter-earlybird'],
['Gingham','filter-gingham'],
['Ginza','filter-ginza'],
['Hefe','filter-hefe'],
['Helena','filter-helena'],
['Hudson','filter-hudson'],
['Inkwell','filter-inkwell'],
['Kelvin','filter-kelvin'],
['Kuno','filter-juno'],
['Lark','filter-lark'],
['Lo-Fi','filter-lofi'],
['Ludwig','filter-ludwig'],
['Maven','filter-maven'],
['Mayfair','filter-mayfair'],
['Moon','filter-moon'],
['Nashville','filter-nashville'],
['Perpetua','filter-perpetua'],
['Poprocket','filter-poprocket'],
['Reyes','filter-reyes'],
['Rise','filter-rise'],
['Sierra','filter-sierra'],
['Skyline','filter-skyline'],
['Slumber','filter-slumber'],
['Stinson','filter-stinson'],
['Sutro','filter-sutro'],
['Toaster','filter-toaster'],
['Valencia','filter-valencia'],
['Vesper','filter-vesper'],
['Walden','filter-walden'],
['Willow','filter-willow'],
['X-Pro II','filter-xpro-ii']
];
}, },
methods: { methods: {
@ -406,6 +593,7 @@ export default {
window.pixelfed.currentUser = res.data; window.pixelfed.currentUser = res.data;
if(res.data.locked == true) { if(res.data.locked == true) {
this.visibility = 'private'; this.visibility = 'private';
this.visibilityTag = 'Followers Only';
} }
}).catch(err => { }).catch(err => {
}); });
@ -422,7 +610,6 @@ export default {
mediaWatcher() { mediaWatcher() {
let self = this; let self = this;
self.mediaDragAndDrop();
$(document).on('change', '#pf-dz', function(e) { $(document).on('change', '#pf-dz', function(e) {
self.mediaUpload(); self.mediaUpload();
}); });
@ -435,6 +622,8 @@ export default {
Array.prototype.forEach.call(io.files, function(io, i) { Array.prototype.forEach.call(io.files, function(io, i) {
if(self.media && self.media.length + i >= self.config.uploader.album_limit) { if(self.media && self.media.length + i >= self.config.uploader.album_limit) {
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error'); swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
self.uploading = false;
self.page = 2;
return; return;
} }
let type = io.type; let type = io.type;
@ -442,6 +631,8 @@ export default {
let validated = $.inArray(type, acceptedMimes); let validated = $.inArray(type, acceptedMimes);
if(validated == -1) { if(validated == -1) {
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error'); swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error');
self.uploading = false;
self.page = 2;
return; return;
} }
@ -460,113 +651,50 @@ export default {
self.uploadProgress = 100; self.uploadProgress = 100;
self.ids.push(e.data.id); self.ids.push(e.data.id);
self.media.push(e.data); self.media.push(e.data);
self.page = 2; self.uploading = false;
setTimeout(function() { setTimeout(function() {
self.uploading = false; self.page = 2;
}, 1000); }, 300);
}).catch(function(e) { }).catch(function(e) {
self.uploading = false; self.uploading = false;
io.value = null; io.value = null;
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error'); swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
self.page = 2;
}); });
io.value = null; io.value = null;
self.uploadProgress = 0; self.uploadProgress = 0;
}); });
}, },
mediaDragAndDrop() {
let self = this;
let pdz = document.getElementById('content');
function allowDrag(e) {
e.dataTransfer.dropEffect = 'copy';
e.preventDefault();
}
function handleDrop(e) {
e.preventDefault();
// let dz = document.querySelector('#pf-dz');
// dz.files = e.dataTransfer.files;
// $('#composeModal').modal('show');
// self.mediaUpload();
}
window.addEventListener('dragenter', function(e) {
});
pdz.addEventListener('dragenter', allowDrag);
pdz.addEventListener('dragover', allowDrag);
pdz.addEventListener('dragleave', function(e) {
//
});
pdz.addEventListener('drop', handleDrop);
},
toggleFilter(e, filter) { toggleFilter(e, filter) {
this.media[this.carouselCursor].filter_class = filter; this.media[this.carouselCursor].filter_class = filter;
}, },
updateMedia() {
this.mediaDrawer = false;
},
deleteMedia() { deleteMedia() {
if(window.confirm('Are you sure you want to delete this media?') == false) { if(window.confirm('Are you sure you want to delete this media?') == false) {
return; return;
} }
let id = this.media[this.carouselCursor].id; let id = this.media[this.carouselCursor].id;
axios.delete('/api/pixelfed/v1/media', { axios.delete('/api/pixelfed/v1/media', {
params: { params: {
id: id id: id
} }
}).then(res => { }).then(res => {
if(this.media.length == 1) { this.ids.splice(this.carouselCursor, 1);
this.mediaDrawer = false; this.media.splice(this.carouselCursor, 1);
if(this.media.length == 0) {
this.ids = []; this.ids = [];
this.media = []; this.media = [];
this.carouselCursor = 0; this.carouselCursor = 0;
} else {
this.carouselCursor = 0;
} }
this.ids.splice(this.carouselCursor, 1);
this.media.splice(this.carouselCursor, 1);
}).catch(err => { }).catch(err => {
swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error'); swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error');
}); });
}, },
mediaAltText() {
return;
// deprecate
swal({
text: 'Add a media description',
content: "input"
}).then(val => {
let media = this.media[this.carouselCursor];
media.alt = val;
});
},
mediaLicense() {
return;
// deprecate
swal({
text: 'Add a media license',
content: "input",
button: {
text: "Update",
closeModal: true,
},
}).then(val => {
let media = this.media[this.carouselCursor];
media.license = val;
});
},
compose() { compose() {
let state = this.composeState; let state = this.composeState;
@ -607,7 +735,6 @@ export default {
break; break;
case 'delete' : case 'delete' :
this.mediaDrawer = false;
this.ids = []; this.ids = [];
this.media = []; this.media = [];
this.carouselCursor = 0; this.carouselCursor = 0;
@ -619,35 +746,28 @@ export default {
} }
}, },
about() {
let text = document.createElement('div');
text.innerHTML = `
<p class="small font-weight-bold">Please visit the <a href="/site/kb/sharing-media">Sharing Media</a> page for more info.</p>
`;
swal({
title: 'Compose UI v3',
content: text,
icon: 'info'
});
},
closeModal() { closeModal() {
this.composeType = ''; this.composeType = '';
$('#composeModal').modal('hide'); $('#composeModal').modal('hide');
}, },
composeMessage() { goBack() {
let config = this.config; this.pageTitle = '';
let composeType = this.composeType;
let video = config.uploader.media_types.includes('video/mp4');
return video ? switch(this.page) {
'Click here to add photos or videos' : case 'addToStory':
'Click here to add photos'; this.page = 1;
}, break;
createCollection() { case 'cropPhoto':
window.location.href = '/i/collections/create'; case 'editMedia':
this.page = 2;
break;
default:
this.namedPages.indexOf(this.page) != -1 ? this.page = 3 : this.page--;
break;
}
}, },
nextPage() { nextPage() {
@ -696,18 +816,6 @@ export default {
this.$refs.cropper.setAspectRatio(ratio); this.$refs.cropper.setAspectRatio(ratio);
}, },
maxSize() {
let limit = this.config.uploader.max_photo_size;
return limit / 1000 + ' MB';
},
acceptedFormats() {
let formats = this.config.uploader.media_types;
return formats.split(',').map(f => {
return ' ' + f.split('/')[1];
}).toString();
},
showTagCard() { showTagCard() {
this.pageTitle = 'Tag People'; this.pageTitle = 'Tag People';
this.page = 'tagPeople'; this.page = 'tagPeople';
@ -746,24 +854,6 @@ export default {
return; return;
}, },
goBack() {
this.pageTitle = '';
switch(this.page) {
case 'addToStory':
this.page = 1;
break;
case 'cropPhoto':
this.page = 2;
break;
default:
this.namedPages.indexOf(this.page) != -1 ? this.page = 3 : this.page--;
break;
}
},
showVisibilityCard() { showVisibilityCard() {
this.pageTitle = 'Post Visibility'; this.pageTitle = 'Post Visibility';
this.page = 'visibility'; this.page = 'visibility';
@ -789,6 +879,33 @@ export default {
this.visibilityTag = tags[state]; this.visibilityTag = tags[state];
this.pageTitle = ''; this.pageTitle = '';
this.page = 3; this.page = 3;
},
showMediaDescriptionsCard() {
this.pageTitle = 'Media Descriptions';
this.page = 'altText';
},
showAddToCollectionsCard() {
this.pageTitle = 'Add to Collection';
this.page = 'addToCollection';
},
showSchedulePostCard() {
this.pageTitle = 'Schedule Post';
this.page = 'schedulePost';
},
showEditMediaCard() {
this.pageTitle = 'Edit Media';
this.page = 'editMedia';
},
fetchCameraRollDrafts() {
axios.get('/api/pixelfed/local/drafts')
.then(res => {
this.cameraRollMedia = res.data;
});
} }
} }
} }

View file

@ -117,10 +117,21 @@
<div class="d-flex flex-md-column flex-column-reverse h-100" style="overflow-y: auto;"> <div class="d-flex flex-md-column flex-column-reverse h-100" style="overflow-y: auto;">
<div class="card-body status-comments pb-5"> <div class="card-body status-comments pb-5">
<div class="status-comment"> <div class="status-comment">
<p :class="[status.content.length > 620 ? 'mb-1 read-more' : 'mb-1']" style="overflow: hidden;"> <div v-if="showCaption != true">
<a class="font-weight-bold pr-1 text-dark text-decoration-none" :href="statusProfileUrl">{{statusUsername}}</a> <span class="py-3">
<span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span> <a class="text-dark font-weight-bold mr-1" :href="status.account.url" v-bind:title="status.account.username">{{truncate(status.account.username,15)}}</a>
</p> <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="showCaption = true">Show</span>
</span>
</span>
</div>
<div v-else>
<p :class="[status.content.length > 620 ? 'mb-1 read-more' : 'mb-1']" style="overflow: hidden;">
<a class="font-weight-bold pr-1 text-dark text-decoration-none" :href="statusProfileUrl">{{statusUsername}}</a>
<span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
</p>
</div>
<div v-if="showComments"> <div v-if="showComments">
<hr> <hr>
@ -214,19 +225,6 @@
</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">😂</li>
<li class="nav-item" v-on:click="emojiReaction">💯</li>
<li class="nav-item" v-on:click="emojiReaction"></li>
<li class="nav-item" v-on:click="emojiReaction">🙌</li>
<li class="nav-item" v-on:click="emojiReaction">👏</li>
<li class="nav-item" v-on:click="emojiReaction">👌</li>
<li class="nav-item" v-on:click="emojiReaction">😍</li>
<li class="nav-item" v-on:click="emojiReaction">😯</li>
<li class="nav-item" v-on:click="emojiReaction">😢</li>
<li class="nav-item" v-on:click="emojiReaction">😅</li>
<li class="nav-item" v-on:click="emojiReaction">😁</li>
<li class="nav-item" v-on:click="emojiReaction">🙂</li>
<li class="nav-item" v-on:click="emojiReaction">😎</li>
<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>
</div> </div>
@ -595,8 +593,9 @@ export default {
loading: null, loading: null,
replyingToId: this.statusId, replyingToId: this.statusId,
replyToIndex: 0, replyToIndex: 0,
emoji: ['😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'], emoji: window.App.util.emoji,
showReadMore: true, showReadMore: true,
showCaption: true,
} }
}, },
@ -663,6 +662,7 @@ export default {
self.likesPage = 2; self.likesPage = 2;
self.sharesPage = 2; self.sharesPage = 2;
this.showMuteBlock(); this.showMuteBlock();
self.showCaption = !response.data.status.sensitive;
if(self.status.comments_disabled == false) { if(self.status.comments_disabled == false) {
self.showComments = true; self.showComments = true;
this.fetchComments(); this.fetchComments();

View file

@ -43,7 +43,7 @@
<li class="nav-item"> <li class="nav-item">
<div class="font-weight-light"> <div class="font-weight-light">
<span class="text-dark text-center"> <span class="text-dark text-center">
<p class="font-weight-bold mb-0">{{profile.statuses_count}}</p> <p class="font-weight-bold mb-0">{{formatCount(profile.statuses_count)}}</p>
<p class="text-muted mb-0 small">Posts</p> <p class="text-muted mb-0 small">Posts</p>
</span> </span>
</div> </div>
@ -51,7 +51,7 @@
<li class="nav-item"> <li class="nav-item">
<div v-if="profileSettings.followers.count" class="font-weight-light"> <div v-if="profileSettings.followers.count" class="font-weight-light">
<a class="text-dark cursor-pointer text-center" v-on:click="followersModal()"> <a class="text-dark cursor-pointer text-center" v-on:click="followersModal()">
<p class="font-weight-bold mb-0">{{profile.followers_count}}</p> <p class="font-weight-bold mb-0">{{formatCount(profile.followers_count)}}</p>
<p class="text-muted mb-0 small">Followers</p> <p class="text-muted mb-0 small">Followers</p>
</a> </a>
</div> </div>
@ -59,7 +59,7 @@
<li class="nav-item"> <li class="nav-item">
<div v-if="profileSettings.following.count" class="font-weight-light"> <div v-if="profileSettings.following.count" class="font-weight-light">
<a class="text-dark cursor-pointer text-center" v-on:click="followingModal()"> <a class="text-dark cursor-pointer text-center" v-on:click="followingModal()">
<p class="font-weight-bold mb-0">{{profile.following_count}}</p> <p class="font-weight-bold mb-0">{{formatCount(profile.following_count)}}</p>
<p class="text-muted mb-0 small">Following</p> <p class="text-muted mb-0 small">Following</p>
</a> </a>
</div> </div>
@ -109,19 +109,19 @@
<div class="d-none d-md-inline-flex profile-stats pb-3"> <div class="d-none d-md-inline-flex profile-stats pb-3">
<div class="font-weight-light pr-5"> <div class="font-weight-light pr-5">
<span class="text-dark"> <span class="text-dark">
<span class="font-weight-bold">{{profile.statuses_count}}</span> <span class="font-weight-bold">{{formatCount(profile.statuses_count)}}</span>
Posts Posts
</span> </span>
</div> </div>
<div v-if="profileSettings.followers.count" class="font-weight-light pr-5"> <div v-if="profileSettings.followers.count" class="font-weight-light pr-5">
<a class="text-dark cursor-pointer" v-on:click="followersModal()"> <a class="text-dark cursor-pointer" v-on:click="followersModal()">
<span class="font-weight-bold">{{profile.followers_count}}</span> <span class="font-weight-bold">{{formatCount(profile.followers_count)}}</span>
Followers Followers
</a> </a>
</div> </div>
<div v-if="profileSettings.following.count" class="font-weight-light"> <div v-if="profileSettings.following.count" class="font-weight-light">
<a class="text-dark cursor-pointer" v-on:click="followingModal()"> <a class="text-dark cursor-pointer" v-on:click="followingModal()">
<span class="font-weight-bold">{{profile.following_count}}</span> <span class="font-weight-bold">{{formatCount(profile.following_count)}}</span>
Following Following
</a> </a>
</div> </div>
@ -304,15 +304,15 @@
<div v-if="profile.note" class="text-center text-muted p-3" v-html="profile.note"></div> <div v-if="profile.note" class="text-center text-muted p-3" v-html="profile.note"></div>
<div class="pb-3 text-muted text-center"> <div class="pb-3 text-muted text-center">
<a class="text-lighter" :href="profile.url"> <a class="text-lighter" :href="profile.url">
<span class="font-weight-bold">{{profile.statuses_count}}</span> <span class="font-weight-bold">{{formatCount(profile.statuses_count)}}</span>
Posts Posts
</a> </a>
<a v-if="profileSettings.followers.count" class="text-lighter cursor-pointer px-3" v-on:click="followersModal()"> <a v-if="profileSettings.followers.count" class="text-lighter cursor-pointer px-3" v-on:click="followersModal()">
<span class="font-weight-bold">{{profile.followers_count}}</span> <span class="font-weight-bold">{{formatCount(profile.followers_count)}}</span>
Followers Followers
</a> </a>
<a v-if="profileSettings.following.count" class="text-lighter cursor-pointer" v-on:click="followingModal()"> <a v-if="profileSettings.following.count" class="text-lighter cursor-pointer" v-on:click="followingModal()">
<span class="font-weight-bold">{{profile.following_count}}</span> <span class="font-weight-bold">{{formatCount(profile.following_count)}}</span>
Following Following
</a> </a>
</div> </div>
@ -1076,6 +1076,10 @@
copyProfileLink() { copyProfileLink() {
navigator.clipboard.writeText(window.location.href); navigator.clipboard.writeText(window.location.href);
this.$refs.visitorContextMenu.hide(); this.$refs.visitorContextMenu.hide();
},
formatCount(count) {
return App.util.format.count(count);
} }
} }
} }

View file

@ -127,7 +127,7 @@
</div> </div>
</div> </div>
<div class="postPresenterContainer" @click="lightbox(status)"> <div class="postPresenterContainer">
<div v-if="status.pf_type === 'photo'" class="w-100"> <div v-if="status.pf_type === 'photo'" class="w-100">
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter> <photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
</div> </div>
@ -203,19 +203,6 @@
<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)">😂</li>
<li class="nav-item" v-on:click="emojiReaction(status)">💯</li>
<li class="nav-item" v-on:click="emojiReaction(status)"></li>
<li class="nav-item" v-on:click="emojiReaction(status)">🙌</li>
<li class="nav-item" v-on:click="emojiReaction(status)">👏</li>
<li class="nav-item" v-on:click="emojiReaction(status)">👌</li>
<li class="nav-item" v-on:click="emojiReaction(status)">😍</li>
<li class="nav-item" v-on:click="emojiReaction(status)">😯</li>
<li class="nav-item" v-on:click="emojiReaction(status)">😢</li>
<li class="nav-item" v-on:click="emojiReaction(status)">😅</li>
<li class="nav-item" v-on:click="emojiReaction(status)">😁</li>
<li class="nav-item" v-on:click="emojiReaction(status)">🙂</li>
<li class="nav-item" v-on:click="emojiReaction(status)">😎</li>
<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>
@ -274,15 +261,15 @@
<div class="card-footer bg-transparent border-0 mt-2 py-1"> <div class="card-footer bg-transparent border-0 mt-2 py-1">
<div class="d-flex justify-content-between text-center"> <div class="d-flex justify-content-between text-center">
<span class="cursor-pointer" @click="redirect(profile.url)"> <span class="cursor-pointer" @click="redirect(profile.url)">
<p class="mb-0 font-weight-bold">{{profile.statuses_count}}</p> <p class="mb-0 font-weight-bold">{{formatCount(profile.statuses_count)}}</p>
<p class="mb-0 small text-muted">Posts</p> <p class="mb-0 small text-muted">Posts</p>
</span> </span>
<span class="cursor-pointer" @click="redirect(profile.url+'?md=followers')"> <span class="cursor-pointer" @click="redirect(profile.url+'?md=followers')">
<p class="mb-0 font-weight-bold">{{profile.followers_count}}</p> <p class="mb-0 font-weight-bold">{{formatCount(profile.followers_count)}}</p>
<p class="mb-0 small text-muted">Followers</p> <p class="mb-0 small text-muted">Followers</p>
</span> </span>
<span class="cursor-pointer" @click="redirect(profile.url+'?md=following')"> <span class="cursor-pointer" @click="redirect(profile.url+'?md=following')">
<p class="mb-0 font-weight-bold">{{profile.following_count}}</p> <p class="mb-0 font-weight-bold">{{formatCount(profile.following_count)}}</p>
<p class="mb-0 small text-muted">Following</p> <p class="mb-0 small text-muted">Following</p>
</span> </span>
</div> </div>
@ -502,7 +489,7 @@
showReadMore: true, showReadMore: true,
replyStatus: {}, replyStatus: {},
replyText: '', replyText: '',
emoji: ['😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'], emoji: window.App.util.emoji,
showHashtagPosts: false, showHashtagPosts: false,
hashtagPosts: [], hashtagPosts: [],
hashtagPostsName: '', hashtagPostsName: '',
@ -592,7 +579,7 @@
axios.get(apiUrl, { axios.get(apiUrl, {
params: { params: {
max_id: this.max_id, max_id: this.max_id,
limit: 4 limit: 5
} }
}).then(res => { }).then(res => {
let data = res.data; let data = res.data;
@ -1311,6 +1298,10 @@
hideTips() { hideTips() {
this.showTips = false; this.showTips = false;
window.localStorage.setItem('metro-tips', false); window.localStorage.setItem('metro-tips', false);
},
formatCount(count) {
return App.util.format.count(count);
} }
} }

View file

@ -28,8 +28,8 @@
</b-carousel> </b-carousel>
</details> </details>
</div> </div>
<div v-else> <div v-else class="w-100 h-100 p-0">
<b-carousel :id="status.id + '-carousel'" <!-- <b-carousel :id="status.id + '-carousel'"
style="text-shadow: 1px 1px 2px #333; background-color: #000;" style="text-shadow: 1px 1px 2px #333; background-color: #000;"
controls controls
img-blank img-blank
@ -49,7 +49,22 @@
<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p> <p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
</b-carousel-slide> </b-carousel-slide>
</b-carousel> </b-carousel> -->
<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb">
<slide v-for="(media, index) in status.media_attachments" :key="'px-carousel-'+media.id + '-' + index" class="w-100 h-100 d-block mx-auto text-center" style="max-height: 600px;">
<video v-if="media.type == 'Video'" class="embed-responsive-item" preload="none" controls loop :title="media.description" width="100%" height="100%" :poster="media.preview_url">
<source :src="media.url" :type="media.mime">
</video>
<div v-else-if="media.type == 'Image'" :title="media.description">
<img :class="media.filter_class + ' img-fluid w-100'" :src="media.url" :alt="media.description" loading="lazy">
</div>
<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
</slide>
</carousel>
</div> </div>
</template> </template>

View file

@ -1,11 +1,11 @@
<template> <template>
<div v-if="status.sensitive == true"> <div v-if="status.sensitive == true">
<details class="details-animated"> <details class="details-animated">
<summary> <summary @click="loadSensitive">
<p class="mb-0 lead font-weight-bold">{{ status.spoiler_text ? status.spoiler_text : 'CW / NSFW / Hidden Media'}}</p> <p class="mb-0 lead font-weight-bold">{{ status.spoiler_text ? status.spoiler_text : 'CW / NSFW / Hidden Media'}}</p>
<p class="font-weight-light">(click to show)</p> <p class="font-weight-light">(click to show)</p>
</summary> </summary>
<b-carousel :id="status.id + '-carousel'" <!-- <b-carousel :id="status.id + '-carousel'"
v-model="cursor" v-model="cursor"
style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;" style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;"
controls controls
@ -20,11 +20,16 @@
<span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;"> <span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;">
{{cursor + 1}} / {{status.media_attachments.length}} {{cursor + 1}} / {{status.media_attachments.length}}
</span> </span>
</b-carousel> </b-carousel> -->
<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb">
<slide v-for="(img, index) in status.media_attachments" :key="'px-carousel-'+img.id + '-' + index" class="w-100 h-100 d-block mx-auto text-center" style="max-height: 600px;" :title="img.description">
<img :class="img.filter_class + ' img-fluid'" style="max-height: 600px;" :src="img.url" :alt="img.description">
</slide>
</carousel>
</details> </details>
</div> </div>
<div v-else> <div v-else class="w-100 h-100 p-0">
<b-carousel :id="status.id + '-carousel'" <!-- <b-carousel :id="status.id + '-carousel'"
v-model="cursor" v-model="cursor"
style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;" style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;"
controls controls
@ -39,7 +44,12 @@
<span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;"> <span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;">
{{cursor + 1}} / {{status.media_attachments.length}} {{cursor + 1}} / {{status.media_attachments.length}}
</span> </span>
</b-carousel> </b-carousel> -->
<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb" class="p-0 m-0">
<slide v-for="(img, index) in status.media_attachments" :key="'px-carousel-'+img.id + '-' + index" class="" style="background: #000; display: flex;align-items: center;max-height: 600px;" :title="img.description">
<img :class="img.filter_class + ' img-fluid w-100 p-0'" style="max-height: 600px;" :src="img.url" :alt="img.description">
</slide>
</carousel>
</div> </div>
</template> </template>
@ -58,6 +68,42 @@
return { return {
cursor: 0 cursor: 0
} }
},
created() {
// window.addEventListener("keydown", this.keypressNavigation);
},
beforeDestroy() {
// window.removeEventListener("keydown", this.keypressNavigation);
},
methods: {
loadSensitive() {
this.$refs.carousel.onResize();
this.$refs.carousel.goToPage(0);
},
keypressNavigation(e) {
let ref = this.$refs.carousel;
if (e.keyCode == "37") {
e.preventDefault();
let direction = "backward";
ref.advancePage(direction);
ref.$emit("navigation-click", direction);
}
if (e.keyCode == "39") {
e.preventDefault();
let direction = "forward";
ref.advancePage(direction);
ref.$emit("navigation-click", direction);
}
}
} }
} }
</script> </script>

View file

@ -559,3 +559,10 @@ details summary::-webkit-details-marker {
.carousel-control-prev-icon, .carousel-control-next-icon { .carousel-control-prev-icon, .carousel-control-next-icon {
filter: drop-shadow(0px 0px 1px black); filter: drop-shadow(0px 0px 1px black);
} }
.VueCarousel:focus,
.VueCarousel-navigation-button:focus,
.VueCarousel-dot:focus,
.VueCarousel-dot--active:focus {
outline: 0px !important;
}

View file

@ -3,6 +3,12 @@
@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">

View file

@ -9,7 +9,10 @@
<div class="card-body row pl-md-5 ml-md-5"> <div class="card-body row pl-md-5 ml-md-5">
@foreach(App\Util\Localization\Localization::languages() as $lang) @foreach(App\Util\Localization\Localization::languages() as $lang)
<div class="col-12 col-md-4 mb-2"> <div class="col-12 col-md-4 mb-2">
<a href="/i/lang/{{$lang}}" class="{{$current == $lang ? 'font-weight-bold text-primary' : 'text-muted'}} pr-3 b-3">{{locale_get_display_language($lang, $lang)}}</a> <a href="/i/lang/{{$lang}}" class="{{$current == $lang ? 'font-weight-bold text-primary' : 'text-muted'}} pr-3 b-3">
{{locale_get_display_language($lang, $lang)}}
<span class="small text-lighter">({{locale_get_display_language($lang, 'en')}})</span>
</a>
</div> </div>
@endforeach @endforeach
</div> </div>

View file

@ -198,7 +198,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('auth/checkpoint', 'AccountController@twoFactorCheckpoint'); Route::get('auth/checkpoint', 'AccountController@twoFactorCheckpoint');
Route::post('auth/checkpoint', 'AccountController@twoFactorVerify'); Route::post('auth/checkpoint', 'AccountController@twoFactorVerify');
Route::get('media/preview/{profileId}/{mediaId}', 'ApiController@showTempMedia')->name('temp-media'); Route::get('media/preview/{profileId}/{mediaId}/{timestamp}', 'ApiController@showTempMedia')->name('temp-media');
Route::get('results', 'SearchController@results'); Route::get('results', 'SearchController@results');
Route::post('visibility', 'StatusController@toggleVisibility'); Route::post('visibility', 'StatusController@toggleVisibility');