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 #1511 from pixelfed/frontend-ui-refactor

Add Collections Feature
This commit is contained in:
daniel 2019-07-17 21:23:20 -06:00 committed by GitHub
commit b9697d15d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 3607 additions and 423 deletions

View file

@ -2,6 +2,7 @@
namespace App; namespace App;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Pixelfed\Snowflake\HasSnowflakePrimary; use Pixelfed\Snowflake\HasSnowflakePrimary;
@ -16,8 +17,34 @@ class Collection extends Model
*/ */
public $incrementing = false; public $incrementing = false;
public $fillable = ['profile_id', 'published_at'];
public $dates = ['published_at'];
public function profile() public function profile()
{ {
return $this->belongsTo(Profile::class); return $this->belongsTo(Profile::class);
} }
public function items()
{
return $this->hasMany(CollectionItem::class);
}
public function posts()
{
return $this->hasManyThrough(
Status::class,
CollectionItem::class,
'collection_id',
'id',
'id',
'object_id',
);
}
public function url()
{
return url("/c/{$this->id}");
}
} }

View file

@ -9,6 +9,13 @@ class CollectionItem extends Model
{ {
use HasSnowflakePrimary; use HasSnowflakePrimary;
public $fillable = [
'collection_id',
'object_type',
'object_id',
'order'
];
/** /**
* Indicates if the IDs are auto-incrementing. * Indicates if the IDs are auto-incrementing.
* *

View file

@ -3,8 +3,199 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Auth;
use App\{
Collection,
CollectionItem,
Profile,
Status
};
use League\Fractal;
use App\Transformer\Api\{
AccountTransformer,
StatusTransformer,
};
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
class CollectionController extends Controller class CollectionController extends Controller
{ {
// public function create(Request $request)
{
abort_if(!Auth::check(), 403);
$profile = Auth::user()->profile;
$collection = Collection::firstOrCreate([
'profile_id' => $profile->id,
'published_at' => null
]);
return view('collection.create', compact('collection'));
}
public function show(Request $request, int $collection)
{
$collection = Collection::whereNotNull('published_at')->findOrFail($collection);
if($collection->profile->status != null) {
abort(404);
}
if($collection->visibility !== 'public') {
abort_if(!Auth::check() || Auth::user()->profile_id != $collection->profile_id, 404);
}
return view('collection.show', compact('collection'));
}
public function index(Request $request)
{
abort_if(!Auth::check(), 403);
return $request->all();
}
public function store(Request $request, int $id)
{
abort_if(!Auth::check(), 403);
$this->validate($request, [
'title' => 'nullable',
'description' => 'nullable',
'visibility' => 'required|alpha|in:public,private'
]);
$profile = Auth::user()->profile;
$collection = Collection::whereProfileId($profile->id)->findOrFail($id);
$collection->title = e($request->input('title'));
$collection->description = e($request->input('description'));
$collection->visibility = e($request->input('visibility'));
$collection->save();
return 200;
}
public function publish(Request $request, int $id)
{
abort_if(!Auth::check(), 403);
$this->validate($request, [
'title' => 'nullable',
'description' => 'nullable',
'visibility' => 'required|alpha|in:public,private'
]);
$profile = Auth::user()->profile;
$collection = Collection::whereProfileId($profile->id)->findOrFail($id);
$collection->title = e($request->input('title'));
$collection->description = e($request->input('description'));
$collection->visibility = e($request->input('visibility'));
$collection->published_at = now();
$collection->save();
return $collection->url();
}
public function delete(Request $request, int $id)
{
abort_if(!Auth::check(), 403);
$user = Auth::user();
$collection = Collection::whereProfileId($user->profile_id)->findOrFail($id);
$collection->items()->delete();
$collection->delete();
return 200;
}
public function storeId(Request $request)
{
$this->validate($request, [
'collection_id' => 'required|int|min:1|exists:collections,id',
'post_id' => 'required|int|min:1|exists:statuses,id'
]);
$profileId = Auth::user()->profile_id;
$collectionId = $request->input('collection_id');
$postId = $request->input('post_id');
$collection = Collection::whereProfileId($profileId)->findOrFail($collectionId);
$count = $collection->items()->count();
if($count >= 18) {
abort(400, 'You can only add 18 posts per collection');
}
$status = Status::whereScope('public')
->whereIn('type', ['photo'])
->findOrFail($postId);
$item = CollectionItem::firstOrCreate([
'collection_id' => $collection->id,
'object_type' => 'App\Status',
'object_id' => $status->id
],[
'order' => $count,
]);
return 200;
}
public function get(Request $request, int $id)
{
$profile = Auth::check() ? Auth::user()->profile : [];
$collection = Collection::whereVisibility('public')->findOrFail($id);
if($collection->published_at == null) {
if(!Auth::check() || $profile->id !== $collection->profile_id) {
abort(404);
}
}
return [
'id' => $collection->id,
'title' => $collection->title,
'description' => $collection->description,
'visibility' => $collection->visibility
];
}
public function getItems(Request $request, int $id)
{
$collection = Collection::findOrFail($id);
if($collection->visibility !== 'public') {
abort_if(!Auth::check() || Auth::user()->profile_id != $collection->profile_id, 404);
}
$posts = $collection->posts()->orderBy('order', 'asc')->paginate(18);
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Collection($posts, new StatusTransformer());
$res = $fractal->createData($resource)->toArray();
return response()->json($res);
}
public function getUserCollections(Request $request, int $id)
{
$profile = Profile::whereNull('status')
->whereNull('domain')
->findOrFail($id);
if($profile->is_private) {
abort_if(!Auth::check(), 404);
abort_if(!$profile->followedBy(Auth::user()->profile) && $profile->id != Auth::user()->profile_id, 404);
}
return $profile
->collections()
->has('posts')
->with('posts')
->whereVisibility('public')
->whereNotNull('published_at')
->orderByDesc('published_at')
->paginate(9)
->map(function($collection) {
return [
'id' => $collection->id,
'title' => $collection->title,
'description' => $collection->description,
'thumb' => $collection->posts()->first()->thumb(),
'url' => $collection->url(),
'published_at' => $collection->published_at
];
});
}
} }

View file

@ -291,4 +291,9 @@ class Profile extends Model
{ {
return $this->hasMany(HashtagFollow::class); return $this->hasMany(HashtagFollow::class);
} }
public function collections()
{
return $this->hasMany(Collection::class);
}
} }

View file

@ -68,4 +68,19 @@ trait User {
{ {
return 100; return 100;
} }
public function getMaxCollectionsPerHourAttribute()
{
return 10;
}
public function getMaxCollectionsPerDayAttribute()
{
return 20;
}
public function getMaxCollectionsPerMonthAttribute()
{
return 100;
}
} }

View file

@ -42,6 +42,7 @@
"fzaninotto/faker": "^1.4", "fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0", "mockery/mockery": "^1.0",
"nunomaduro/collision": "^2.0", "nunomaduro/collision": "^2.0",
"nunomaduro/phpinsights": "^1.7",
"phpunit/phpunit": "^7.5" "phpunit/phpunit": "^7.5"
}, },
"autoload": { "autoload": {

3190
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -150,7 +150,6 @@ return [
/* /*
* Package Service Providers... * Package Service Providers...
*/ */
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...
@ -211,7 +210,6 @@ return [
'Validator' => Illuminate\Support\Facades\Validator::class, 'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class, 'View' => Illuminate\Support\Facades\View::class,
'DotenvEditor' => Jackiedo\DotenvEditor\Facades\DotenvEditor::class,
'PrettyNumber' => App\Util\Lexer\PrettyNumber::class, 'PrettyNumber' => App\Util\Lexer\PrettyNumber::class,
'Purify' => Stevebauman\Purify\Facades\Purify::class, 'Purify' => Stevebauman\Purify\Facades\Purify::class,
'FFMpeg' => Pbmedia\LaravelFFMpeg\FFMpegFacade::class, 'FFMpeg' => Pbmedia\LaravelFFMpeg\FFMpegFacade::class,

View file

@ -3,7 +3,7 @@
return [ return [
'announcement' => [ 'announcement' => [
'enabled' => env('INSTANCE_ANNOUNCEMENT_ENABLED', true), 'enabled' => env('INSTANCE_ANNOUNCEMENT_ENABLED', false),
'message' => env('INSTANCE_ANNOUNCEMENT_MESSAGE', 'Example announcement message.<br><span class="font-weight-normal">Something else here</span>') 'message' => env('INSTANCE_ANNOUNCEMENT_MESSAGE', 'Example announcement message.<br><span class="font-weight-normal">Something else here</span>')
], ],

View file

@ -23,7 +23,7 @@ return [
| This value is the version of your Pixelfed instance. | This value is the version of your Pixelfed instance.
| |
*/ */
'version' => '0.9.6', 'version' => '0.10.0',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -1,4 +1,4 @@
FROM php:7.3-apache FROM php:7.3-apache-buster
ARG COMPOSER_VERSION="1.8.5" ARG COMPOSER_VERSION="1.8.5"
ARG COMPOSER_CHECKSUM="4e4c1cd74b54a26618699f3190e6f5fc63bb308b13fa660f71f2a2df047c0e17" ARG COMPOSER_CHECKSUM="4e4c1cd74b54a26618699f3190e6f5fc63bb308b13fa660f71f2a2df047c0e17"
@ -7,13 +7,13 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-utils \ && apt-get install -y --no-install-recommends apt-utils \
&& apt-get install -y --no-install-recommends git gosu \ && apt-get install -y --no-install-recommends git gosu \
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \ optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \
libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-3 \ libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-6 \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev \ libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev \
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \ && sed -i '/en_US/s/^#//g' /etc/locale.gen \
&& locale-gen && update-locale \ && locale-gen && update-locale \
&& docker-php-source extract \ && docker-php-source extract \
&& docker-php-ext-configure gd \ && docker-php-ext-configure gd \
--with-freetype-dir=/usr/lib/x86_64-linux-gnu/ \ --enable-freetype \
--with-jpeg-dir=/usr/lib/x86_64-linux-gnu/ \ --with-jpeg-dir=/usr/lib/x86_64-linux-gnu/ \
--with-xpm-dir=/usr/lib/x86_64-linux-gnu/ \ --with-xpm-dir=/usr/lib/x86_64-linux-gnu/ \
--with-webp-dir=/usr/lib/x86_64-linux-gnu/ \ --with-webp-dir=/usr/lib/x86_64-linux-gnu/ \

View file

@ -1,24 +1,25 @@
FROM php:7.2-fpm FROM php:7.3-fpm-buster
ARG COMPOSER_VERSION="1.8.5" ARG COMPOSER_VERSION="1.8.5"
ARG COMPOSER_CHECKSUM="4e4c1cd74b54a26618699f3190e6f5fc63bb308b13fa660f71f2a2df047c0e17" ARG COMPOSER_CHECKSUM="4e4c1cd74b54a26618699f3190e6f5fc63bb308b13fa660f71f2a2df047c0e17"
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-utils \
&& apt-get install -y --no-install-recommends git gosu \ && apt-get install -y --no-install-recommends git gosu \
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev \ optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \
libfreetype6 libjpeg62-turbo libpng16-16 libxpm4 libvpx4 libmagickwand-6.q16-3 \ libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-6 \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libvpx-dev libmagickwand-dev \ libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev \
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \ && sed -i '/en_US/s/^#//g' /etc/locale.gen \
&& locale-gen && update-locale \ && locale-gen && update-locale \
&& docker-php-source extract \ && docker-php-source extract \
&& docker-php-ext-configure gd \ && docker-php-ext-configure gd \
--with-freetype-dir=/usr/lib/x86_64-linux-gnu/ \ --enable-freetype \
--with-jpeg-dir=/usr/lib/x86_64-linux-gnu/ \ --with-jpeg-dir=/usr/lib/x86_64-linux-gnu/ \
--with-xpm-dir=/usr/lib/x86_64-linux-gnu/ \ --with-xpm-dir=/usr/lib/x86_64-linux-gnu/ \
--with-vpx-dir=/usr/lib/x86_64-linux-gnu/ \ --with-webp-dir=/usr/lib/x86_64-linux-gnu/ \
&& docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite pcntl gd exif bcmath intl zip \ && docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite pcntl gd exif bcmath intl zip curl \
&& pecl install imagick \ && pecl install imagick \
&& docker-php-ext-enable imagick pcntl imagick gd exif zip \ && docker-php-ext-enable imagick pcntl imagick gd exif zip curl \
&& curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /usr/bin/composer \ && curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /usr/bin/composer \
&& echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \ && echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \
&& chmod 755 /usr/bin/composer \ && chmod 755 /usr/bin/composer \
@ -32,7 +33,7 @@ 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 \ RUN mkdir public.ext && 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-dist --no-interaction \ && composer install --prefer-dist --no-interaction \
&& rm -rf html && ln -s public html && rm -rf html && ln -s public html

File diff suppressed because one or more lines are too long

1
public/js/collections.js vendored Normal file
View file

@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{17:function(t,e,s){t.exports=s("ntcu")},BzCV:function(t,e,s){"use strict";s.r(e);var n={props:["collection-id"],data:function(){return{loaded:!1,posts:[]}},beforeMount:function(){this.fetchItems()},mounted:function(){},methods:{fetchItems:function(){var t=this;axios.get("/api/local/collection/items/"+this.collectionId).then(function(e){t.posts=e.data})},previewUrl:function(t){return t.sensitive?"/storage/no-preview.png?v="+(new Date).getTime():t.media_attachments[0].preview_url},previewBackground:function(t){return"background-image: url("+this.previewUrl(t)+");"}}},a=s("KHd+"),i=Object(a.a)(n,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[s("div",{staticClass:"row"},t._l(t.posts,function(e,n){return s("div",{staticClass:"col-4 p-0 p-sm-2 p-md-3 p-xs-1"},[s("a",{staticClass:"card info-overlay card-md-border-0",attrs:{href:e.url}},[s("div",{staticClass:"square"},["photo:album"==e.pf_type?s("span",{staticClass:"float-right mr-3 post-icon"},[s("i",{staticClass:"fas fa-images fa-2x"})]):t._e(),t._v(" "),"video"==e.pf_type?s("span",{staticClass:"float-right mr-3 post-icon"},[s("i",{staticClass:"fas fa-video fa-2x"})]):t._e(),t._v(" "),"video:album"==e.pf_type?s("span",{staticClass:"float-right mr-3 post-icon"},[s("i",{staticClass:"fas fa-film fa-2x"})]):t._e(),t._v(" "),s("div",{staticClass:"square-content",style:t.previewBackground(e)}),t._v(" "),s("div",{staticClass:"info-overlay-text"},[s("h5",{staticClass:"text-white m-auto font-weight-bold"},[s("span",[s("span",{staticClass:"far fa-heart fa-lg p-2 d-flex-inline"}),t._v(" "),s("span",{staticClass:"d-flex-inline"},[t._v(t._s(e.favourites_count))])]),t._v(" "),s("span",[s("span",{staticClass:"fas fa-retweet fa-lg p-2 d-flex-inline"}),t._v(" "),s("span",{staticClass:"d-flex-inline"},[t._v(t._s(e.reblogs_count))])])])])])])])}),0)])},[],!1,null,"5edf1095",null);e.default=i.exports},"KHd+":function(t,e,s){"use strict";function n(t,e,s,n,a,i,o,r){var c,l="function"==typeof t?t.options:t;if(e&&(l.render=e,l.staticRenderFns=s,l._compiled=!0),n&&(l.functional=!0),i&&(l._scopeId="data-v-"+i),o?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),a&&a.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(o)},l._ssrRegister=c):a&&(c=r?function(){a.call(this,this.$root.$options.shadowRoot)}:a),c)if(l.functional){l._injectStyles=c;var f=l.render;l.render=function(t,e){return c.call(e),f(t,e)}}else{var p=l.beforeCreate;l.beforeCreate=p?[].concat(p,c):[c]}return{exports:t,options:l}}s.d(e,"a",function(){return n})},ntcu:function(t,e,s){Vue.component("collection-component",s("BzCV").default)}},[[17,0]]]);

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/loops.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

