--- /dev/null
+<?php
+
+namespace App;
+
+use Auth;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations;
+use Illuminate\Http\Request;
+
+/**
+ * Class AccessLog
+ * @package App
+ * @property string $ip;
+ * @property string $user_agent;
+ * @property User $user;
+ * @property int $user_id;
+ *
+ */
+class AccessLog extends Model
+{
+ protected $guarded = [];
+
+
+ /**
+ * @return Relations\BelongsTo
+ */
+ public function user(): Relations\BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+
+ /**
+ * @param Request $request
+ */
+ public static function log(Request $request): void
+ {
+ $entry = new self;
+
+ $entry->ip = implode(', ', $request->ips());
+ $entry->user_id = Auth::check() ? Auth::user()->id : null;
+ $entry->user_agent = $request->userAgent();
+
+ $entry->save();
+
+ }
+}
+
namespace App\Http\Controllers;
+use App\AccessLog;
use App\Flowpaper\Pdf2Json;
use App\PdfFile;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class FlowpaperController extends Controller
{
+
/**
* @param PdfFile $file
+ * @param Request $request
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function view(PdfFile $file): View
+ public function view(PdfFile $file, Request $request): View
{
$this->authorize('view', $file);
+ AccessLog::log($request);
return $file->view();
}
use App\Jobs\ProcessEmailBatch;
use App\Jobs\ProcessPdfFile;
use App\PdfFile;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Arr;
*/
public function collections()
{
- return new ResourceCollection(FileCollection::all());
+ $files = FileCollection::query()->with(['files' => function($builder) {
+ return $builder->orderByDesc('updated_at')->first();
+ }])->get();
+
+ return new ResourceCollection($files);
}
public function recipientsCount()
namespace App\Http;
+use App\Http\Middleware\LoginWithToken;
+use App\LoginToken;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
* @var array
*/
protected $routeMiddleware = [
+ 'login.token' => LoginWithToken::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
--- /dev/null
+<?php
+
+namespace App\Http\Middleware;
+
+use App\LoginToken;
+use Closure;
+
+class LoginWithToken
+{
+ /**
+ * Handle an incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ LoginToken::checkAndLogin($request);
+
+ return $next($request);
+ }
+}
/** @var Collection $users */
$users = User::subscribed()->get();
- $size = 2;
+ $size = env('MAILGUN_CHUNK_SIZE', 50);
$chunks = $users->chunk($size);
$this->processUpdate([
foreach($chunks as $chunk) {
- $variables = json_encode($chunk->mapWithKeys(function($user) {
+ $variables = json_encode($chunk->mapWithKeys(function(User $user) {
return [$user->email => [
'id' => $user->id,
'name' => $user->name,
+ 'file_url' => $this->batch->file->getUrlWithToken($user),
]];
}));
$view = view('emails.batch', [
'subject' => $this->batch->subject,
'content' => $this->batch->content['body'],
- 'link' => $this->batch->file->getUrl(),
])->render();
$params = [
--- /dev/null
+<?php
+
+namespace App;
+
+use Auth;
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Http\Request;
+
+/**
+ * Class LoginToken
+ * @package App
+ * @property string $token
+ * @property int $user_id
+ * @property User $user
+ * @property Carbon $valid_until
+ */
+class LoginToken extends Model
+{
+
+ /**
+ *
+ */
+ protected static function booted()
+ {
+ static::addGlobalScope('valid', function (Builder $builder) {
+ $builder->where('valid_until', '>', now())->orWhereNull('valid_until');
+ });
+ }
+
+ /**
+ * @param User $user
+ * @param Carbon|null $validUntil
+ * @return LoginToken
+ */
+ public static function generateToken(User $user, ?Carbon $validUntil = null): LoginToken
+ {
+ $token = new self;
+ $token->user_id = $user->id;
+ $token->valid_until = $validUntil;
+ $token->token = \Str::random(128);
+ $token->save();
+
+ return $token;
+
+ }
+
+
+ public static function checkAndLogin(Request $request): void
+ {
+ if(! $request->has('token')) {
+ return;
+ }
+
+ $token = $request->get('token');
+
+ if($loginToken = self::where('token', $token)->first()) {
+ Auth::loginUsingId($loginToken->user_id);
+ $loginToken->delete();
+ }
+
+ }
+
+ /**
+ * @return BelongsTo
+ */
+ public function user(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString(): string
+ {
+ return $this->token;
+ }
+}
--- /dev/null
+<?php
+
+namespace App\Models;
+
+use A17\Twill\Models\User;
+
+class Admin extends User
+{
+
+
+}
+++ /dev/null
-<?php
-
-namespace App\Models;
-
-
-use A17\Twill\Models\Model;
-
-class User extends Model
-{
-
-
-
- protected $fillable = [
- 'published',
- 'title',
- 'description',
- ];
-
-}
*
* @var string
*/
- public static $model = \A17\Twill\Models\User::class;
+ public static $model = \App\Models\Admin::class;
/**
* The single value that should be used to represent the resource when being displayed.
'subject',
];
+
/**
* Get the fields displayed by the resource.
*
namespace App\Nova;
use Illuminate\Http\Request;
+use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
'name',
];
+
+ public static $group = "CRM";
+
+ public static function label()
+ {
+ return "Organisations";
+ }
+
+
/**
* Get the fields displayed by the resource.
*
ID::make()->sortable(),
Text::make('Nom', 'name'),
HasMany::make('Membres', 'members', User::class),
+ Boolean::make('Abonnement actif', 'subscription_active')
];
}
'title',
];
+
+
/**
* Get the fields displayed by the resource.
*
'id',
];
+
+
/**
* Get the fields displayed by the resource.
*
use Illuminate\Http\Request;
use Laravel\Nova\Fields\BelongsTo;
+use Laravel\Nova\Fields\Boolean;
+use Laravel\Nova\Fields\Country;
use Laravel\Nova\Fields\ID;
+use Laravel\Nova\Fields\Place;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
+use Laravel\Nova\Panel;
class User extends Resource
{
*
* @var string
*/
- public static $title = 'id';
+ public static $title = 'name';
- /**
- * The columns that should be searched.
- *
- * @var array
- */
- public static $search = [
- 'id',
- ];
+ public static $group = "CRM";
+
+ public static function label()
+ {
+ return "Abonnés";
+ }
/**
* Get the fields displayed by the resource.
public function fields(Request $request)
{
return [
- ID::make()->sortable(),
- Text::make('Nom', 'name'),
- Text::make('Email'),
- BelongsTo::make('Organisation', 'organization', Organization::class),
+ new Panel('Fiche', $this->basicInfo()),
+ new Panel('Adresse', $this->addressFields()),
+ new Panel('Affiliation', [
+ BelongsTo::make('Organisation', 'organization', Organization::class),
+
+ ]),
+ Boolean::make('Abonnement actif', 'isSubscribed')->readonly()->onlyOnIndex(),
];
}
+
+ protected function basicInfo()
+ {
+ return [
+ ID::make()->sortable()->onlyOnIndex(),
+ Text::make('Prénom', 'first_name'),
+ Text::make('Nom', 'last_name'),
+ Text::make('Email')->hideFromIndex(),
+ Text::make('Position'),
+ Text::make('Téléphone', 'phone'),
+ ];
+
+ }
+
+
+ /**
+ * Get the address fields for the resource.
+ *
+ *
+ */
+ protected function addressFields()
+ {
+ return [
+ Place::make('Address', 'address_line_1')->hideFromIndex(),
+ Text::make('City')->hideFromIndex(),
+ Text::make('Postal Code')->hideFromIndex(),
+ Country::make('Country')->hideFromIndex(),
+ ];
+ }
+
+
/**
* Get the cards available for the request.
*
$this->makeJson();
$this->makeCover();
$this->makeSearchable();
- $this->shortenLinks();
+ if(!env('APP_ENV') === 'local')
+ $this->shortenLinks();
$this->saveToCloud();
+
}
*/
public function getCoverUrlAttribute(): string
{
- return Storage::cloud()->temporaryUrl($this->coverPath, now()->addDay());
+ return Storage::cloud()->url($this->coverPath);
}
return route('flowpaper.view', ['file' => $this->slug]);
}
+ public function getUrlWithToken(User $user)
+ {
+ return $user->routeWithToken('flowpaper.view', ['file' => $this->slug]);
+ }
+
Storage::disk('public')->makeDirectory('covers');
$pdf = new PdfToImage\Pdf($this->absolutePdfPath);
$pdf->setResolution(72)
- ->setCompressionQuality(60)
->saveImage($tmp);
+ $image =\Image::make($tmp);
+ $image->interlace();
+ $image->save(null, 50);
+
+
Storage::cloud()->putFileAS('/', $tmp, $this->coverPath);
unlink($tmp);
* @return mixed
* @throws AuthenticationException
*/
- public function view($user, PdfFile $pdfFile)
+ public function view($user = null, PdfFile $pdfFile)
{
- if($pdfFile->is_free || ($user instanceof \App\User ? $user->isSubscribed() : false)) {
+ if($pdfFile->is_free || ($user instanceof \App\User ? $user->isSubscribed : false)) {
return true;
}
'slug' => $this->file->slug,
'tags' => $this->file->fileTags()->pluck('content')->toArray(),
'collection' => (string) $this->file->collection,
- 'ref' => $this->file->ref
+ 'ref' => $this->file->ref,
+ 'cover' => $this->file->coverUrl,
],
'content' => $this->content,
'page' => $this->page,
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
+use Laravel\Scout\Searchable;
/**
* Class User
* @package App
* @property $first_name
* @property $last_name
+ * @property $id
* @property-read $name
* @property Organization $organization
+ * @property string $position
+ * @property bool $isSubscribed
*/
class User extends Authenticatable
{
use Notifiable;
+ use Searchable;
/**
* The attributes that are mass assignable.
'subscription_active' => 'boolean',
];
+ public function toSearchableArray()
+ {
+ return [
+ 'first_name' => $this->first_name,
+ 'last_name' => $this->last_name,
+ 'position' => $this->position,
+ 'organization' => $this->organization->name,
+ ];
+ }
/**
* @return BelongsTo
/**
* @return bool
*/
- public function isSubscribed(): bool
+ public function getIsSubscribedAttribute(): bool
{
if($o = $this->organization){
return $o->isSubscribed();
return false;
}
+ public function loginTokens()
+ {
+ return $this->hasMany(LoginToken::class);
+ }
+
+ public function routeWithToken($route, $params = [], $absolute = true)
+ {
+ $token = [ 'token' => LoginToken::generateToken($this)->token ];
+ return route($route, array_merge($params, $token), $absolute);
+ }
+
/**
* @param Builder $builder
*/
+
+
+
}
|
*/
- 'locale' => 'en',
+ 'locale' => 'fr',
/*
|--------------------------------------------------------------------------
'admins' => [
'driver' => 'eloquent',
- 'model' => \A17\Twill\Models\User::class,
+ 'model' => \App\Models\Admin::class,
],
],
|--------------------------------------------------------------------------
|
*/
- 'locale' => 'en',
+ 'locale' => 'fr',
'fallback_locale' => 'en',
];
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateAccessLogsTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('access_logs', function (Blueprint $table) {
+ $table->id();
+ $table->timestamps();
+ $table->string('ip');
+ $table->string('user_agent');
+ $table->unsignedBigInteger('user_id')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('access_logs');
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateAccessTokensTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('login_tokens', function (Blueprint $table) {
+ $table->id();
+ $table->string('token')->unique();
+ $table->unsignedBigInteger('user_id');
+ $table->dateTime('valid_until')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('login_tokens');
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class UserPlaceUpdateToUsers extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->renameColumn('city_name', 'city');
+ $table->renameColumn('zip_code', 'postal_code');
+
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->renameColumn('city', 'city_name');
+ $table->renameColumn('postal_code', 'zip_code');
+ });
+ }
+}
border: 0;
}
-.form-control {
+.form-control,
+.ais-SearchBox-input {
display: block;
width: 100%;
height: calc(1.6em + 0.75rem + 2px);
}
@media (prefers-reduced-motion: reduce) {
- .form-control {
+ .form-control,
+ .ais-SearchBox-input {
transition: none;
}
}
-.form-control::-ms-expand {
+.form-control::-ms-expand,
+.ais-SearchBox-input::-ms-expand {
background-color: transparent;
border: 0;
}
-.form-control:-moz-focusring {
+.form-control:-moz-focusring,
+.ais-SearchBox-input:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #495057;
}
-.form-control:focus {
+.form-control:focus,
+.ais-SearchBox-input:focus {
color: #495057;
background-color: #fff;
border-color: #a1cbef;
box-shadow: 0 0 0 0.2rem rgba(52, 144, 220, 0.25);
}
-.form-control::-webkit-input-placeholder {
+.form-control::-webkit-input-placeholder, .ais-SearchBox-input::-webkit-input-placeholder {
color: #6c757d;
opacity: 1;
}
-.form-control::-moz-placeholder {
+.form-control::-moz-placeholder, .ais-SearchBox-input::-moz-placeholder {
color: #6c757d;
opacity: 1;
}
-.form-control:-ms-input-placeholder {
+.form-control:-ms-input-placeholder, .ais-SearchBox-input:-ms-input-placeholder {
color: #6c757d;
opacity: 1;
}
-.form-control::-ms-input-placeholder {
+.form-control::-ms-input-placeholder, .ais-SearchBox-input::-ms-input-placeholder {
color: #6c757d;
opacity: 1;
}
-.form-control::placeholder {
+.form-control::placeholder,
+.ais-SearchBox-input::placeholder {
color: #6c757d;
opacity: 1;
}
.form-control:disabled,
-.form-control[readonly] {
+.ais-SearchBox-input:disabled,
+.form-control[readonly],
+[readonly].ais-SearchBox-input {
background-color: #e9ecef;
opacity: 1;
}
-select.form-control:focus::-ms-value {
+select.form-control:focus::-ms-value,
+select.ais-SearchBox-input:focus::-ms-value {
color: #495057;
background-color: #fff;
}
}
select.form-control[size],
-select.form-control[multiple] {
+select[size].ais-SearchBox-input,
+select.form-control[multiple],
+select[multiple].ais-SearchBox-input {
height: auto;
}
-textarea.form-control {
+textarea.form-control,
+textarea.ais-SearchBox-input {
height: auto;
}
}
.was-validated .form-control:valid,
-.form-control.is-valid {
+.was-validated .ais-SearchBox-input:valid,
+.form-control.is-valid,
+.is-valid.ais-SearchBox-input {
border-color: #38c172;
padding-right: calc(1.6em + 0.75rem);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2338c172' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
}
.was-validated .form-control:valid:focus,
-.form-control.is-valid:focus {
+.was-validated .ais-SearchBox-input:valid:focus,
+.form-control.is-valid:focus,
+.is-valid.ais-SearchBox-input:focus {
border-color: #38c172;
box-shadow: 0 0 0 0.2rem rgba(56, 193, 114, 0.25);
}
.was-validated textarea.form-control:valid,
-textarea.form-control.is-valid {
+.was-validated textarea.ais-SearchBox-input:valid,
+textarea.form-control.is-valid,
+textarea.is-valid.ais-SearchBox-input {
padding-right: calc(1.6em + 0.75rem);
background-position: top calc(0.4em + 0.1875rem) right calc(0.4em + 0.1875rem);
}
}
.was-validated .form-control:invalid,
-.form-control.is-invalid {
+.was-validated .ais-SearchBox-input:invalid,
+.form-control.is-invalid,
+.is-invalid.ais-SearchBox-input {
border-color: #e3342f;
padding-right: calc(1.6em + 0.75rem);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e3342f' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e3342f' stroke='none'/%3e%3c/svg%3e");
}
.was-validated .form-control:invalid:focus,
-.form-control.is-invalid:focus {
+.was-validated .ais-SearchBox-input:invalid:focus,
+.form-control.is-invalid:focus,
+.is-invalid.ais-SearchBox-input:focus {
border-color: #e3342f;
box-shadow: 0 0 0 0.2rem rgba(227, 52, 47, 0.25);
}
.was-validated textarea.form-control:invalid,
-textarea.form-control.is-invalid {
+.was-validated textarea.ais-SearchBox-input:invalid,
+textarea.form-control.is-invalid,
+textarea.is-invalid.ais-SearchBox-input {
padding-right: calc(1.6em + 0.75rem);
background-position: top calc(0.4em + 0.1875rem) right calc(0.4em + 0.1875rem);
}
margin-bottom: 0;
}
- .form-inline .form-control {
+ .form-inline .form-control,
+ .form-inline .ais-SearchBox-input {
display: inline-block;
width: auto;
vertical-align: middle;
}
.input-group > .form-control,
+.input-group > .ais-SearchBox-input,
.input-group > .form-control-plaintext,
.input-group > .custom-select,
.input-group > .custom-file {
}
.input-group > .form-control + .form-control,
+.input-group > .ais-SearchBox-input + .form-control,
+.input-group > .form-control + .ais-SearchBox-input,
+.input-group > .ais-SearchBox-input + .ais-SearchBox-input,
.input-group > .form-control + .custom-select,
+.input-group > .ais-SearchBox-input + .custom-select,
.input-group > .form-control + .custom-file,
+.input-group > .ais-SearchBox-input + .custom-file,
.input-group > .form-control-plaintext + .form-control,
+.input-group > .form-control-plaintext + .ais-SearchBox-input,
.input-group > .form-control-plaintext + .custom-select,
.input-group > .form-control-plaintext + .custom-file,
.input-group > .custom-select + .form-control,
+.input-group > .custom-select + .ais-SearchBox-input,
.input-group > .custom-select + .custom-select,
.input-group > .custom-select + .custom-file,
.input-group > .custom-file + .form-control,
+.input-group > .custom-file + .ais-SearchBox-input,
.input-group > .custom-file + .custom-select,
.input-group > .custom-file + .custom-file {
margin-left: -1px;
}
.input-group > .form-control:focus,
+.input-group > .ais-SearchBox-input:focus,
.input-group > .custom-select:focus,
.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {
z-index: 3;
}
.input-group > .form-control:not(:last-child),
+.input-group > .ais-SearchBox-input:not(:last-child),
.input-group > .custom-select:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group > .form-control:not(:first-child),
+.input-group > .ais-SearchBox-input:not(:first-child),
.input-group > .custom-select:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.input-group-lg > .form-control:not(textarea),
+.input-group-lg > .ais-SearchBox-input:not(textarea),
.input-group-lg > .custom-select {
height: calc(1.5em + 1rem + 2px);
}
.input-group-lg > .form-control,
+.input-group-lg > .ais-SearchBox-input,
.input-group-lg > .custom-select,
.input-group-lg > .input-group-prepend > .input-group-text,
.input-group-lg > .input-group-append > .input-group-text,
}
.input-group-sm > .form-control:not(textarea),
+.input-group-sm > .ais-SearchBox-input:not(textarea),
.input-group-sm > .custom-select {
height: calc(1.5em + 0.5rem + 2px);
}
.input-group-sm > .form-control,
+.input-group-sm > .ais-SearchBox-input,
.input-group-sm > .custom-select,
.input-group-sm > .input-group-prepend > .input-group-text,
.input-group-sm > .input-group-append > .input-group-text,
-webkit-animation: blink-fade 1000ms infinite;
}
+.ais-SearchBox-input {
+ width: calc(100% - 20px);
+ display: inline-block;
+}
+
+mark.ais-Snippet-highlighted,
+mark.mark {
+ padding: 0;
+ background-color: #fff252;
+}
+
//
//
//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
/* harmony default export */ __webpack_exports__["default"] = ({
name: "FileHit",
props: ['hit'],
return {};
},
computed: {
- imgLink: function imgLink() {
- return "/files/".concat(this.hit.file.slug, "/cover");
- },
viewLink: function viewLink() {
- return "/view/".concat(this.hit.file.slug);
+ return "/view/".concat(this.hit.file.slug, "#page=").concat(this.hit.page);
}
}
});
//
//
//
+//
+//
+//
/* harmony default export */ __webpack_exports__["default"] = ({
mixins: [_mixins_SearchMixin__WEBPACK_IMPORTED_MODULE_0__["default"]],
//
//
//
-//
/* harmony default export */ __webpack_exports__["default"] = ({
uploadedFile: function uploadedFile() {
return this.files.length > 0 ? this.files[0] : null;
},
+ nextRef: function nextRef() {
+ return this.getNextRef(this.file_collection);
+ },
fileValid: function fileValid() {
if (this.files.length < 1) return;
return this.checkFileValid(this.files[0]);
});
}
},
+ getNextRef: function getNextRef(collection_id) {
+ var collection = this.collections.find(function (c) {
+ return c.id === collection_id;
+ });
+ if (collection === undefined) return null;
+ var lastRef = collection.files[0].ref;
+ return lastRef.replace(/(\d+)+/g, function (match, number) {
+ return parseInt(number) + 1;
+ });
+ },
checkFileValid: function checkFileValid(file) {
return file.type === 'application/pdf';
},
var _this = this;
axios.get('/publish/collections').then(function (d) {
- return _this.collections = d.data.data;
+ _this.collections = d.data.data;
+ _this.file_ref = _this.getNextRef(1);
});
axios.get('/publish/tags').then(function (d) {
var tags = d.data.data;
file: this.$root.publishState.file,
editor: _ckeditor_ckeditor5_build_classic__WEBPACK_IMPORTED_MODULE_0___default.a,
email: {
- content: "\n <p>Bonjour $nom$,</p>\n <p>Voici la nouvelle \xE9dition du jour !</p>\n ",
+ content: "\n <p>Bonjour %recipient.name%,</p>\n <p>Voici la nouvelle \xE9dition du jour !</p>\n ",
subject: "[".concat(this.$root.publishState.file.title, "] ")
},
csrf: document.querySelectorAll('meta[name="csrf-token"]')[0].content,
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
- return _c("div", { staticClass: "card w-100" }, [
- _c("img", {
- staticClass: "card-img-top",
- attrs: { src: _vm.imgLink, alt: "Cover" }
- }),
- _vm._v(" "),
- _c("div", { staticClass: "card-body" }, [
- _c("h5", { staticClass: "card-title" }, [
- _vm._v(_vm._s(_vm.hit.file.collection) + " - "),
- _c("strong", [_vm._v(_vm._s(_vm.hit.file.ref))])
+ return _c(
+ "div",
+ { staticClass: "card w-100" },
+ [
+ _c("img", {
+ staticClass: "card-img-top",
+ attrs: { src: _vm.hit.file.cover, alt: "Cover" }
+ }),
+ _vm._v(" "),
+ _c("div", { staticClass: "card-body" }, [
+ _c("h5", { staticClass: "card-title" }, [
+ _vm._v(_vm._s(_vm.hit.file.collection) + " - "),
+ _c("strong", [_vm._v(_vm._s(_vm.hit.file.ref))])
+ ]),
+ _vm._v(" "),
+ _c(
+ "p",
+ { staticClass: "card-text" },
+ _vm._l(_vm.hit.file.tags, function(tag) {
+ return _c("span", { staticClass: "badge badge-light mr-1" }, [
+ _c("i", { staticClass: "fas fa-tag" }),
+ _vm._v(" " + _vm._s(tag) + "\n ")
+ ])
+ }),
+ 0
+ ),
+ _vm._v(" "),
+ _c(
+ "p",
+ { staticClass: "card-text" },
+ [
+ _vm.hit._highlightResult.content.matchLevel !== "none"
+ ? _c("ais-snippet", {
+ attrs: { attribute: "content", hit: _vm.hit }
+ })
+ : _vm._e()
+ ],
+ 1
+ )
]),
_vm._v(" "),
_c(
- "p",
- { staticClass: "card-text" },
- [_c("ais-snippet", { attrs: { attribute: "content", hit: _vm.hit } })],
- 1
+ "md-button",
+ {
+ staticClass: "md-raised ",
+ attrs: { href: _vm.viewLink, target: "_blank" }
+ },
+ [
+ _c("i", { staticClass: "fas fa-book-open" }),
+ _vm._v(" Lire cette édition")
+ ]
)
- ]),
- _vm._v(" "),
- _c("a", { staticClass: "btn btn-primary", attrs: { href: _vm.viewLink } }, [
- _c("i", { staticClass: "fas fa-book-open" }),
- _vm._v(" Lire cette édition")
- ])
- ])
+ ],
+ 1
+ )
}
var staticRenderFns = []
render._withStripped = true
"div",
{ staticClass: "col-md-12" },
[
- _c("ais-search-box", {
- staticClass: "searchbox",
- attrs: { placeholder: "Rechercher..." }
- }),
+ _c(
+ "div",
+ { staticClass: "px-3" },
+ [
+ _c("ais-search-box", {
+ staticClass: "searchbox ",
+ attrs: { placeholder: "Rechercher..." }
+ })
+ ],
+ 1
+ ),
_vm._v(" "),
- _c("ais-stats"),
+ _c("ais-stats", { staticClass: "mt-2 ml-3" }),
_vm._v(" "),
_c(
"div",
}
},
_vm._l(_vm.collections, function(collection) {
- return _c("option", { domProps: { value: collection.id } }, [
- _vm._v(_vm._s(collection.name))
- ])
+ return _c("option", {
+ domProps: {
+ value: collection.id,
+ textContent: _vm._s(collection.name)
+ }
+ })
}),
0
)
expression: "file_ref"
}
],
- staticClass: "form-control is-invalid",
+ staticClass: "form-control is-valid",
attrs: {
type: "email",
id: "file_ref",
- placeholder: "ex: 482",
+ placeholder: _vm.nextRef,
required: ""
},
domProps: { value: _vm.file_ref },
},
[
_c("i", { staticClass: "fa fa-plus" }),
- _vm._v(
- "\n Sélectionnez un fichier\n "
- )
+ _vm._v("\n Sélectionnez un fichier\n ")
]
)
],
},
[
_c("i", { staticClass: "fas fa-check" }),
- _vm._v(" \n "),
+ _vm._v(" \n "),
_c("span", [
_c("strong", [
_vm._v(
window.Vue.use(_ckeditor_ckeditor5_vue__WEBPACK_IMPORTED_MODULE_5___default.a);
window.Vue.use(vue_instantsearch__WEBPACK_IMPORTED_MODULE_6__["default"]);
window.Vue.use(vue_material_dist_components__WEBPACK_IMPORTED_MODULE_8__["MdProgress"]);
+window.Vue.use(vue_material_dist_components__WEBPACK_IMPORTED_MODULE_8__["MdBadge"]);
window.Vue.use(vue_material_dist_components__WEBPACK_IMPORTED_MODULE_8__["MdButton"]);
/**
* The following block of code may be used to automatically register your
import CKEditor from '@ckeditor/ckeditor5-vue';
import InstantSearch from "vue-instantsearch";
import 'instantsearch.css/themes/reset-min.css';
-import { MdProgress, MdButton } from 'vue-material/dist/components'
+import { MdProgress, MdButton, MdBadge } from 'vue-material/dist/components'
window.Vue.use( CKEditor );
window.Vue.use(InstantSearch);
window.Vue.use(MdProgress);
+window.Vue.use(MdBadge);
window.Vue.use(MdButton);
/**
<template>
<div class="card w-100">
- <img class="card-img-top" :src="imgLink" alt="Cover">
+ <img class="card-img-top" :src="hit.file.cover" alt="Cover">
<div class="card-body">
<h5 class="card-title">{{hit.file.collection}} - <strong>{{hit.file.ref}}</strong></h5>
- <p class="card-text"><ais-snippet attribute="content" :hit="hit"/></p>
+
+ <p class="card-text">
+ <span class="badge badge-light mr-1" v-for="tag in hit.file.tags">
+ <i class="fas fa-tag"></i> {{tag}}
+ </span>
+ </p>
+ <p class="card-text">
+ <ais-snippet attribute="content" :hit="hit" v-if="hit._highlightResult.content.matchLevel !== 'none'"/>
+ </p>
</div>
- <a :href="viewLink" class="btn btn-primary"><i class="fas fa-book-open"></i> Lire cette édition</a>
+
+ <md-button :href="viewLink" class="md-raised " target="_blank"><i class="fas fa-book-open"></i> Lire cette édition</md-button>
+
+
}
},
computed: {
- imgLink: function() {
- return `/files/${this.hit.file.slug}/cover`;
- },
viewLink: function () {
- return `/view/${this.hit.file.slug}`;
+ return `/view/${this.hit.file.slug}#page=${this.hit.page}`;
}
}
<div class="col-md-12">
- <ais-search-box placeholder="Rechercher..." class="searchbox" />
- <ais-stats></ais-stats>
+ <div class="px-3">
+ <ais-search-box placeholder="Rechercher..." class="searchbox " />
+
+ </div>
+ <ais-stats class="mt-2 ml-3"></ais-stats>
<div class="my-4">
<ais-infinite-hits :class-names="{
<div class="form-group">
<label for="file_collection">Collection *</label>
<select class="form-control" id="file_collection" v-model="file_collection">
- <option v-for="collection in collections" :value="collection.id">{{collection.name}}</option>
+ <option v-for="collection in collections" :value="collection.id" v-text="collection.name"></option>
</select>
</div>
</div>
<div class="col-4">
<div class="form-group">
<label for="file_ref">Numéro *</label>
- <input type="email" class="form-control is-invalid" id="file_ref" placeholder="ex: 482" v-model="file_ref" required>
+ <input type="email" class="form-control is-valid" id="file_ref" :placeholder="nextRef" v-model="file_ref" required>
</div>
</div>
<div class="col-12">
}"
:data="file_data"
ref="upload">
- <i class="fa fa-plus"></i>
- Sélectionnez un fichier
+ <i class="fa fa-plus"></i>
+ Sélectionnez un fichier
</file-upload>
</div>
<div v-if="fileValid">
<p class="mb-3"><strong>Fichier sélectionné : </strong> {{files[0].name}} </p>
-<!-- <button type="button" class="btn btn-default btn-primary" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">-->
-<!-- <i class="fa fa-arrow-up" aria-hidden="true"></i>-->
-<!-- Envoyer-->
-<!-- </button>-->
-<!-- <button type="button" class="btn btn-default btn-danger" v-else @click.prevent="$refs.upload.active = false">-->
-<!-- <i class="fa fa-stop" aria-hidden="true"></i>-->
-<!-- Arrêter-->
-<!-- </button>-->
+ <!-- <button type="button" class="btn btn-default btn-primary" v-if="!$refs.upload || !$refs.upload.active" @click.prevent="$refs.upload.active = true">-->
+ <!-- <i class="fa fa-arrow-up" aria-hidden="true"></i>-->
+ <!-- Envoyer-->
+ <!-- </button>-->
+ <!-- <button type="button" class="btn btn-default btn-danger" v-else @click.prevent="$refs.upload.active = false">-->
+ <!-- <i class="fa fa-stop" aria-hidden="true"></i>-->
+ <!-- Arrêter-->
+ <!-- </button>-->
</div>
<div v-else class="alert alert-warning" role="alert">
- <span class="font-bold">Fichier incorrect</span>
- <span>Le fichier {{uploadedFile.name}} n'est pas valide, veuillez choisir un fichier .pdf !</span>
+ <span class="font-bold">Fichier incorrect</span>
+ <span>Le fichier {{uploadedFile.name}} n'est pas valide, veuillez choisir un fichier .pdf !</span>
</div>
</template>
-<!--@todo Check NEXT button activation-->
<script>
import FileUpload from 'vue-upload-component';
import ProgressBar from 'vue-simple-progress';
}
},
computed: {
- uploadedFile: function(){
- return (this.files.length > 0) ? this.files[0] : null;
- },
-
- fileValid: function() {
- if(this.files.length < 1)
- return;
-
- return this.checkFileValid(this.files[0]);
- },
- file_data: function() {
- return {
- collection_id: this.file_collection,
- ref: this.file_ref,
- tags: this.tags
- }
- }
+ uploadedFile: function(){
+ return (this.files.length > 0) ? this.files[0] : null;
+ },
+ nextRef: function(){
+ return this.getNextRef(this.file_collection);
+ },
+
+
+ fileValid: function() {
+ if(this.files.length < 1)
+ return;
+
+ return this.checkFileValid(this.files[0]);
+ },
+ file_data: function() {
+ return {
+ collection_id: this.file_collection,
+ ref: this.file_ref,
+ tags: this.tags
+ }
+ }
},
watch: {
- file_ref: function(value){
- if(value.length > 0) {
- this.status = "ready";
- $('#file_ref').addClass('is-valid').removeClass('is-invalid')
- } else {
- this.status = "start";
- $('#file_ref').addClass('is-invalid').removeClass('is-valid')
-
- }
-
- },
- uploadedFile: function(file){
- if (file.success){
- this.progressMode = 'indeterminate';
- Echo.private(`fileProcessingStatus.${file.response.data.slug}`)
- .listen('.status.update', this.processStatusUpdate)
- }
- }
+ file_ref: function(value){
+ if(value.length > 0) {
+ this.status = "ready";
+ $('#file_ref').addClass('is-valid').removeClass('is-invalid')
+ } else {
+ this.status = "start";
+ $('#file_ref').addClass('is-invalid').removeClass('is-valid')
+
+ }
+
+ },
+ uploadedFile: function(file){
+ if (file.success){
+ this.progressMode = 'indeterminate';
+ Echo.private(`fileProcessingStatus.${file.response.data.slug}`)
+ .listen('.status.update', this.processStatusUpdate)
+ }
+ },
+
},
methods: {
processStatusUpdate(e){
},
+ getNextRef(collection_id) {
+ let collection = this.collections.find(c => c.id === collection_id);
+ if(collection === undefined)
+ return null;
+ let lastRef = collection.files[0].ref;
+ return lastRef.replace(/(\d+)+/g, function(match, number) {
+ return parseInt(number)+1;
+ });
+
+ },
+
checkFileValid(file) {
return file.type === 'application/pdf';
},
inputFile(newFile, oldFile) {
- if (
- newFile &&
- (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error)
- && !this.$refs.upload.active
- && this.checkFileValid(newFile)
- ) {
- this.$refs.upload.active = true
- this.status = 'uploading';
- }
-
- if(newFile && newFile.success === true) {
- this.status = 'processing';
- }
+ if (
+ newFile &&
+ (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error)
+ && !this.$refs.upload.active
+ && this.checkFileValid(newFile)
+ ) {
+ this.$refs.upload.active = true;
+ this.status = 'uploading';
+ }
+
+ if(newFile && newFile.success === true) {
+ this.status = 'processing';
+ }
}
},
mounted() {
- axios.get('/publish/collections').then(d => this.collections = d.data.data);
+ axios.get('/publish/collections').then(d => {
+ this.collections = d.data.data;
+ this.file_ref = this.getNextRef(1);
+ });
+
axios.get('/publish/tags').then(d => {
let tags = d.data.data;
editor: ClassicEditor,
email: {
content: `
- <p>Bonjour $nom$,</p>
+ <p>Bonjour %recipient.name%,</p>
<p>Voici la nouvelle édition du jour !</p>
`,
subject: `[${this.$root.publishState.file.title}] `
animation:blink-fade 1000ms infinite;
-webkit-animation:blink-fade 1000ms infinite;
}
+
+.ais-SearchBox-input {
+ @extend .form-control;
+ width: calc(100% - 20px);
+ display: inline-block;
+}
+
+mark.ais-Snippet-highlighted, mark.mark {
+ padding: 0;
+ background-color: #fff252;
+}
{!! $content !!}
<a
- href="{!! $link ?? '' !!}"
+ href="{{ $link ?? '%recipient.file_url%' }}"
target="_blank"
class="btn btn-success mx-auto my-3 d-block"
style="width: fit-content;"
Route::get('/setup', 'Auth\\RegisterController@setup')->name('setup');
/** Flowpaper viewer */
- Route::prefix('/view')->group(function () {
- Route::get('/data/{file:slug}.bin', 'FlowpaperController@outputFile')->name('flowpaper.bin');
+ Route::prefix('/view')->middleware('login.token')->group(function () {
Route::get('/{file:slug}', 'FlowpaperController@view')->name('flowpaper.view');
});