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:
commit
b9697d15d6
39 changed files with 3607 additions and 423 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Pixelfed\Snowflake\HasSnowflakePrimary;
|
||||
|
||||
|
@ -16,8 +17,34 @@ class Collection extends Model
|
|||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
public $fillable = ['profile_id', 'published_at'];
|
||||
|
||||
public $dates = ['published_at'];
|
||||
|
||||
public function profile()
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,13 @@ class CollectionItem extends Model
|
|||
{
|
||||
use HasSnowflakePrimary;
|
||||
|
||||
public $fillable = [
|
||||
'collection_id',
|
||||
'object_type',
|
||||
'object_id',
|
||||
'order'
|
||||
];
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
|
|
|
@ -3,8 +3,199 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
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
|
||||
{
|
||||
//
|
||||
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
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -291,4 +291,9 @@ class Profile extends Model
|
|||
{
|
||||
return $this->hasMany(HashtagFollow::class);
|
||||
}
|
||||
|
||||
public function collections()
|
||||
{
|
||||
return $this->hasMany(Collection::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,4 +68,19 @@ trait User {
|
|||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
public function getMaxCollectionsPerHourAttribute()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function getMaxCollectionsPerDayAttribute()
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
public function getMaxCollectionsPerMonthAttribute()
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@
|
|||
"fzaninotto/faker": "^1.4",
|
||||
"mockery/mockery": "^1.0",
|
||||
"nunomaduro/collision": "^2.0",
|
||||
"nunomaduro/phpinsights": "^1.7",
|
||||
"phpunit/phpunit": "^7.5"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
3190
composer.lock
generated
3190
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -150,7 +150,6 @@ return [
|
|||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
|
@ -211,7 +210,6 @@ return [
|
|||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
|
||||
'DotenvEditor' => Jackiedo\DotenvEditor\Facades\DotenvEditor::class,
|
||||
'PrettyNumber' => App\Util\Lexer\PrettyNumber::class,
|
||||
'Purify' => Stevebauman\Purify\Facades\Purify::class,
|
||||
'FFMpeg' => Pbmedia\LaravelFFMpeg\FFMpegFacade::class,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
return [
|
||||
|
||||
'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>')
|
||||
],
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your Pixelfed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.9.6',
|
||||
'version' => '0.10.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM php:7.3-apache
|
||||
FROM php:7.3-apache-buster
|
||||
|
||||
ARG COMPOSER_VERSION="1.8.5"
|
||||
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 git gosu \
|
||||
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 \
|
||||
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \
|
||||
&& locale-gen && update-locale \
|
||||
&& docker-php-source extract \
|
||||
&& 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-xpm-dir=/usr/lib/x86_64-linux-gnu/ \
|
||||
--with-webp-dir=/usr/lib/x86_64-linux-gnu/ \
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
FROM php:7.2-fpm
|
||||
FROM php:7.3-fpm-buster
|
||||
|
||||
ARG COMPOSER_VERSION="1.8.5"
|
||||
ARG COMPOSER_CHECKSUM="4e4c1cd74b54a26618699f3190e6f5fc63bb308b13fa660f71f2a2df047c0e17"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends apt-utils \
|
||||
&& apt-get install -y --no-install-recommends git gosu \
|
||||
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev \
|
||||
libfreetype6 libjpeg62-turbo libpng16-16 libxpm4 libvpx4 libmagickwand-6.q16-3 \
|
||||
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libvpx-dev libmagickwand-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-6 \
|
||||
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev \
|
||||
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \
|
||||
&& locale-gen && update-locale \
|
||||
&& docker-php-source extract \
|
||||
&& 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-xpm-dir=/usr/lib/x86_64-linux-gnu/ \
|
||||
--with-vpx-dir=/usr/lib/x86_64-linux-gnu/ \
|
||||
&& docker-php-ext-install pdo_mysql pdo_pgsql pdo_sqlite pcntl gd exif bcmath intl zip \
|
||||
--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 curl \
|
||||
&& 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 \
|
||||
&& echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \
|
||||
&& chmod 755 /usr/bin/composer \
|
||||
|
@ -32,7 +33,7 @@ ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}"
|
|||
COPY . /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 \
|
||||
&& composer install --prefer-dist --no-interaction \
|
||||
&& rm -rf html && ln -s public html
|
||||
|
|
2
public/js/collectioncompose.js
vendored
2
public/js/collectioncompose.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/collections.js
vendored
Normal file
1
public/js/collections.js
vendored
Normal 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]]]);
|
2
public/js/components.js
vendored
2
public/js/components.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/compose.js
vendored
2
public/js/compose.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/developers.js
vendored
2
public/js/developers.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/discover.js
vendored
2
public/js/discover.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/hashtag.js
vendored
2
public/js/hashtag.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/loops.js
vendored
2
public/js/loops.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/mode-dot.js
vendored
2
public/js/mode-dot.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/profile.js
vendored
2
public/js/profile.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/quill.js
vendored
2
public/js/quill.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/search.js
vendored
2
public/js/search.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/status.js
vendored
2
public/js/status.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/theme-monokai.js
vendored
2
public/js/theme-monokai.js
vendored
|
@ -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]]]);
|
2
public/js/timeline.js
vendored
2
public/js/timeline.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/vendor.js
vendored
2
public/js/vendor.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"/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/activity.js": "/js/activity.js?id=7405cc1a22814a5b2a70",
|
||||
"/js/app.js": "/js/app.js?id=2f034c84c06dbb3e511d",
|
||||
|
@ -8,18 +8,19 @@
|
|||
"/css/appdark.css": "/css/appdark.css?id=1b13fc163fa4deb9233f",
|
||||
"/css/landing.css": "/css/landing.css?id=31de3e75de8690f7ece5",
|
||||
"/css/quill.css": "/css/quill.css?id=81604d62610b0dbffad6",
|
||||
"/js/collectioncompose.js": "/js/collectioncompose.js?id=ec48ebc94ae6b1ec70ea",
|
||||
"/js/components.js": "/js/components.js?id=d64d41a2defb1a205865",
|
||||
"/js/compose.js": "/js/compose.js?id=488b24bf2f2168540449",
|
||||
"/js/developers.js": "/js/developers.js?id=ba029a560f0c30c5efc9",
|
||||
"/js/discover.js": "/js/discover.js?id=627f0a106fd359ae3b80",
|
||||
"/js/hashtag.js": "/js/hashtag.js?id=0f7e529e8128cc17638b",
|
||||
"/js/loops.js": "/js/loops.js?id=19112dc8663fc43db735",
|
||||
"/js/mode-dot.js": "/js/mode-dot.js?id=c7c83849e6bba99f1c33",
|
||||
"/js/profile.js": "/js/profile.js?id=e285b20ac6d467f99138",
|
||||
"/js/quill.js": "/js/quill.js?id=c2b060eaf87ef63eb5c1",
|
||||
"/js/search.js": "/js/search.js?id=3186ccb02c7fad43c701",
|
||||
"/js/status.js": "/js/status.js?id=4b9a52b586880e264f05",
|
||||
"/js/theme-monokai.js": "/js/theme-monokai.js?id=a4da64fc6e2f406a616d",
|
||||
"/js/timeline.js": "/js/timeline.js?id=a3c3e38470108fa5df42"
|
||||
"/js/collectioncompose.js": "/js/collectioncompose.js?id=200765234feeb3b1351c",
|
||||
"/js/collections.js": "/js/collections.js?id=93bac411f11eb701648f",
|
||||
"/js/components.js": "/js/components.js?id=7e4df37c02f12db5ef96",
|
||||
"/js/compose.js": "/js/compose.js?id=df5bd23aef5b73027cce",
|
||||
"/js/developers.js": "/js/developers.js?id=a395f12c52bb0eada6ab",
|
||||
"/js/discover.js": "/js/discover.js?id=f8da29f2b16ae5be93fd",
|
||||
"/js/hashtag.js": "/js/hashtag.js?id=b4ffe6499880acf0591c",
|
||||
"/js/loops.js": "/js/loops.js?id=214f31fc6c2d990487d8",
|
||||
"/js/mode-dot.js": "/js/mode-dot.js?id=8224e306cf53e3336620",
|
||||
"/js/profile.js": "/js/profile.js?id=56aca6209960f8ac2110",
|
||||
"/js/quill.js": "/js/quill.js?id=9edfe94c043a1bc68860",
|
||||
"/js/search.js": "/js/search.js?id=b1bd588d07e682f8fce5",
|
||||
"/js/status.js": "/js/status.js?id=248bb6c2bec534e81503",
|
||||
"/js/theme-monokai.js": "/js/theme-monokai.js?id=344fb8527bb66574e4cd",
|
||||
"/js/timeline.js": "/js/timeline.js?id=b894c6fe644d16ffc330"
|
||||
}
|
||||
|
|
4
resources/assets/js/collectioncompose.js
vendored
Normal file
4
resources/assets/js/collectioncompose.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Vue.component(
|
||||
'collection-compose',
|
||||
require('./components/CollectionCompose.vue').default
|
||||
);
|
4
resources/assets/js/collections.js
vendored
Normal file
4
resources/assets/js/collections.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Vue.component(
|
||||
'collection-component',
|
||||
require('./components/CollectionComponent.vue').default
|
||||
);
|
69
resources/assets/js/components/CollectionComponent.vue
Normal file
69
resources/assets/js/components/CollectionComponent.vue
Normal 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>
|
257
resources/assets/js/components/CollectionCompose.vue
Normal file
257
resources/assets/js/components/CollectionCompose.vue
Normal 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>
|
|
@ -14,9 +14,9 @@
|
|||
<span class="fas fa-ellipsis-v fa-lg text-muted"></span>
|
||||
</button>
|
||||
<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="about">About</div>
|
||||
<div class="dropdown-item small font-weight-bold" v-on:click="createCollection">Create Collection</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>
|
||||
</div>
|
||||
|
@ -507,6 +507,10 @@ export default {
|
|||
return video ?
|
||||
'Click here to add photos or videos' :
|
||||
'Click here to add photos';
|
||||
},
|
||||
|
||||
createCollection() {
|
||||
window.location.href = '/i/collections/create';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,9 +152,9 @@
|
|||
<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>
|
||||
</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>
|
||||
</li> -->
|
||||
</li>
|
||||
<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>
|
||||
</li>
|
||||
|
@ -189,7 +189,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<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-header d-inline-flex align-items-center bg-white">
|
||||
|
@ -282,6 +282,12 @@
|
|||
</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">
|
||||
<infinite-loading @infinite="infiniteTimeline">
|
||||
<div slot="no-more"></div>
|
||||
|
@ -289,6 +295,7 @@
|
|||
</infinite-loading>
|
||||
</div>
|
||||
<div class="row" v-if="mode == 'bookmarks'">
|
||||
<div v-if="bookmarks.length">
|
||||
<div class="col-4 p-0 p-sm-2 p-md-3 p-xs-1" v-for="(s, index) in bookmarks">
|
||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||
<div class="square">
|
||||
|
@ -313,8 +320,30 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="mode == 'collections'">
|
||||
<p class="text-center">Collections here</p>
|
||||
<div v-else class="col-12">
|
||||
<div class="py-5 text-center text-muted">
|
||||
<p><i class="fas fa-bookmark fa-2x"></i></p>
|
||||
<p class="h2 font-weight-light pt-3">You have no saved bookmarks</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12" v-if="mode == 'collections'">
|
||||
<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>
|
||||
|
@ -688,6 +717,12 @@
|
|||
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() {
|
||||
|
|
24
resources/views/collection/create.blade.php
Normal file
24
resources/views/collection/create.blade.php
Normal 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
|
33
resources/views/collection/show.blade.php
Normal file
33
resources/views/collection/show.blade.php
Normal 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
|
|
@ -115,6 +115,13 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('discover/tag/list', 'HashtagFollowController@getTags');
|
||||
Route::get('profile/sponsor/{id}', 'ProfileSponsorController@get');
|
||||
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/profile', 'ReportController@abusiveProfileForm')->name('report.abusive.profile');
|
||||
});
|
||||
|
||||
Route::get('collections/create', 'CollectionController@create');
|
||||
});
|
||||
|
||||
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('c/{collection}', 'CollectionController@show');
|
||||
Route::get('p/{username}/{id}/c/{cid}', 'CommentController@show');
|
||||
Route::get('p/{username}/{id}/c', 'CommentController@showAll');
|
||||
Route::get('p/{username}/{id}/edit', 'StatusController@edit');
|
||||
|
|
3
webpack.mix.js
vendored
3
webpack.mix.js
vendored
|
@ -31,6 +31,9 @@ mix.js('resources/assets/js/app.js', 'public/js')
|
|||
// .js('resources/assets/js/embed.js', 'public')
|
||||
// .js('resources/assets/js/direct.js', 'public/js')
|
||||
.js('resources/assets/js/hashtag.js', 'public/js')
|
||||
.js('resources/assets/js/collectioncompose.js', 'public/js')
|
||||
.js('resources/assets/js/collections.js', 'public/js')
|
||||
|
||||
.extract([
|
||||
'lodash',
|
||||
'popper.js',
|
||||
|
|
Loading…
Add table
Reference in a new issue