2
public/js/quill.js vendored

File diff suppressed because one or more lines are too long

2
public/js/search.js vendored

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

View file

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

File diff suppressed because one or more lines are too long

2
public/js/vendor.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{ {
"/js/manifest.js": "/js/manifest.js?id=01c8731923a46c30aaed", "/js/manifest.js": "/js/manifest.js?id=01c8731923a46c30aaed",
"/js/vendor.js": "/js/vendor.js?id=6e7489157aac9e82dbbe", "/js/vendor.js": "/js/vendor.js?id=383c6f227a3b8d8d1c71",
"/js/ace.js": "/js/ace.js?id=4a28163d5fd63e64d6af", "/js/ace.js": "/js/ace.js?id=4a28163d5fd63e64d6af",
"/js/activity.js": "/js/activity.js?id=7405cc1a22814a5b2a70", "/js/activity.js": "/js/activity.js?id=7405cc1a22814a5b2a70",
"/js/app.js": "/js/app.js?id=2f034c84c06dbb3e511d", "/js/app.js": "/js/app.js?id=2f034c84c06dbb3e511d",
@ -8,18 +8,19 @@
"/css/appdark.css": "/css/appdark.css?id=1b13fc163fa4deb9233f", "/css/appdark.css": "/css/appdark.css?id=1b13fc163fa4deb9233f",
"/css/landing.css": "/css/landing.css?id=31de3e75de8690f7ece5", "/css/landing.css": "/css/landing.css?id=31de3e75de8690f7ece5",
"/css/quill.css": "/css/quill.css?id=81604d62610b0dbffad6", "/css/quill.css": "/css/quill.css?id=81604d62610b0dbffad6",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=ec48ebc94ae6b1ec70ea", "/js/collectioncompose.js": "/js/collectioncompose.js?id=200765234feeb3b1351c",
"/js/components.js": "/js/components.js?id=d64d41a2defb1a205865", "/js/collections.js": "/js/collections.js?id=93bac411f11eb701648f",
"/js/compose.js": "/js/compose.js?id=488b24bf2f2168540449", "/js/components.js": "/js/components.js?id=7e4df37c02f12db5ef96",
"/js/developers.js": "/js/developers.js?id=ba029a560f0c30c5efc9", "/js/compose.js": "/js/compose.js?id=df5bd23aef5b73027cce",
"/js/discover.js": "/js/discover.js?id=627f0a106fd359ae3b80", "/js/developers.js": "/js/developers.js?id=a395f12c52bb0eada6ab",
"/js/hashtag.js": "/js/hashtag.js?id=0f7e529e8128cc17638b", "/js/discover.js": "/js/discover.js?id=f8da29f2b16ae5be93fd",
"/js/loops.js": "/js/loops.js?id=19112dc8663fc43db735", "/js/hashtag.js": "/js/hashtag.js?id=b4ffe6499880acf0591c",
"/js/mode-dot.js": "/js/mode-dot.js?id=c7c83849e6bba99f1c33", "/js/loops.js": "/js/loops.js?id=214f31fc6c2d990487d8",
"/js/profile.js": "/js/profile.js?id=e285b20ac6d467f99138", "/js/mode-dot.js": "/js/mode-dot.js?id=8224e306cf53e3336620",
"/js/quill.js": "/js/quill.js?id=c2b060eaf87ef63eb5c1", "/js/profile.js": "/js/profile.js?id=56aca6209960f8ac2110",
"/js/search.js": "/js/search.js?id=3186ccb02c7fad43c701", "/js/quill.js": "/js/quill.js?id=9edfe94c043a1bc68860",
"/js/status.js": "/js/status.js?id=4b9a52b586880e264f05", "/js/search.js": "/js/search.js?id=b1bd588d07e682f8fce5",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=a4da64fc6e2f406a616d", "/js/status.js": "/js/status.js?id=248bb6c2bec534e81503",
"/js/timeline.js": "/js/timeline.js?id=a3c3e38470108fa5df42" "/js/theme-monokai.js": "/js/theme-monokai.js?id=344fb8527bb66574e4cd",
"/js/timeline.js": "/js/timeline.js?id=b894c6fe644d16ffc330"
} }

