Merge branch 'master' into 'production'
finish memos cover See merge request Romulus21/portal!32
This commit is contained in:
1
.idea/php.xml
generated
1
.idea/php.xml
generated
@@ -111,6 +111,7 @@
|
||||
<path value="$PROJECT_DIR$/vendor/firebase/php-jwt" />
|
||||
<path value="$PROJECT_DIR$/vendor/laravel/passport" />
|
||||
<path value="$PROJECT_DIR$/vendor/spatie/laravel-web-tinker" />
|
||||
<path value="$PROJECT_DIR$/vendor/intervention/image" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
|
||||
|
||||
1
.idea/portal.iml
generated
1
.idea/portal.iml
generated
@@ -26,6 +26,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/hamcrest/hamcrest-php" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/intervention/image" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jakub-onderka/php-console-color" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jakub-onderka/php-console-highlighter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laminas/laminas-diactoros" />
|
||||
|
||||
57
app/Http/Controllers/ImageController.php
Normal file
57
app/Http/Controllers/ImageController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\Image as ImageResource;
|
||||
use App\Models\Memo;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Intervention\Image\Facades\Image;
|
||||
|
||||
class ImageController extends Controller
|
||||
{
|
||||
public function users(User $user)
|
||||
{
|
||||
$data = $this->storeImage();
|
||||
|
||||
$newImage = auth()->user()->images()->create([
|
||||
'path' => $data['path'],
|
||||
'width' => $data['width'],
|
||||
'height' => $data['height'],
|
||||
'location' => $data['location'],
|
||||
]);
|
||||
|
||||
return new ImageResource($newImage);
|
||||
}
|
||||
|
||||
public function memos(Memo $memo)
|
||||
{
|
||||
$data = $this->storeImage();
|
||||
|
||||
$newImage = $memo->images()->create([
|
||||
'path' => $data['path'],
|
||||
'width' => $data['width'],
|
||||
'height' => $data['height'],
|
||||
'location' => $data['location'],
|
||||
]);
|
||||
|
||||
return new ImageResource($newImage);
|
||||
}
|
||||
|
||||
private function storeImage() {
|
||||
$data = request()->validate([
|
||||
'image' => 'required',
|
||||
'width' => 'required',
|
||||
'height' => 'required',
|
||||
'location' => 'required',
|
||||
]);
|
||||
|
||||
$data['path'] = $data['image']->store('images', 'public');
|
||||
|
||||
Image::make($data['image'])
|
||||
->fit($data['width'], $data['height'])
|
||||
->save(storage_path('app/public/images/'.$data['image']->hashName()));
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,11 @@ class UserController extends Controller
|
||||
->setStatusCode(Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
public function show(User $user)
|
||||
{
|
||||
return New UserResource($user);
|
||||
}
|
||||
|
||||
private function validateData()
|
||||
{
|
||||
return request()->validate([
|
||||
|
||||
33
app/Http/Resources/Image.php
Normal file
33
app/Http/Resources/Image.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class Image extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'data' => [
|
||||
'type' => 'images',
|
||||
'image_id' => $this->id,
|
||||
'attributes' => [
|
||||
'path' => url('storage/'.$this->path),
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'location' => $this->location,
|
||||
]
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/images/'.$this->id),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Http\Resources\Image as ImageResource;
|
||||
use App\Http\Resources\User as UserResource;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class Memo extends JsonResource
|
||||
@@ -16,10 +18,15 @@ class Memo extends JsonResource
|
||||
{
|
||||
return [
|
||||
'data' => [
|
||||
'type' => 'memos',
|
||||
'memo_id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'memo' => $this->memo,
|
||||
'last_updated' => $this->updated_at->diffForHumans(),
|
||||
'attributes' => [
|
||||
'posted_by' => new UserResource($this->user),
|
||||
'cover_image' => new ImageResource($this->coverImage),
|
||||
]
|
||||
//'tags' => TagResource::collection($this->tags),
|
||||
],
|
||||
'links' => [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Http\Resources\Image as ImageResource;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class User extends JsonResource
|
||||
@@ -21,6 +22,8 @@ class User extends JsonResource
|
||||
'attributes' => [
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'profile_image' => new ImageResource($this->profileImage),
|
||||
'cover_image' => new ImageResource($this->coverImage),
|
||||
'last_login' => optional($this->login_at)->diffForHumans(),
|
||||
'is_admin' => $this->isAdmin(),
|
||||
],
|
||||
|
||||
15
app/Models/Image.php
Normal file
15
app/Models/Image.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Image extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function imageable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ namespace App\Models;
|
||||
|
||||
use App\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
|
||||
class Memo extends Model
|
||||
@@ -24,4 +26,19 @@ class Memo extends Model
|
||||
// {
|
||||
// return $this->morphToMany(Tag::class, 'taggable')->withTimestamps()->withPivot('user_id');
|
||||
// }
|
||||
|
||||
public function images(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Image::class, 'imageable');
|
||||
}
|
||||
|
||||
public function coverImage(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Image::class, 'imageable')
|
||||
->orderBy('id', 'desc')
|
||||
->where('location', 'cover')
|
||||
->withDefault(function ($userImage) {
|
||||
$userImage->path = 'images/default-cover.jpg';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Models\Image;
|
||||
use App\Models\Memo;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Passport\HasApiTokens;
|
||||
@@ -54,4 +57,23 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasMany(Memo::class);
|
||||
}
|
||||
|
||||
public function images(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Image::class, 'imageable');
|
||||
}
|
||||
|
||||
public function profileImage(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Image::class, 'imageable')
|
||||
->where('location', 'profile')
|
||||
->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
public function coverImage(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Image::class, 'imageable')
|
||||
->where('location', 'cover')
|
||||
->orderBy('id', 'desc');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fruitcake/laravel-cors": "^1.0",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"intervention/image": "^2.5",
|
||||
"laravel/framework": "^7.0",
|
||||
"laravel/passport": "^8.4",
|
||||
"laravel/tinker": "^2.0",
|
||||
|
||||
72
composer.lock
generated
72
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bc656763558a52327994265003e73f70",
|
||||
"content-hash": "2c4223d87e78990cf94811823ed227a9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
@@ -752,6 +752,76 @@
|
||||
],
|
||||
"time": "2019-07-01T23:21:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"version": "2.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image.git",
|
||||
"reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/abbf18d5ab8367f96b3205ca3c89fb2fa598c69e",
|
||||
"reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-fileinfo": "*",
|
||||
"guzzlehttp/psr7": "~1.1",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9.2",
|
||||
"phpunit/phpunit": "^4.8 || ^5.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "to use GD library based image processing.",
|
||||
"ext-imagick": "to use Imagick based image processing.",
|
||||
"intervention/imagecache": "Caching extension for the Intervention Image library"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Intervention\\Image\\ImageServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Image": "Intervention\\Image\\Facades\\Image"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\": "src/Intervention/Image"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@olivervogel.com",
|
||||
"homepage": "http://olivervogel.com/"
|
||||
}
|
||||
],
|
||||
"description": "Image handling and manipulation library with support for Laravel integration",
|
||||
"homepage": "http://image.intervention.io/",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"laravel",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"time": "2019-11-02T09:15:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jakub-onderka/php-console-color",
|
||||
"version": "v0.2",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateImagesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('images', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('imageable');
|
||||
$table->string('path');
|
||||
$table->smallInteger('width');
|
||||
$table->smallInteger('height');
|
||||
$table->string('location');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('images');
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
"vuex": "^3.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"dropzone": "^5.7.0",
|
||||
"laravel-mix-svg-vue": "^0.2.6",
|
||||
"markdown-it": "^10.0.0",
|
||||
"markdown-it-checkbox": "^1.1.0"
|
||||
|
||||
89
resources/js/components/UploadableImage.vue
Normal file
89
resources/js/components/UploadableImage.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div>
|
||||
<img
|
||||
v-if="image"
|
||||
:class="classes"
|
||||
:src="imageObject.data.attributes.path"
|
||||
ref="image"
|
||||
:alt="alt">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropzone from 'dropzone'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: "UploadableImage",
|
||||
props: {
|
||||
imageWidth: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
imageHeight: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
location: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
image: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
author: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
classes: String,
|
||||
alt: String
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
dropzone: null,
|
||||
dropImage: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if(this.authUser.data.user_id === this.author.data.user_id) {
|
||||
this.dropzone = new Dropzone(this.$refs.image, this.settings)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
authUser: 'authUser'
|
||||
}),
|
||||
settings() {
|
||||
let url = '/api/images/' + this.model + '/' + this.id
|
||||
return {
|
||||
paramName: 'image',
|
||||
url: url,
|
||||
acceptedFiles: 'image/*',
|
||||
params: {
|
||||
'width': this.imageWidth,
|
||||
'height': this.imageHeight,
|
||||
'location': this.location,
|
||||
},
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.head.querySelector('meta[name=csrf-token]').content
|
||||
},
|
||||
success: (e, res) => {
|
||||
this.dropImage = res
|
||||
}
|
||||
}
|
||||
},
|
||||
imageObject() {
|
||||
return this.dropImage || this.image
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
2
resources/js/router.js
vendored
2
resources/js/router.js
vendored
@@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Home from "./views/Home"
|
||||
import Profil from "./views/User/UserProfil";
|
||||
import Profil from "./views/User/UserProfile";
|
||||
import DashBoard from "./views/DashBoard";
|
||||
import CssTesteur from "./views/CssTesteur";
|
||||
import MemoIndex from "./views/Memo/MemoIndex";
|
||||
|
||||
@@ -1,33 +1,51 @@
|
||||
<template>
|
||||
<div class="memo-edit p-2">
|
||||
<div class="flex-between mb-1">
|
||||
<router-link :to="'/memos/' + this.$route.params.id" class="btn">< Back</router-link>
|
||||
</div>
|
||||
<form @submit.prevent="submitForm">
|
||||
<InputField name="name" :data="form.name" label="Title" placeholder="Your Title" required @update:field="form.name = $event" :errors="errors" />
|
||||
<TextAreaField class="memo-text-area" name="memo" :data="form.memo" placeholder="Your Memo" required @update:field="form.memo = $event" :errors="errors" />
|
||||
<div class="flex-end mt-1">
|
||||
<button class="btn-primary">Save</button>
|
||||
<div class="memo-edit">
|
||||
<div class="relative">
|
||||
<UploadableImage
|
||||
v-if="!loading"
|
||||
:image-width=1500
|
||||
:image-height=500
|
||||
location="cover"
|
||||
:image="form.attributes.cover_image"
|
||||
:author="form.attributes.posted_by"
|
||||
:id="form.memo_id"
|
||||
:model="form.type"
|
||||
classes="cover"
|
||||
:alt="form.name"/>
|
||||
<div class="flex-between ml-2 mt-1 absolute t-0">
|
||||
<router-link :to="'/memos/' + this.$route.params.id" class="btn-secondary">< Back</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<form @submit.prevent="submitForm">
|
||||
<InputField name="name" :data="form.name" label="Title" placeholder="Your Title" required @update:field="form.name = $event" :errors="errors" />
|
||||
<TextAreaField class="memo-text-area" name="memo" :data="form.memo" placeholder="Your Memo" required @update:field="form.memo = $event" :errors="errors" />
|
||||
<div class="flex-end mt-1">
|
||||
<button class="btn-primary">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InputField from "../../components/InputField";
|
||||
import TextAreaField from "../../components/TextAreaField";
|
||||
import UploadableImage from "../../components/UploadableImage";
|
||||
|
||||
export default {
|
||||
name: "MemoEdit",
|
||||
components: {
|
||||
InputField, TextAreaField
|
||||
InputField, TextAreaField, UploadableImage
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
form: {
|
||||
'name': '',
|
||||
'memo': '',
|
||||
'attributes': {}
|
||||
},
|
||||
errors: null,
|
||||
loading: true,
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
<p>No memos yet. <router-link to="/memos/create">Get Started ></router-link></p>
|
||||
</div>
|
||||
<router-link v-for="memo in memos" :key="memo.data.memo_id" :to="'/memos/' + memo.data.memo_id" class="card">
|
||||
<h1>{{ memo.data.name }}</h1>
|
||||
<div class="memo-date">{{ memo.data.last_updated }}</div>
|
||||
<div>
|
||||
<img :src="memo.data.attributes.cover_image.data.attributes.path" alt="" class="cover">
|
||||
<h1 class="p-1">{{ memo.data.name }}</h1>
|
||||
</div>
|
||||
<div class="memo-date p-1">{{ memo.data.last_updated }}</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div>
|
||||
<Loader v-if="loading" />
|
||||
<div v-else>
|
||||
<div class="flex-between flex-center mb-1 relative">
|
||||
<router-link to="/memos/" class="btn">< Back</router-link>
|
||||
<div v-if="modal" class="modal-container" @click="modal = ! modal">
|
||||
<div class="modal">
|
||||
<p class="m-1 text-center">Are you sure you want to delete this record ?</p>
|
||||
<div class="flex-center m-1 mt-2">
|
||||
<button class="btn-secondary mr-2" @click="modal = ! modal">Cancel</button>
|
||||
<button class="btn-alert" @click="destroy"> Delete</button>
|
||||
<div class="relative">
|
||||
<img
|
||||
v-if="!loading"
|
||||
class="cover"
|
||||
:src="memo.attributes.cover_image.data.attributes.path"
|
||||
/>
|
||||
<div class="flex-col flex-between absolute memo-cover">
|
||||
<div class="flex-between px-2 py-1">
|
||||
<router-link to="/memos/" class="btn-secondary">< Back</router-link>
|
||||
<div v-if="modal" class="modal-container" @click="modal = ! modal">
|
||||
<div class="modal">
|
||||
<p class="m-1 text-center">Are you sure you want to delete this record ?</p>
|
||||
<div class="flex-center m-1 mt-2">
|
||||
<button class="btn-secondary mr-2" @click="modal = ! modal">Cancel</button>
|
||||
<button class="btn-alert" @click="destroy"> Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-middle">
|
||||
<router-link :to="'/memos/' + memo.memo_id + '/edit'" class="btn-secondary mr-1" >Edit</router-link>
|
||||
<a href="#" class="btn-alert" @click="modal = ! modal">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-middle">
|
||||
<router-link :to="'/memos/' + memo.memo_id + '/edit'" class="btn-secondary mr-1" >Edit</router-link>
|
||||
<a href="#" class="btn-alert" @click="modal = ! modal">Delete</a>
|
||||
<h1 class="memo-title flex-center">{{ memo.name }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <TagBox :memo="memo" />-->
|
||||
<h1 class="memo-title flex-center">{{ memo.name }}</h1>
|
||||
<p class="memo-style pt-1" v-html="memoMarkdown"></p>
|
||||
<div class="memo-change">@last update : {{ memo.last_updated }}</div>
|
||||
<div class="p-2">
|
||||
|
||||
<p class="memo-style pt-1" v-html="memoMarkdown"></p>
|
||||
<div class="memo-change">@last update : {{ memo.last_updated }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
1
resources/sass/app.scss
vendored
1
resources/sass/app.scss
vendored
@@ -15,6 +15,7 @@
|
||||
@import "components/nav";
|
||||
@import "components/topbar";
|
||||
@import "components/modal";
|
||||
@import "components/images";
|
||||
@import "components/avatar";
|
||||
@import "components/alert_box";
|
||||
@import "components/search_box";
|
||||
|
||||
10
resources/sass/components/images.scss
vendored
Normal file
10
resources/sass/components/images.scss
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.cover {
|
||||
width: 100%;
|
||||
//height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 50% 50%;
|
||||
}
|
||||
|
||||
.test {
|
||||
height: 30rem;
|
||||
}
|
||||
12
resources/sass/pages/memos.scss
vendored
12
resources/sass/pages/memos.scss
vendored
@@ -33,6 +33,18 @@
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: $white;
|
||||
text-shadow: 1px 1px 2px $dark;
|
||||
background-image: linear-gradient(transparent, rgba(0,0,0,0.7));
|
||||
}
|
||||
|
||||
&-cover {
|
||||
height: calc(100% - 5px);
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.memo-edit {
|
||||
|
||||
2
resources/sass/setup/_containers.scss
vendored
2
resources/sass/setup/_containers.scss
vendored
@@ -8,7 +8,7 @@
|
||||
.card {
|
||||
background-color: $light;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
//padding: 1rem;
|
||||
box-shadow: 1px 1px 2px $grey;
|
||||
border-radius: 2px;
|
||||
text-decoration: none;
|
||||
|
||||
20
resources/sass/setup/_positions.scss
vendored
20
resources/sass/setup/_positions.scss
vendored
@@ -55,6 +55,10 @@ $base: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-100{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.m-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
@@ -127,6 +131,22 @@ $base: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.t-0 {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.l-0 {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.r-0 {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.b-0 {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Route::middleware('auth:api')->group(function () {
|
||||
|
||||
Route::get('auth-user', 'AuthUserController@show');
|
||||
|
||||
|
||||
Route::apiResources([
|
||||
'/users' => 'UserController',
|
||||
'/memos' => 'MemosController',
|
||||
@@ -26,4 +27,7 @@ Route::middleware('auth:api')->group(function () {
|
||||
// '/friend-request' => 'FriendRequestController',
|
||||
]);
|
||||
|
||||
Route::post('/images/users/{users}', 'ImageController@users');
|
||||
Route::post('/images/memos/{memo}', 'ImageController@memos');
|
||||
|
||||
});
|
||||
|
||||
191
tests/Feature/ImagesTest.php
Normal file
191
tests/Feature/ImagesTest.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Image;
|
||||
use App\Models\Memo;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ImagesTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Storage::fake('public');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function images_can_be_uploaded()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user= factory(User::class)->create(), 'api');
|
||||
$file = UploadedFile::fake()->image('test-image.jpg');
|
||||
|
||||
$response = $this->post('/api/images/users/'.$user->id, [
|
||||
'image' => $file,
|
||||
'width' => '850',
|
||||
'height' => '300',
|
||||
'location' => 'cover',
|
||||
])->assertStatus(201);
|
||||
|
||||
Storage::disk('public')->assertExists('images/'.$file->hashName());
|
||||
$image = Image::first();
|
||||
$this->assertEquals('images/'.$file->hashName(), $image->path);
|
||||
$this->assertEquals(850, $image->width);
|
||||
$this->assertEquals(300, $image->height);
|
||||
$this->assertEquals('cover', $image->location);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'type' => 'images',
|
||||
'image_id' => $image->id,
|
||||
'attributes' => [
|
||||
'path' => url('storage/'.$image->path),
|
||||
'width' => $image->width,
|
||||
'height' => $image->height,
|
||||
'location' => $image->location,
|
||||
|
||||
]
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/images/'.$image->id),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function users_are_returned_with_their_images()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user= factory(User::class)->create(), 'api');
|
||||
$file = UploadedFile::fake()->image('user-image.jpg');
|
||||
|
||||
$this->post('/api/images/users/'.$user->id, [
|
||||
'image' => $file,
|
||||
'width' => 850,
|
||||
'height' => 300,
|
||||
'location' => 'cover',
|
||||
])->assertStatus(201);
|
||||
$this->post('/api/images/users/'.$user->id, [
|
||||
'image' => $file,
|
||||
'width' => 850,
|
||||
'height' => 300,
|
||||
'location' => 'profile',
|
||||
])->assertStatus(201);
|
||||
|
||||
$response = $this->get('/api/users/'.$user->id);
|
||||
|
||||
$userImage = Image::first();
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'user_id' => $user->id,
|
||||
'attributes' => [
|
||||
'name' => $user->name,
|
||||
'cover_image' => [
|
||||
'data' => [
|
||||
'type' => 'images',
|
||||
'image_id' => 1,
|
||||
'attributes' => []
|
||||
]
|
||||
],
|
||||
'profile_image' => [
|
||||
'data' => [
|
||||
'type' => 'images',
|
||||
'image_id' => 2,
|
||||
'attributes' => []
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function memos_are_returned_with_their_image_cover()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user= factory(User::class)->create(), 'api');
|
||||
$file = UploadedFile::fake()->image('user-image.jpg');
|
||||
$memo = factory(Memo::class)->create(['user_id' => $user->id]);
|
||||
|
||||
$memo->coverImage()->create([
|
||||
'path' => $file,
|
||||
'width' => 850,
|
||||
'height' => 300,
|
||||
'location' => 'cover',
|
||||
]);
|
||||
|
||||
$response = $this->get('/api/memos/'.$memo->id);
|
||||
|
||||
$memo = Memo::first();
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'type' => 'memos',
|
||||
'memo_id' => $memo->id,
|
||||
'name' => $memo->name,
|
||||
'memo' => $memo->memo,
|
||||
'last_updated' => $memo->updated_at->diffForHumans(),
|
||||
'attributes' => [
|
||||
'cover_image' => [
|
||||
'data' => [
|
||||
'type' => 'images',
|
||||
'image_id' => 1,
|
||||
'attributes' => []
|
||||
]
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function memos_images_cover_can_be_uploaded()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user= factory(User::class)->create(), 'api');
|
||||
$file = UploadedFile::fake()->image('user-image.jpg');
|
||||
$memo = factory(Memo::class)->create(['user_id' => $user->id, 'id' => 123]);
|
||||
|
||||
$this->post('/api/images/memos/'.$memo->id, [
|
||||
'image' => $file,
|
||||
'width' => 850,
|
||||
'height' => 300,
|
||||
'location' => 'cover',
|
||||
])->assertStatus(201);
|
||||
|
||||
$image = Image::first();
|
||||
$response = $this->get('/api/memos/123');
|
||||
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'type' => 'memos',
|
||||
'attributes' => [
|
||||
'cover_image' => [
|
||||
'data' => [
|
||||
'type' => 'images',
|
||||
'image_id' => $image->id,
|
||||
'attributes' => [
|
||||
'path' => url('storage/'.$image->path),
|
||||
'width' => $image->width,
|
||||
'height' => $image->height,
|
||||
'location' => $image->location,
|
||||
|
||||
]
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/images/'.$memo->coverImage->id),
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user