mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 14:37:59 +01:00
Merge pull request #92 from Poniverse/native-notifications
Native notifications
This commit is contained in:
commit
a9e7a69268
22 changed files with 988 additions and 22 deletions
|
@ -21,10 +21,13 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Models\Notification;
|
||||
use Poniverse\Ponyfm\Models\Subscription;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Minishlink\WebPush\WebPush;
|
||||
|
||||
class NotificationsController extends ApiControllerBase
|
||||
{
|
||||
|
@ -59,4 +62,52 @@ class NotificationsController extends ApiControllerBase
|
|||
|
||||
return ['notifications_updated' => $numberOfUpdatedRows];
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe a user to native push notifications. Takes an endpoint and
|
||||
* encryption keys from the client and stores them in the database
|
||||
* for future use.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function postSubscribe()
|
||||
{
|
||||
$input = json_decode(Input::json('subscription'));
|
||||
if ($input != 'null') {
|
||||
$existing = Subscription::where('endpoint', '=', $input->endpoint)
|
||||
->where('user_id', '=', Auth::user()->id)
|
||||
->first();
|
||||
|
||||
if ($existing === null) {
|
||||
$subscription = Subscription::create([
|
||||
'user_id' => Auth::user()->id,
|
||||
'endpoint' => $input->endpoint,
|
||||
'p256dh' => $input->keys->p256dh,
|
||||
'auth' => $input->keys->auth
|
||||
]);
|
||||
|
||||
return ['id' => $subscription->id];
|
||||
} else {
|
||||
return ['id' => $existing->id];
|
||||
}
|
||||
} else {
|
||||
return ['error' => 'No data'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user's notification subscription
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function postUnsubscribe()
|
||||
{
|
||||
$input = json_decode(Input::json('subscription'));
|
||||
|
||||
$existing = Subscription::where('endpoint', '=', $input->endpoint)
|
||||
->where('user_id', '=', Auth::user()->id)
|
||||
->delete();
|
||||
|
||||
return ['result' => 'success'];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,8 +134,11 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
|
||||
Route::post('/dashboard/read-news', 'Api\Web\DashboardController@postReadNews');
|
||||
Route::get('/account/settings/{slug}', 'Api\Web\AccountController@getSettings');
|
||||
|
||||
Route::get('/notifications', 'Api\Web\NotificationsController@getNotifications');
|
||||
Route::put('/notifications/mark-as-read', 'Api\Web\NotificationsController@putMarkAsRead');
|
||||
Route::post('/notifications/subscribe', 'Api\Web\NotificationsController@postSubscribe');
|
||||
Route::post('/notifications/unsubscribe', 'Api\Web\NotificationsController@postUnsubscribe');
|
||||
|
||||
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||
use Poniverse\Ponyfm\Jobs\Job;
|
||||
use Illuminate\Contracts\Bus\SelfHandling;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\AbstractDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use SerializesModels;
|
||||
|
@ -60,7 +61,8 @@ class SendNotifications extends Job implements SelfHandling, ShouldQueue
|
|||
// to work around a Laravel bug - namely, the SerializesModels trait
|
||||
// tries (and fails) to serialize static fields.
|
||||
$drivers = [
|
||||
PonyfmDriver::class
|
||||
PonyfmDriver::class,
|
||||
NativeDriver::class
|
||||
];
|
||||
|
||||
foreach ($drivers as $driver) {
|
||||
|
|
129
app/Library/Notifications/Drivers/NativeDriver.php
Normal file
129
app/Library/Notifications/Drivers/NativeDriver.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Josef Citrine
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Library\Notifications\Drivers;
|
||||
|
||||
|
||||
use Config;
|
||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||
use Poniverse\Ponyfm\Models\Activity;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Minishlink\WebPush\WebPush;
|
||||
|
||||
class NativeDriver extends AbstractDriver {
|
||||
/**
|
||||
* Method for sending notifications to devices
|
||||
*
|
||||
* @param Activity $activity
|
||||
* @param User[] $recipients collection of {@link User} objects
|
||||
*/
|
||||
private function pushNotifications(Activity $activity, $recipients)
|
||||
{
|
||||
if (Config::get('ponyfm.gcm_key') != 'default') {
|
||||
$apiKeys = array(
|
||||
'GCM' => Config::get('ponyfm.gcm_key'),
|
||||
);
|
||||
|
||||
$webPush = new WebPush($apiKeys);
|
||||
|
||||
$data = [
|
||||
'id' => $activity->id,
|
||||
'text' => $activity->getTextAttribute(),
|
||||
'title' => $activity->getTitleFromActivityType(),
|
||||
'image' => $activity->getThumbnailUrlAttribute(),
|
||||
'url' => $activity->url
|
||||
];
|
||||
|
||||
$jsonData = json_encode($data);
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$webPush->sendNotification(
|
||||
$recipient->endpoint,
|
||||
$jsonData,
|
||||
$recipient->p256dh,
|
||||
$recipient->auth
|
||||
);
|
||||
}
|
||||
|
||||
$webPush->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function publishedNewTrack(Track $track) {
|
||||
$activity = Activity::where('user_id', $track->user_id)
|
||||
->where('activity_type', Activity::TYPE_PUBLISHED_TRACK)
|
||||
->where('resource_id', $track->id)
|
||||
->get()[0];
|
||||
|
||||
|
||||
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function publishedNewPlaylist(Playlist $playlist) {
|
||||
$activity = Activity::where('user_id', $playlist->user_id)
|
||||
->where('activity_type', Activity::TYPE_PUBLISHED_PLAYLIST)
|
||||
->where('resource_id', $playlist->id)
|
||||
->get()[0];
|
||||
|
||||
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
public function newFollower(User $userBeingFollowed, User $follower) {
|
||||
$activity = Activity::where('user_id', $follower->id)
|
||||
->where('activity_type', Activity::TYPE_NEW_FOLLOWER)
|
||||
->where('resource_id', $userBeingFollowed->id)
|
||||
->get()[0];
|
||||
|
||||
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function newComment(Comment $comment) {
|
||||
$activity = Activity::where('user_id', $comment->user_id)
|
||||
->where('activity_type', Activity::TYPE_NEW_COMMENT)
|
||||
->where('resource_id', $comment->id)
|
||||
->get()[0];
|
||||
|
||||
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function newFavourite(Favouritable $entityBeingFavourited, User $favouriter) {
|
||||
$activity = Activity::where('user_id', $favouriter->id)
|
||||
->where('activity_type', Activity::TYPE_CONTENT_FAVOURITED)
|
||||
->where('resource_id', $entityBeingFavourited->id)
|
||||
->get()[0];
|
||||
|
||||
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||
}
|
||||
}
|
|
@ -25,9 +25,11 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
|
|||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||
use Poniverse\Ponyfm\Contracts\NotificationHandler;
|
||||
use Poniverse\Ponyfm\Jobs\SendNotifications;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver;
|
||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Subscription;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
|
@ -59,6 +61,21 @@ class RecipientFinder implements NotificationHandler {
|
|||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return $track->user->followers;
|
||||
case NativeDriver::class:
|
||||
$followerIds = [];
|
||||
$subIds = [];
|
||||
$rawSubIds = Subscription::select('id')->get();
|
||||
|
||||
foreach ($track->user->followers as $follower) {
|
||||
array_push($followerIds, $follower->id);
|
||||
}
|
||||
|
||||
foreach ($rawSubIds as $sub) {
|
||||
array_push($subIds, $sub->id);
|
||||
}
|
||||
|
||||
$targetIds = array_intersect($followerIds, $subIds);
|
||||
return Subscription::whereIn('user_id', $targetIds)->get();
|
||||
default:
|
||||
return $this->fail();
|
||||
}
|
||||
|
@ -71,6 +88,21 @@ class RecipientFinder implements NotificationHandler {
|
|||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return $playlist->user->followers;
|
||||
case NativeDriver::class:
|
||||
$followerIds = [];
|
||||
$subIds = [];
|
||||
$rawSubIds = Subscription::select('id')->get();
|
||||
|
||||
foreach ($playlist->user->followers as $follower) {
|
||||
array_push($followerIds, $follower->id);
|
||||
}
|
||||
|
||||
foreach ($rawSubIds as $sub) {
|
||||
array_push($subIds, $sub->id);
|
||||
}
|
||||
|
||||
$targetIds = array_intersect($followerIds, $subIds);
|
||||
return Subscription::whereIn('user_id', $targetIds)->get();
|
||||
default:
|
||||
return $this->fail();
|
||||
}
|
||||
|
@ -83,6 +115,8 @@ class RecipientFinder implements NotificationHandler {
|
|||
switch ($this->notificationDriver) {
|
||||
case PonyfmDriver::class:
|
||||
return [$userBeingFollowed];
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $userBeingFollowed->id)->get();
|
||||
default:
|
||||
return $this->fail();
|
||||
}
|
||||
|
@ -98,6 +132,8 @@ class RecipientFinder implements NotificationHandler {
|
|||
$comment->user->id === $comment->resource->user->id
|
||||
? []
|
||||
: [$comment->resource->user];
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $comment->resource->user->id)->get();
|
||||
default:
|
||||
return $this->fail();
|
||||
}
|
||||
|
@ -113,6 +149,8 @@ class RecipientFinder implements NotificationHandler {
|
|||
$favouriter->id === $entityBeingFavourited->user->id
|
||||
? []
|
||||
: [$entityBeingFavourited->user];
|
||||
case NativeDriver::class:
|
||||
return Subscription::where('user_id', '=', $entityBeingFavourited->user->id)->get();
|
||||
default:
|
||||
return $this->fail();
|
||||
}
|
||||
|
|
|
@ -165,6 +165,24 @@ class Activity extends Model {
|
|||
}
|
||||
}
|
||||
|
||||
public function getTitleFromActivityType() {
|
||||
switch($this->activity_type) {
|
||||
case static::TYPE_PUBLISHED_TRACK:
|
||||
return "Pony.fm - New track";
|
||||
case static::TYPE_PUBLISHED_PLAYLIST:
|
||||
return "Pony.fm - New playlist";
|
||||
case static::TYPE_NEW_FOLLOWER:
|
||||
return "Pony.fm - New follower";
|
||||
case static::TYPE_NEW_COMMENT:
|
||||
return "Pony.fm - New comment";
|
||||
case static::TYPE_CONTENT_FAVOURITED:
|
||||
return "Pony.fm - Favourited";
|
||||
|
||||
default:
|
||||
return "Pony.fm - Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string human-readable Markdown string describing this notification
|
||||
* @throws \Exception
|
||||
|
|
49
app/Models/Subscription.php
Normal file
49
app/Models/Subscription.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Josef Citrine
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Subscription
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property string $endpoint
|
||||
* @property string $p256dh
|
||||
* @property string $auth
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
*/
|
||||
class Subscription extends Model {
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['user_id', 'endpoint', 'p256dh', 'auth'];
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'endpoint' => 'string',
|
||||
'p256dh' => 'string',
|
||||
'auth' => 'string'
|
||||
];
|
||||
|
||||
public function user() {
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
}
|
|
@ -18,7 +18,8 @@
|
|||
"barryvdh/laravel-debugbar": "^2.2",
|
||||
"predis/predis": "^1.0",
|
||||
"ksubileau/color-thief-php": "^1.3",
|
||||
"graham-campbell/exceptions": "^8.6"
|
||||
"graham-campbell/exceptions": "^8.6",
|
||||
"minishlink/web-push": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fzaninotto/faker": "~1.4",
|
||||
|
|
378
composer.lock
generated
378
composer.lock
generated
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "5c9f86045c835b8964e1eb198a7727ea",
|
||||
"content-hash": "a09f15b4c222efb8e676a4efdd979b4f",
|
||||
"hash": "070553e4e21387213808a4cb779e5f16",
|
||||
"content-hash": "98c97b7ca37abf031e353edaf4ac2ae3",
|
||||
"packages": [
|
||||
{
|
||||
"name": "barryvdh/laravel-debugbar",
|
||||
|
@ -124,6 +124,59 @@
|
|||
],
|
||||
"time": "2016-03-03 08:45:00"
|
||||
},
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
"version": "v2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/beberlei/assert.git",
|
||||
"reference": "91e2690c4ecc8a4e3e2d333430069f6a0c694a7a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/beberlei/assert/zipball/91e2690c4ecc8a4e3e2d333430069f6a0c694a7a",
|
||||
"reference": "91e2690c4ecc8a4e3e2d333430069f6a0c694a7a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "@stable"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Assert": "lib/"
|
||||
},
|
||||
"files": [
|
||||
"lib/Assert/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
}
|
||||
],
|
||||
"description": "Thin assertion library for input validation in business models.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"assertion",
|
||||
"validation"
|
||||
],
|
||||
"time": "2016-03-22 14:34:51"
|
||||
},
|
||||
{
|
||||
"name": "classpreloader/classpreloader",
|
||||
"version": "3.0.0",
|
||||
|
@ -883,6 +936,58 @@
|
|||
],
|
||||
"time": "2016-03-18 16:31:37"
|
||||
},
|
||||
{
|
||||
"name": "fgrosse/phpasn1",
|
||||
"version": "1.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fgrosse/PHPASN1.git",
|
||||
"reference": "ee6d1abd18f8bcbaf0b55563ba87e5ed16cd0c98"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/ee6d1abd18f8bcbaf0b55563ba87e5ed16cd0c98",
|
||||
"reference": "ee6d1abd18f8bcbaf0b55563ba87e5ed16cd0c98",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gmp": "*",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FG\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Friedrich Große",
|
||||
"email": "friedrich.grosse@gmail.com",
|
||||
"homepage": "https://github.com/FGrosse",
|
||||
"role": "Author"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/FGrosse/PHPASN1/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
|
||||
"homepage": "https://github.com/FGrosse/PHPASN1",
|
||||
"time": "2015-07-15 21:26:40"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/exceptions",
|
||||
"version": "v8.6.1",
|
||||
|
@ -1454,6 +1559,54 @@
|
|||
],
|
||||
"time": "2015-12-05 17:17:57"
|
||||
},
|
||||
{
|
||||
"name": "kriswallsmith/buzz",
|
||||
"version": "v0.15",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/kriswallsmith/Buzz.git",
|
||||
"reference": "d4041666c3ffb379af02a92dabe81c904b35fab8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/kriswallsmith/Buzz/zipball/d4041666c3ffb379af02a92dabe81c904b35fab8",
|
||||
"reference": "d4041666c3ffb379af02a92dabe81c904b35fab8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "3.7.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Buzz": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kris Wallsmith",
|
||||
"email": "kris.wallsmith@gmail.com",
|
||||
"homepage": "http://kriswallsmith.net/"
|
||||
}
|
||||
],
|
||||
"description": "Lightweight HTTP client",
|
||||
"homepage": "https://github.com/kriswallsmith/Buzz",
|
||||
"keywords": [
|
||||
"curl",
|
||||
"http client"
|
||||
],
|
||||
"time": "2015-06-25 17:26:56"
|
||||
},
|
||||
{
|
||||
"name": "ksubileau/color-thief-php",
|
||||
"version": "v1.3.0",
|
||||
|
@ -1780,6 +1933,118 @@
|
|||
],
|
||||
"time": "2016-01-22 12:22:23"
|
||||
},
|
||||
{
|
||||
"name": "mdanter/ecc",
|
||||
"version": "v0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpecc/phpecc.git",
|
||||
"reference": "8b588fc094ba743d8f8c84980bcc6b470c4baed7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpecc/phpecc/zipball/8b588fc094ba743d8f8c84980bcc6b470c4baed7",
|
||||
"reference": "8b588fc094ba743d8f8c84980bcc6b470c4baed7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gmp": "*",
|
||||
"ext-mcrypt": "*",
|
||||
"fgrosse/phpasn1": "~1.3.1",
|
||||
"php": ">=5.4.0",
|
||||
"symfony/console": "~2.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.1",
|
||||
"squizlabs/php_codesniffer": "~2",
|
||||
"symfony/yaml": "~2.6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mdanter\\Ecc\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matyas Danter",
|
||||
"homepage": "http://matejdanter.com/",
|
||||
"role": "Author"
|
||||
},
|
||||
{
|
||||
"name": "Thibaud Fabre",
|
||||
"email": "thibaud@aztech.io",
|
||||
"homepage": "http://aztech.io",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Drak",
|
||||
"email": "drak@zikula.org",
|
||||
"homepage": "http://zikula.org",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "PHP Elliptic Curve Cryptography library",
|
||||
"homepage": "https://github.com/mdanter/phpecc",
|
||||
"time": "2014-07-07 12:44:15"
|
||||
},
|
||||
{
|
||||
"name": "minishlink/web-push",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Minishlink/web-push.git",
|
||||
"reference": "ad407ca84e87595760ff87a836a55ad107fa8245"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Minishlink/web-push/zipball/ad407ca84e87595760ff87a836a55ad107fa8245",
|
||||
"reference": "ad407ca84e87595760ff87a836a55ad107fa8245",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"kriswallsmith/buzz": ">=0.6",
|
||||
"lib-openssl": "*",
|
||||
"mdanter/ecc": "^0.3.0",
|
||||
"php": ">=5.4",
|
||||
"spomky-labs/base64url": "^1.0",
|
||||
"spomky-labs/php-aes-gcm": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.8.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Minishlink\\WebPush\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Louis Lagrange",
|
||||
"email": "lagrange.louis@gmail.com",
|
||||
"homepage": "https://github.com/Minishlink"
|
||||
}
|
||||
],
|
||||
"description": "Web Push library for PHP",
|
||||
"homepage": "https://github.com/Minishlink/web-push",
|
||||
"keywords": [
|
||||
"Push API",
|
||||
"WebPush",
|
||||
"notifications",
|
||||
"push",
|
||||
"web"
|
||||
],
|
||||
"time": "2016-05-13 21:21:54"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "1.19.0",
|
||||
|
@ -2400,6 +2665,115 @@
|
|||
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||
"time": "2016-05-03 17:50:52"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/base64url",
|
||||
"version": "v1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/base64url.git",
|
||||
"reference": "ef6d5fb93894063d9cee996022259fd08d6646ea"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/ef6d5fb93894063d9cee996022259fd08d6646ea",
|
||||
"reference": "ef6d5fb93894063d9cee996022259fd08d6646ea",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.0|^5.0",
|
||||
"satooshi/php-coveralls": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Base64Url\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky-Labs/base64url/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Base 64 URL Safe Encoding/decoding PHP Library",
|
||||
"homepage": "https://github.com/Spomky-Labs/base64url",
|
||||
"keywords": [
|
||||
"base64",
|
||||
"rfc4648",
|
||||
"safe",
|
||||
"url"
|
||||
],
|
||||
"time": "2016-01-21 19:50:30"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/php-aes-gcm",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/php-aes-gcm.git",
|
||||
"reference": "7d415c6eeb5133804a38451d6ed93b5af76872ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/php-aes-gcm/zipball/7d415c6eeb5133804a38451d6ed93b5af76872ce",
|
||||
"reference": "7d415c6eeb5133804a38451d6ed93b5af76872ce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"beberlei/assert": "^2.0",
|
||||
"lib-openssl": "*",
|
||||
"php": ">=5.4",
|
||||
"symfony/polyfill-mbstring": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.5|^5.0",
|
||||
"satooshi/php-coveralls": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"AESGCM\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/Spomky-Labs/php-aes-gcm/contributors"
|
||||
}
|
||||
],
|
||||
"description": "AES GCM (Galois Counter Mode) PHP implementation.",
|
||||
"homepage": "https://github.com/Spomky-Labs/php-aes-gcm",
|
||||
"keywords": [
|
||||
"Galois Counter Mode",
|
||||
"gcm"
|
||||
],
|
||||
"time": "2016-04-28 13:58:02"
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v5.4.2",
|
||||
|
|
|
@ -115,5 +115,18 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'user_slug_minimum_length' => 3
|
||||
'user_slug_minimum_length' => 3,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Indexing queue name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Google Cloud Messaging API key. Needs to be generated in the Google Cloud
|
||||
| Console as a browser key. This is used to send notifications to users
|
||||
| with push notifications enabled.
|
||||
|
|
||||
*/
|
||||
|
||||
'gcm_key' => env('GCM_KEY', 'default'),
|
||||
];
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateSubscriptionTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('subscriptions', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedInteger('user_id')->index();
|
||||
$table->string('endpoint');
|
||||
$table->string('p256dh');
|
||||
$table->string('auth');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('subscriptions');
|
||||
}
|
||||
}
|
|
@ -22,5 +22,6 @@
|
|||
"background_color": "#EEE",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
"orientation": "portrait",
|
||||
"gcm_sender_id": "628116355343"
|
||||
}
|
||||
|
|
|
@ -23,6 +23,14 @@ var urlsToCache = [
|
|||
|
||||
var CACHE_NAME = 'pfm-offline-v1';
|
||||
|
||||
var notifUrlCache = {};
|
||||
|
||||
function getChromeVersion () {
|
||||
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
|
||||
return raw ? parseInt(raw[2], 10) : false;
|
||||
}
|
||||
|
||||
// Set the callback for the install step
|
||||
self.addEventListener('install', function(event) {
|
||||
// Perform install steps
|
||||
|
@ -48,8 +56,12 @@ self.addEventListener('activate', function (event) {
|
|||
// Basic offline mode
|
||||
// Just respond with an offline error page for now
|
||||
self.addEventListener('fetch', function(event) {
|
||||
if (event.request.url.indexOf('stage.pony.fm') > -1 || event.request.url.indexOf('upload') > -1) {
|
||||
// Ignore some requests
|
||||
return;
|
||||
} else {
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(function(response) {
|
||||
caches.match(event.request).then(function (response) {
|
||||
return response || fetch(event.request);
|
||||
}).catch(function () {
|
||||
if (event.request.mode == 'navigate') {
|
||||
|
@ -57,4 +69,45 @@ self.addEventListener('fetch', function(event) {
|
|||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('push', function(event) {
|
||||
console.log(event);
|
||||
var data = {};
|
||||
|
||||
if (event.data) {
|
||||
console.log(event.data.json());
|
||||
data = JSON.parse(event.data.text());
|
||||
}
|
||||
|
||||
notifUrlCache['pfm-' + data.id] = data.url;
|
||||
|
||||
self.registration.showNotification(data.title, {
|
||||
body: data.text,
|
||||
icon: data.image,
|
||||
tag: 'pfm-' + data.id
|
||||
})
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
event.notification.close();
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({
|
||||
type: "window"
|
||||
})
|
||||
.then(function(clientList) {
|
||||
var url = notifUrlCache[event.notification.tag];
|
||||
for (var i = 0; i < clientList.length; i++) {
|
||||
var client = clientList[i];
|
||||
if (client.url == url && 'focus' in client)
|
||||
return client.focus();
|
||||
}
|
||||
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(url);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
|
@ -1,7 +1,14 @@
|
|||
<div class="notif-list">
|
||||
<div class="notif-switch" ng-hide="switchHidden">
|
||||
<input id="test" type="checkbox" hidden="hidden" ng-model="subscribed" ng-change="switchToggled()" ng-disabled="switchDisabled"/>
|
||||
<label for="test" class="switch" ng-class="{'disabled': switchDisabled}"></label>
|
||||
<span>Enable push notifications</span>
|
||||
</div>
|
||||
<div class="notif-scroll">
|
||||
<div ng-repeat="notification in notifications" class="notification" ng-class="{'unread': !notification.is_read}">
|
||||
<a href="{{ ::notification.url }}" class="img-link"><img pfm-src-loader="::notification.thumbnail_url" pfm-src-size="thumbnail"></a>
|
||||
<a href="{{ ::notification.url }}" class="message"><p>{{ ::notification.text }}</p></a>
|
||||
</div>
|
||||
<p ng-show="notifications.length < 1" class="error">No notifications :(</p>
|
||||
</div>
|
||||
</div>
|
|
@ -33,6 +33,7 @@ module.exports = angular.module('ponyfm').controller "application", [
|
|||
console.log 'SW registered', reg
|
||||
).catch (err) ->
|
||||
console.log 'SW register failed', err
|
||||
notifications.serviceWorkerSupported = false
|
||||
|
||||
$scope.menuToggle = () ->
|
||||
$rootScope.$broadcast('sidebarToggled')
|
||||
|
|
|
@ -21,9 +21,12 @@ module.exports = angular.module('ponyfm').directive 'pfmNotificationList', () ->
|
|||
scope: {}
|
||||
|
||||
controller: [
|
||||
'$scope', 'notifications', '$timeout', '$rootScope'
|
||||
($scope, notifications, $timeout, $rootScope) ->
|
||||
'$scope', 'notifications', '$timeout', '$rootScope', '$http'
|
||||
($scope, notifications, $timeout, $rootScope, $http) ->
|
||||
$scope.notifications = []
|
||||
$scope.subscribed = false
|
||||
$scope.switchDisabled = true
|
||||
$scope.switchHidden = false
|
||||
isTimeoutScheduled = false
|
||||
|
||||
# TODO: ADD REFRESH BUTTON
|
||||
|
@ -31,6 +34,37 @@ module.exports = angular.module('ponyfm').directive 'pfmNotificationList', () ->
|
|||
$rootScope.$on 'shouldUpdateNotifications', () ->
|
||||
refreshNotifications()
|
||||
|
||||
$scope.switchToggled = () ->
|
||||
if $scope.subscribed
|
||||
$scope.switchDisabled = true
|
||||
notifications.subscribe().done (result) ->
|
||||
if result
|
||||
$scope.switchDisabled = false
|
||||
else
|
||||
$scope.switchDisabled = true
|
||||
notifications.unsubscribe().done (result) ->
|
||||
if result
|
||||
$scope.switchDisabled = false
|
||||
|
||||
|
||||
checkSubscription = () ->
|
||||
if 'serviceWorker' of navigator && notifications.serviceWorkerSupported
|
||||
$scope.disabled = true
|
||||
notifications.checkSubscription().done (subStatus) ->
|
||||
switch subStatus
|
||||
when 0
|
||||
$scope.subscribed = false
|
||||
$scope.switchDisabled = false
|
||||
when 1
|
||||
$scope.subscribed = true
|
||||
$scope.switchDisabled = false
|
||||
else
|
||||
$scope.subscribed = false
|
||||
$scope.switchDisabled = true
|
||||
$scope.hidden = true
|
||||
else
|
||||
$scope.switchHidden = true
|
||||
|
||||
refreshNotifications = () ->
|
||||
notifications.getNotifications().done (result) ->
|
||||
if $scope.notifications.length > 0
|
||||
|
@ -51,5 +85,6 @@ module.exports = angular.module('ponyfm').directive 'pfmNotificationList', () ->
|
|||
isTimeoutScheduled = false
|
||||
, 60000)
|
||||
|
||||
checkSubscription()
|
||||
refreshNotifications()
|
||||
]
|
||||
|
|
|
@ -19,6 +19,7 @@ module.exports = angular.module('ponyfm').factory('notifications', [
|
|||
($rootScope, $http) ->
|
||||
self =
|
||||
notificationList: []
|
||||
serviceWorkerSupported: true
|
||||
|
||||
getNotifications: () ->
|
||||
def = new $.Deferred()
|
||||
|
@ -61,5 +62,95 @@ module.exports = angular.module('ponyfm').factory('notifications', [
|
|||
else
|
||||
return 0
|
||||
|
||||
subscribe: () ->
|
||||
def = new $.Deferred()
|
||||
navigator.serviceWorker.ready.then (reg) ->
|
||||
reg.pushManager.subscribe({userVisibleOnly: true}).then (sub) ->
|
||||
console.log 'Push sub', JSON.stringify(sub)
|
||||
self.sendSubscriptionToServer(sub).done (result) ->
|
||||
def.resolve result
|
||||
|
||||
def.promise()
|
||||
|
||||
unsubscribe: () ->
|
||||
def = new $.Deferred()
|
||||
navigator.serviceWorker.ready.then (reg) ->
|
||||
reg.pushManager.getSubscription().then (sub) ->
|
||||
sub.unsubscribe().then (result) ->
|
||||
self.removeSubscriptionFromServer(sub).done (result) ->
|
||||
def.resolve true
|
||||
.catch (e) ->
|
||||
console.warn('Unsubscription error: ', e)
|
||||
def.resolve false
|
||||
|
||||
def.promise()
|
||||
|
||||
sendSubscriptionToServer: (sub) ->
|
||||
def = new $.Deferred()
|
||||
subData = JSON.stringify(sub)
|
||||
$http.post('/api/web/notifications/subscribe', {subscription: subData}).success () ->
|
||||
def.resolve true
|
||||
.error () ->
|
||||
def.resolve false
|
||||
|
||||
def.promise()
|
||||
|
||||
removeSubscriptionFromServer: (sub) ->
|
||||
def = new $.Deferred()
|
||||
subData = JSON.stringify(sub)
|
||||
$http.post('/api/web/notifications/unsubscribe', {subscription: subData}).success () ->
|
||||
def.resolve true
|
||||
.error () ->
|
||||
def.resolve false
|
||||
|
||||
def.promise()
|
||||
|
||||
checkSubscription: () ->
|
||||
def = new $.Deferred()
|
||||
|
||||
if 'serviceWorker' of navigator
|
||||
if !self.checkPushSupport()
|
||||
def.resolve -1
|
||||
|
||||
navigator.serviceWorker.ready.then (reg) ->
|
||||
reg.pushManager.getSubscription().then (sub) ->
|
||||
if !sub
|
||||
def.resolve 0
|
||||
|
||||
self.sendSubscriptionToServer(sub)
|
||||
|
||||
def.resolve 1
|
||||
|
||||
|
||||
else
|
||||
console.warn('Service worker isn\'t supported.')
|
||||
def.resolve -1
|
||||
|
||||
def.promise()
|
||||
|
||||
checkPushSupport: () ->
|
||||
if !('showNotification' of ServiceWorkerRegistration.prototype)
|
||||
console.warn('Notifications aren\'t supported.')
|
||||
return false
|
||||
|
||||
if Notification.permission == 'denied'
|
||||
console.warn('The user has blocked notifications.')
|
||||
return false
|
||||
|
||||
if !('PushManager' of window)
|
||||
console.warn('Push messaging isn\'t supported.')
|
||||
return false
|
||||
|
||||
# If Chrome 50+
|
||||
if !!window.chrome && !!window.chrome.webstore
|
||||
if parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]) >= 50
|
||||
return true
|
||||
# If Firefox 46+
|
||||
else if typeof InstallTrigger != 'undefined'
|
||||
if parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1]) >= 46
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
self
|
||||
])
|
||||
|
|
8
resources/assets/styles/content.less
vendored
8
resources/assets/styles/content.less
vendored
|
@ -626,6 +626,14 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
.notif-switch {
|
||||
margin-bottom: 15px;
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.notif-list {
|
||||
.error {
|
||||
text-align: center;
|
||||
|
|
52
resources/assets/styles/forms.less
vendored
52
resources/assets/styles/forms.less
vendored
|
@ -121,3 +121,55 @@ label {
|
|||
textarea {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.switch {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
background: rgba(0,0,0,0.26);
|
||||
-webkit-transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #fafafa;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.28);
|
||||
border-radius: 50%;
|
||||
transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
&:active::before {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.28), 0 0 0 20px rgba(128,128,128,0.1);
|
||||
}
|
||||
}
|
||||
|
||||
input:checked + .switch {
|
||||
background: rgba(132,82,138,0.5);
|
||||
}
|
||||
|
||||
input:checked + .switch::before {
|
||||
left: 20px;
|
||||
background: #84528a;
|
||||
}
|
||||
|
||||
input:checked + .switch:active::before {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.28), 0 0 0 20px rgba(0,150,136,0.2);
|
||||
}
|
||||
|
||||
.switch.disabled {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
|
||||
.switch.disabled::before {
|
||||
background: #dadada;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,3 +24,4 @@ PONI_CLIENT_SECRET=null
|
|||
PONYFM_DATASTORE=/vagrant/storage/app/datastore
|
||||
|
||||
GOOGLE_ANALYTICS_ID=null
|
||||
GCM_KEY=12345
|
||||
|
|
|
@ -24,6 +24,9 @@ sudo apt-get -qq update
|
|||
echo "Installing tagging tools & other dependencies..."
|
||||
sudo apt-get -qq install -y AtomicParsley flac vorbis-tools imagemagick oracle-java8-installer elasticsearch pkg-config yasm libfaac-dev libmp3lame-dev libvorbis-dev libtheora-dev
|
||||
|
||||
echo "Installing PHP extensions"
|
||||
sudo apt-get -qq install -y libgmp-dev php-gmp
|
||||
|
||||
|
||||
if type ffmpeg &>/dev/null; then
|
||||
echo "ffmpeg is installed!"
|
||||
|
|
Loading…
Reference in a new issue