View file

@ -0,0 +1,4 @@
Vue.component(
'collection-compose',
require('./components/CollectionCompose.vue').default
);

4
resources/assets/js/collections.js vendored Normal file
View file

@ -0,0 +1,4 @@
Vue.component(
'collection-component',
require('./components/CollectionComponent.vue').default
);

View file

@ -0,0 +1,69 @@
<template>
<div>
<div class="row">
<div class="col-4 p-0 p-sm-2 p-md-3 p-xs-1" v-for="(s, index) in posts">
<a class="card info-overlay card-md-border-0" :href="s.url">
<div class="square">
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
<div class="square-content" v-bind:style="previewBackground(s)">
</div>
<div class="info-overlay-text">
<h5 class="text-white m-auto font-weight-bold">
<span>
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{s.favourites_count}}</span>
</span>
<span>
<span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{s.reblogs_count}}</span>
</span>
</h5>
</div>
</div>
</a>
</div>
</div>
</div>
</template>
<style type="text/css" scoped></style>
<script type="text/javascript">
export default {
props: ['collection-id'],
data() {
return {
loaded: false,
posts: [],
}
},
beforeMount() {
this.fetchItems();
},
mounted() {
},
methods: {
fetchItems() {
axios.get('/api/local/collection/items/' + this.collectionId)
.then(res => {
this.posts = res.data;
});
},
previewUrl(status) {
return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url;
},
previewBackground(status) {
let preview = this.previewUrl(status);
return 'background-image: url(' + preview + ');';
},
}
}
</script>

View file

@ -0,0 +1,257 @@
<template>
<div class="container">
<div v-if="loaded" class="row">
<div class="col-12 col-md-6 offset-md-3 pt-5">
<div class="text-center pb-4">
<h1>Create Collection</h1>
</div>
</div>
<div class="col-12 col-md-4 pt-3">
<div class="card rounded-0 shadow-none border " style="min-height: 440px;">
<div class="card-body">
<div>
<form>
<div class="form-group">
<label for="title" class="font-weight-bold text-muted">Title</label>
<input type="text" class="form-control" id="title" placeholder="Collection Title" v-model="collection.title">
</div>
<div class="form-group">
<label for="description" class="font-weight-bold text-muted">Description</label>
<textarea class="form-control" id="description" placeholder="Example description here" v-model="collection.description" rows="3">
</textarea>
</div>
<div class="form-group">
<label for="visibility" class="font-weight-bold text-muted">Visibility</label>
<select class="custom-select" v-model="collection.visibility">
<option value="public">Public</option>
<option value="private">Followers Only</option>
</select>
</div>
</form>
<hr>
<p>
<button type="button" class="btn btn-primary font-weight-bold btn-block" @click="publish">Publish</button>
</p>
<p>
<button type="button" class="btn btn-outline-primary font-weight-bold btn-block" @click="save">Save</button>
</p>
<p class="mb-0">
<button type="button" class="btn btn-outline-secondary font-weight-bold btn-block" @click="deleteCollection">Delete</button>
</p>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-8 pt-3">
<div>
<ul class="nav nav-tabs">
<li class="nav-item">
<a :class="[tab == 'add' ? 'nav-link font-weight-bold bg-white active' : 'nav-link font-weight-bold text-muted']" href="#" @click.prevent="tab = 'add'">Add Posts</a>
</li>
<li class="nav-item">
<a :class="[tab == 'all' ? 'nav-link font-weight-bold bg-white active' : 'nav-link font-weight-bold text-muted']" href="#" @click.prevent="tab = 'all'">Preview</a>
</li>
</ul>
</div>
<div class="card rounded-0 shadow-none border border-top-0">
<div class="card-body" style="height: 460px; overflow-y: auto">
<div v-if="tab == 'all'" class="row">
<div class="col-4 p-1" v-for="(s, index) in posts">
<a class="card info-overlay card-md-border-0" :href="s.url">
<div class="square">
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
<div class="square-content" v-bind:style="previewBackground(s)">
</div>
<div class="info-overlay-text">
<h5 class="text-white m-auto font-weight-bold">
<span>
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{s.favourites_count}}</span>
</span>
<span>
<span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{s.reblogs_count}}</span>
</span>
</h5>
</div>
</div>
</a>
</div>
</div>
<div v-if="tab == 'add'">
<div class="form-group">
<label for="title" class="font-weight-bold text-muted">Add Post by URL</label>
<input type="text" class="form-control" placeholder="https://pixelfed.dev/p/admin/1" v-model="id">
<p class="help-text small text-muted">Only local, public posts can be added</p>
</div>
<div class="form-group pt-4">
<label for="title" class="font-weight-bold text-muted">Add Recent Post</label>
<div>
<div v-for="(s, index) in recentPosts" :class="[selectedPost == s.id ? 'box-shadow border border-warning d-inline-block m-1':'d-inline-block m-1']" @click="selectPost(s)">
<div class="cursor-pointer" :style="'width: 175px; height: 175px; ' + previewBackground(s)"></div>
</div>
</div>
</div>
<hr>
<button type="button" class="btn btn-primary font-weight-bold btn-block" @click="addId">Add Post</button>
</div>
<div v-if="tab == 'order'">
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['collection-id', 'profile-id'],
data() {
return {
loaded: false,
limit: 8,
step: 1,
title: '',
description: '',
visibility: 'private',
collection: {
title: '',
description: '',
visibility: 'public'
},
id: '',
posts: [],
tab: 'add',
tabs: [
'all',
'add',
'order'
],
recentPosts: [],
selectedPost: '',
}
},
beforeMount() {
axios.get('/api/local/collection/' + this.collectionId)
.then(res => {
this.collection = res.data;
});
},
mounted() {
this.fetchRecentPosts();
this.fetchItems();
},
methods: {
addToIds(id) {
axios.post('/api/local/collection/item', {
collection_id: this.collectionId,
post_id: id
}).then(res => {
this.fetchItems();
this.fetchRecentPosts();
this.tab = 'all';
this.id = '';
}).catch(err => {
swal('Invalid URL', 'The post you entered was invalid', 'error');
this.id = '';
})
},
fetchItems() {
axios.get('/api/local/collection/items/' + this.collectionId)
.then(res => {
this.posts = res.data;
this.loaded = true;
});
},
addId() {
let max = 18;
if(this.posts.length >= max) {
swal('Error', 'You can only add ' + max + ' posts per collection', 'error');
return;
}
let url = this.id;
let origin = window.location.origin;
let split = url.split('/');
if(url.slice(0, origin.length) !== origin) {
swal('Invalid URL', 'You can only add posts from this instance', 'error');
this.id = '';
}
if(url.slice(0, origin.length + 3) !== origin + '/p/' || split.length !== 6) {
swal('Invalid URL', 'Invalid URL', 'error');
this.id = '';
}
this.addToIds(split[5]);
return;
},
previewUrl(status) {
return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url;
},
previewBackground(status) {
let preview = this.previewUrl(status);
return 'background-image: url(' + preview + ');background-size:cover;';
},
fetchRecentPosts() {
axios.get('/api/v1/accounts/' + this.profileId + '/statuses', {
params: {
only_media: true,
min_id: 1,
}
}).then(res => {
this.recentPosts = res.data.filter(s => {
let ids = this.posts.map(s => {
return s.id;
});
return s.visibility == 'public' && s.sensitive == false && ids.indexOf(s.id) == -1;
}).slice(0,3);
});
},
selectPost(status) {
this.selectedPost = status.id;
this.id = status.url;
},
publish() {
axios.post('/api/local/collection/' + this.collectionId + '/publish', {
title: this.collection.title,
description: this.collection.description,
visibility: this.collection.visibility
})
.then(res => {
window.location.href = res.data;
});
},
save() {
axios.post('/api/local/collection/' + this.collectionId, {
title: this.collection.title,
description: this.collection.description,
visibility: this.collection.visibility
})
.then(res => {
swal('Saved!', 'You have successfully saved this collection.', 'success');
});
},
deleteCollection() {
let confirm = window.confirm('Are you sure you want to delete this collection?');
if(!confirm) {
return;
}
axios.delete('/api/local/collection/' + this.collectionId)
.then(res => {
window.location.href = '/';
});
}
}
}
</script>

View file

@ -14,9 +14,9 @@
<span class="fas fa-ellipsis-v fa-lg text-muted"></span> <span class="fas fa-ellipsis-v fa-lg text-muted"></span>
</button> </button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<div v-show="media.length > 0" class="dropdown-item small font-weight-bold" v-on:click="mediaDrawer = !mediaDrawer">{{mediaDrawer ? 'Hide' : 'Show'}} Media Toolbar</div> <div class="dropdown-item small font-weight-bold" v-on:click="createCollection">Create Collection</div>
<div class="dropdown-item small font-weight-bold" v-on:click="about">About</div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<div class="dropdown-item small font-weight-bold" v-on:click="about">About</div>
<div class="dropdown-item small font-weight-bold" v-on:click="closeModal">Close</div> <div class="dropdown-item small font-weight-bold" v-on:click="closeModal">Close</div>
</div> </div>
</div> </div>
@ -507,6 +507,10 @@ export default {
return video ? return video ?
'Click here to add photos or videos' : 'Click here to add photos or videos' :
'Click here to add photos'; 'Click here to add photos';
},
createCollection() {
window.location.href = '/i/collections/create';
} }
} }
} }

View file

@ -152,9 +152,9 @@
<li class="nav-item px-3"> <li class="nav-item px-3">
<a :class="this.mode == 'list' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('list')"><i class="fas fa-th-list fa-lg"></i></a> <a :class="this.mode == 'list' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('list')"><i class="fas fa-th-list fa-lg"></i></a>
</li> </li>
<!-- <li class="nav-item pr-3"> <li class="nav-item pr-3">
<a :class="this.mode == 'collections' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('collections')"><i class="fas fa-images fa-lg"></i></a> <a :class="this.mode == 'collections' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('collections')"><i class="fas fa-images fa-lg"></i></a>
</li> --> </li>
<li class="nav-item" v-if="owner"> <li class="nav-item" v-if="owner">
<a :class="this.mode == 'bookmarks' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('bookmarks')"><i class="fas fa-bookmark fa-lg"></i></a> <a :class="this.mode == 'bookmarks' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('bookmarks')"><i class="fas fa-bookmark fa-lg"></i></a>
</li> </li>
@ -189,7 +189,7 @@
</div> </div>
</div> </div>
<div class="row" v-if="mode == 'list'"> <div class="row" v-if="mode == 'list'">
<div class="col-md-8 col-lg-8 offset-md-2 px-0 mb-3 timeline"> <div class="col-md-8 col-lg-8 offset-md-2 px-0 timeline">
<div class="card status-card card-md-rounded-0 my-sm-2 my-md-3 my-lg-4" :data-status-id="status.id" v-for="(status, index) in timeline" :key="status.id"> <div class="card status-card card-md-rounded-0 my-sm-2 my-md-3 my-lg-4" :data-status-id="status.id" v-for="(status, index) in timeline" :key="status.id">
<div class="card-header d-inline-flex align-items-center bg-white"> <div class="card-header d-inline-flex align-items-center bg-white">
@ -282,6 +282,12 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="['grid','list'].indexOf(mode) != -1 && timeline.length == 0">
<div class="py-5 text-center text-muted">
<p><i class="fas fa-camera-retro fa-2x"></i></p>
<p class="h2 font-weight-light pt-3">No posts yet</p>
</div>
</div>
<div v-if="timeline.length && ['grid','list'].indexOf(mode) != -1"> <div v-if="timeline.length && ['grid','list'].indexOf(mode) != -1">
<infinite-loading @infinite="infiniteTimeline"> <infinite-loading @infinite="infiniteTimeline">
<div slot="no-more"></div> <div slot="no-more"></div>
@ -289,32 +295,55 @@
</infinite-loading> </infinite-loading>
</div> </div>
<div class="row" v-if="mode == 'bookmarks'"> <div class="row" v-if="mode == 'bookmarks'">
<div class="col-4 p-0 p-sm-2 p-md-3 p-xs-1" v-for="(s, index) in bookmarks"> <div v-if="bookmarks.length">
<a class="card info-overlay card-md-border-0" :href="s.url"> <div class="col-4 p-0 p-sm-2 p-md-3 p-xs-1" v-for="(s, index) in bookmarks">
<div class="square"> <a class="card info-overlay card-md-border-0" :href="s.url">
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span> <div class="square">
<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 == '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:album'" class="float-right mr-3 post-icon"><i class="fas fa-film 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>
<div class="square-content" v-bind:style="previewBackground(s)"> <span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
<div class="square-content" v-bind:style="previewBackground(s)">
</div>
<div class="info-overlay-text">
<h5 class="text-white m-auto font-weight-bold">
<span>
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{s.favourites_count}}</span>
</span>
<span>
<span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span>
<span class="d-flex-inline">{{s.reblogs_count}}</span>
</span>
</h5>
</div>
</div> </div>
<div class="info-overlay-text"> </a>
<h5 class="text-white m-auto font-weight-bold"> </div>
<span> </div>
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span> <div v-else class="col-12">
<span class="d-flex-inline">{{s.favourites_count}}</span> <div class="py-5 text-center text-muted">
</span> <p><i class="fas fa-bookmark fa-2x"></i></p>
<span> <p class="h2 font-weight-light pt-3">You have no saved bookmarks</p>
<span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span> </div>
<span class="d-flex-inline">{{s.reblogs_count}}</span>
</span>
</h5>
</div>
</div>
</a>
</div> </div>
</div> </div>
<div class="row" v-if="mode == 'collections'"> <div class="col-12" v-if="mode == 'collections'">
<p class="text-center">Collections here</p> <div v-if="collections.length" class="row">
<div class="col-4 p-0 p-sm-2 p-md-3 p-xs-1" v-for="(c, index) in collections">
<a class="card info-overlay card-md-border-0" :href="c.url">
<div class="square">
<div class="square-content" v-bind:style="'background-image: url(' + c.thumb + ');'">
</div>
</div>
</a>
</div>
</div>
<div v-else>
<div class="py-5 text-center text-muted">
<p><i class="fas fa-images fa-2x"></i></p>
<p class="h2 font-weight-light pt-3">No collections yet</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -688,6 +717,12 @@
this.bookmarks = res.data this.bookmarks = res.data
}); });
} }
if(this.mode == 'collections' && this.collections.length == 0) {
axios.get('/api/local/profile/collections/' + this.profileId)
.then(res => {
this.collections = res.data
});
}
}, },
reportProfile() { reportProfile() {

View file

@ -0,0 +1,24 @@
@extends('layouts.app')
@section('content')
<collection-compose collection-id="{{$collection->id}}" profile-id="{{Auth::user()->profile_id}}"></collection-compose>
@endsection
@push('styles')
<style type="text/css">
</style>
@endpush
@push('scripts')
<script type="text/javascript" src="{{ mix('js/collectioncompose.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
<script type="text/javascript">
$(document).ready(function() {
new Vue({
el: '#content'
});
});
</script>
@endpush

View file

@ -0,0 +1,33 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-12 mt-5 py-5">
<div class="text-center">
<h1>Collection</h1>
<h4 class="text-muted">{{$collection->title}}</h4>
</div>
</div>
<div class="col-12">
<collection-component collection-id="{{$collection->id}}"></collection-component>
</div>
</div>
</div>
@endsection
@push('styles')
<style type="text/css">
</style>
@endpush
@push('scripts')
<script type="text/javascript" src="{{mix('js/compose.js')}}"></script>
<script type="text/javascript" src="{{mix('js/collections.js')}}"></script>
<script type="text/javascript">
new Vue({
el: '#content'
})
</script>
@endpush

View file

@ -115,6 +115,13 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
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');
Route::get('collection/items/{id}', 'CollectionController@getItems');
Route::post('collection/item', 'CollectionController@storeId');
Route::get('collection/{id}', 'CollectionController@get');
Route::post('collection/{id}', 'CollectionController@store');
Route::delete('collection/{id}', 'CollectionController@delete');
Route::post('collection/{id}/publish', 'CollectionController@publish')->middleware('throttle:maxCollectionsPerHour,60')->middleware('throttle:maxCollectionsPerDay,1440')->middleware('throttle:maxCollectionsPerMonth,43800');
Route::get('profile/collections/{id}', 'CollectionController@getUserCollections');
}); });
}); });
@ -167,6 +174,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('abusive/post', 'ReportController@abusivePostForm')->name('report.abusive.post'); Route::get('abusive/post', 'ReportController@abusivePostForm')->name('report.abusive.post');
Route::get('abusive/profile', 'ReportController@abusiveProfileForm')->name('report.abusive.profile'); Route::get('abusive/profile', 'ReportController@abusiveProfileForm')->name('report.abusive.profile');
}); });
Route::get('collections/create', 'CollectionController@create');
}); });
Route::group(['prefix' => 'account'], function () { Route::group(['prefix' => 'account'], function () {
@ -314,6 +323,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('{username}/following', 'FederationController@userFollowing'); Route::get('{username}/following', 'FederationController@userFollowing');
}); });
Route::get('c/{collection}', 'CollectionController@show');
Route::get('p/{username}/{id}/c/{cid}', 'CommentController@show'); Route::get('p/{username}/{id}/c/{cid}', 'CommentController@show');
Route::get('p/{username}/{id}/c', 'CommentController@showAll'); Route::get('p/{username}/{id}/c', 'CommentController@showAll');
Route::get('p/{username}/{id}/edit', 'StatusController@edit'); Route::get('p/{username}/{id}/edit', 'StatusController@edit');

3
webpack.mix.js vendored
View file

@ -31,6 +31,9 @@ mix.js('resources/assets/js/app.js', 'public/js')
// .js('resources/assets/js/embed.js', 'public') // .js('resources/assets/js/embed.js', 'public')
// .js('resources/assets/js/direct.js', 'public/js') // .js('resources/assets/js/direct.js', 'public/js')
.js('resources/assets/js/hashtag.js', 'public/js') .js('resources/assets/js/hashtag.js', 'public/js')
.js('resources/assets/js/collectioncompose.js', 'public/js')
.js('resources/assets/js/collections.js', 'public/js')
.extract([ .extract([
'lodash', 'lodash',
'popper.js', 'popper.js',