updated attachments
This commit is contained in:
@@ -8,17 +8,14 @@ use App\Enum\HttpResponseCodes;
|
|||||||
use App\Http\Requests\ArticleRequest;
|
use App\Http\Requests\ArticleRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Models\Gallery;
|
use App\Traits\HasAttachments;
|
||||||
use App\Traits\HasGallery;
|
use App\Traits\HasGallery;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Carbon\Exceptions\InvalidFormatException;
|
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
|
||||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
|
||||||
use Illuminate\Database\Eloquent\MassAssignmentException;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ArticleController extends ApiController
|
class ArticleController extends ApiController
|
||||||
{
|
{
|
||||||
|
use HasAttachments;
|
||||||
use HasGallery;
|
use HasGallery;
|
||||||
|
|
||||||
|
|
||||||
@@ -78,7 +75,11 @@ class ArticleController extends ApiController
|
|||||||
public function store(ArticleRequest $request)
|
public function store(ArticleRequest $request)
|
||||||
{
|
{
|
||||||
if (ArticleConductor::creatable() === true) {
|
if (ArticleConductor::creatable() === true) {
|
||||||
$article = Article::create($request->except('gallery'));
|
$article = Article::create($request->except(['attachments', 'gallery']));
|
||||||
|
|
||||||
|
if ($request->has('attachments') === true) {
|
||||||
|
$article->attachmentsAddMany($request->get('attachments'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('gallery') === true) {
|
if ($request->has('gallery') === true) {
|
||||||
$article->galleryAddMany($request->get('gallery'));
|
$article->galleryAddMany($request->get('gallery'));
|
||||||
@@ -103,12 +104,17 @@ class ArticleController extends ApiController
|
|||||||
public function update(ArticleRequest $request, Article $article)
|
public function update(ArticleRequest $request, Article $article)
|
||||||
{
|
{
|
||||||
if (ArticleConductor::updatable($article) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
|
if ($request->has('attachments') === true) {
|
||||||
|
$article->attachments()->delete();
|
||||||
|
$article->attachmentsAddMany($request->get('attachments'));
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('gallery') === true) {
|
if ($request->has('gallery') === true) {
|
||||||
$article->gallery()->delete();
|
$article->gallery()->delete();
|
||||||
$article->galleryAddMany($request->get('gallery'));
|
$article->galleryAddMany($request->get('gallery'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$article->update($request->except('gallery'));
|
$article->update($request->except(['attachments', 'gallery']));
|
||||||
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,125 +136,4 @@ class ArticleController extends ApiController
|
|||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of attachments related to this model.
|
|
||||||
*
|
|
||||||
* @param Request $request The user request.
|
|
||||||
* @param Article $article The article model.
|
|
||||||
* @return JsonResponse Returns the article attachments.
|
|
||||||
*/
|
|
||||||
public function attachmentIndex(Request $request, Article $article): JsonResponse
|
|
||||||
{
|
|
||||||
if (ArticleConductor::viewable($article) === true) {
|
|
||||||
$medium = $article->attachments->map(function ($attachment) {
|
|
||||||
return $attachment->media;
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this->respondAsResource(MediaConductor::collection($request, $medium), ['isCollection' => true, 'resourceName' => 'attachment']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondForbidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store an attachment related to this model.
|
|
||||||
*
|
|
||||||
* @param Request $request The user request.
|
|
||||||
* @param Article $article The article model.
|
|
||||||
* @return JsonResponse The response.
|
|
||||||
*/
|
|
||||||
public function attachmentStore(Request $request, Article $article): JsonResponse
|
|
||||||
{
|
|
||||||
if (ArticleConductor::updatable($article) === true) {
|
|
||||||
if ($request->has("medium") === true && Media::find($request->medium) !== null) {
|
|
||||||
$article->attachments()->create(['media_id' => $request->medium]);
|
|
||||||
return $this->respondCreated();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondWithErrors(['media' => 'The media ID was not found']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondForbidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update/replace attachments related to this model.
|
|
||||||
*
|
|
||||||
* @param Request $request The user request.
|
|
||||||
* @param Article $article The related model.
|
|
||||||
* @return JsonResponse
|
|
||||||
*/
|
|
||||||
public function attachmentUpdate(Request $request, Article $article): JsonResponse
|
|
||||||
{
|
|
||||||
if (ArticleConductor::updatable($article) === true) {
|
|
||||||
$mediaIds = $request->attachments;
|
|
||||||
if (is_array($mediaIds) === false) {
|
|
||||||
$mediaIds = explode(',', $request->attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
|
||||||
$attachments = $article->attachments;
|
|
||||||
|
|
||||||
// Delete attachments that are not in $mediaIds
|
|
||||||
foreach ($attachments as $attachment) {
|
|
||||||
if (in_array($attachment->media_id, $mediaIds) === false) {
|
|
||||||
$attachment->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new attachments for media IDs that are not already in $article->attachments()
|
|
||||||
foreach ($mediaIds as $mediaId) {
|
|
||||||
$found = false;
|
|
||||||
|
|
||||||
foreach ($attachments as $attachment) {
|
|
||||||
if ($attachment->media_id === $mediaId) {
|
|
||||||
$found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($found === false) {
|
|
||||||
$article->attachments()->create(['media_id' => $mediaId]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondNoContent();
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
return $this->respondForbidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a specific related attachment.
|
|
||||||
* @param Request $request The user request.
|
|
||||||
* @param Article $article The model.
|
|
||||||
* @param Media $medium The attachment medium.
|
|
||||||
* @return JsonResponse
|
|
||||||
*/
|
|
||||||
public function attachmentDelete(Request $request, Article $article, Media $medium): JsonResponse
|
|
||||||
{
|
|
||||||
if (ArticleConductor::updatable($article) === true) {
|
|
||||||
$attachments = $article->attachments;
|
|
||||||
$deleted = false;
|
|
||||||
|
|
||||||
foreach ($attachments as $attachment) {
|
|
||||||
if ($attachment->media_id === $medium->id) {
|
|
||||||
$attachment->delete();
|
|
||||||
$deleted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($deleted === true) {
|
|
||||||
// Attachment was deleted successfully
|
|
||||||
return $this->respondNoContent();
|
|
||||||
} else {
|
|
||||||
// Attachment with matching media ID was not found
|
|
||||||
return $this->respondNotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondForbidden();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,14 +39,4 @@ class Article extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all of the article's attachments.
|
|
||||||
*
|
|
||||||
* @return Illuminate\Database\Eloquent\Relations\MorphMany
|
|
||||||
*/
|
|
||||||
public function attachments(): MorphMany
|
|
||||||
{
|
|
||||||
return $this->morphMany(\App\Models\Attachment::class, 'attachable');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasAttachments;
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
|
|||||||
|
|
||||||
class Event extends Model
|
class Event extends Model
|
||||||
{
|
{
|
||||||
|
use HasAttachments;
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuids;
|
use Uuids;
|
||||||
|
|
||||||
@@ -37,16 +39,10 @@ class Event extends Model
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all of the article's attachments.
|
|
||||||
*/
|
|
||||||
public function attachments(): MorphMany
|
|
||||||
{
|
|
||||||
return $this->morphMany(\App\Models\Attachment::class, 'attachable');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the associated users.
|
* Get all the associated users.
|
||||||
|
*
|
||||||
|
* @return BelongsToMany
|
||||||
*/
|
*/
|
||||||
public function users(): BelongsToMany
|
public function users(): BelongsToMany
|
||||||
{
|
{
|
||||||
|
|||||||
63
app/Traits/HasAttachments.php
Normal file
63
app/Traits/HasAttachments.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
|
||||||
|
trait HasAttachments
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Boot function from Laravel.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function bootHasAttachments(): void
|
||||||
|
{
|
||||||
|
static::deleting(function ($model) {
|
||||||
|
$model->attachments()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add multiple attachments items to the model.
|
||||||
|
*
|
||||||
|
* @param array|string $ids The media ids to add.
|
||||||
|
* @param string $delimiter The split delimiter if $ids is a string.
|
||||||
|
* @param boolean $allowDuplicates Whether to allow duplicate media IDs or not.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function attachmentsAddMany(array|string $ids, string $delimiter = ',', bool $allowDuplicates = false): void
|
||||||
|
{
|
||||||
|
if (is_array($ids) === false) {
|
||||||
|
$ids = explode($delimiter, $ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_map('trim', $ids);
|
||||||
|
$existingIds = $this->gallery()->pluck('media_id')->toArray();
|
||||||
|
|
||||||
|
$galleryItems = [];
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if ($allowDuplicates === false && in_array($id, $existingIds) === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$media = Media::find($id);
|
||||||
|
if ($media !== null) {
|
||||||
|
$galleryItems[] = ['media_id' => $id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->attachments()->createMany($galleryItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the article's attachments.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\MorphMany The attachments items
|
||||||
|
*/
|
||||||
|
public function attachments(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(\App\Models\Attachment::class, 'addendum');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('attachments', function (Blueprint $table) {
|
||||||
|
$table->renameColumn('attachable_type', 'addendum_type');
|
||||||
|
$table->renameColumn('attachable_id', 'addendum_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('attachments', function (Blueprint $table) {
|
||||||
|
$table->renameColumn('addendum_type', 'attachable_type');
|
||||||
|
$table->renameColumn('addendum_id', 'attachable_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,28 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<SMHeader
|
<SMHeader
|
||||||
v-if="props.attachments && props.attachments.length > 0"
|
v-if="showEditor || (modelValue && modelValue.length > 0)"
|
||||||
|
:no-copy="props.showEditor"
|
||||||
text="Files" />
|
text="Files" />
|
||||||
|
<p v-if="props.showEditor" class="small">
|
||||||
|
{{ modelValue.length }} file{{ modelValue.length != 1 ? "s" : "" }}
|
||||||
|
</p>
|
||||||
<table
|
<table
|
||||||
v-if="props.attachments && props.attachments.length > 0"
|
v-if="modelValue && modelValue.length > 0"
|
||||||
class="w-full border-1 rounded-2 bg-white text-sm mt-2">
|
class="w-full border-1 rounded-2 bg-white text-sm mt-2">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="file of props.attachments" :key="file.id">
|
<tr v-for="file of modelValue" :key="file.id">
|
||||||
<td class="py-2 pl-2">
|
<td class="py-2 pl-2 hidden sm:block">
|
||||||
<img
|
<img
|
||||||
:src="getFileIconImagePath(file.name || file.title)"
|
:src="getFileIconImagePath(file.name || file.title)"
|
||||||
class="h-10 text-center" />
|
class="h-10 text-center" />
|
||||||
</td>
|
</td>
|
||||||
<td class="">
|
<td class="pl-2 py-4 w-full">
|
||||||
<a :href="file.url">{{ file.title || file.name }}</a>
|
<a :href="file.url">{{ file.title || file.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="">
|
<td class="pr-2">
|
||||||
<a :href="file.url + '?download=1'"
|
<a :href="file.url + '?download=1'"
|
||||||
><svg
|
><svg
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-5 text-gray">
|
class="h-7 pt-1 text-gray">
|
||||||
<path
|
<path
|
||||||
d="M12 10V20M12 20L9.5 17.5M12 20L14.5 17.5"
|
d="M12 10V20M12 20L9.5 17.5M12 20L14.5 17.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -37,12 +41,20 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-xs text-gray">
|
<td
|
||||||
|
class="text-xs text-gray whitespace-nowrap pr-2 py-2 hidden sm:table-cell">
|
||||||
({{ bytesReadable(file.size) }})
|
({{ bytesReadable(file.size) }})
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<button
|
||||||
|
v-if="props.showEditor"
|
||||||
|
type="button"
|
||||||
|
class="font-medium mt-4 px-6 py-1.5 rounded-md hover:shadow-md transition text-sm bg-sky-600 hover:bg-sky-500 text-white cursor-pointer"
|
||||||
|
@click="handleClickAdd">
|
||||||
|
Add File
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -50,16 +62,53 @@
|
|||||||
import { bytesReadable } from "../helpers/types";
|
import { bytesReadable } from "../helpers/types";
|
||||||
import { getFileIconImagePath } from "../helpers/utils";
|
import { getFileIconImagePath } from "../helpers/utils";
|
||||||
import SMHeader from "../components/SMHeader.vue";
|
import SMHeader from "../components/SMHeader.vue";
|
||||||
|
import { openDialog } from "../components/SMDialog";
|
||||||
|
import SMDialogMedia from "./dialogs/SMDialogMedia.vue";
|
||||||
|
import { Media } from "../helpers/api.types";
|
||||||
|
|
||||||
|
const emits = defineEmits(["update:modelValue"]);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
attachments: {
|
modelValue: {
|
||||||
type: Object,
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
showEditor: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the user adding a new media item.
|
||||||
|
*/
|
||||||
|
const handleClickAdd = async () => {
|
||||||
|
let result = await openDialog(SMDialogMedia, {
|
||||||
|
mime: "",
|
||||||
|
accepts: "",
|
||||||
|
allowUpload: true,
|
||||||
|
multiple: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
const mediaResult = result as Media[];
|
||||||
|
let newValue = props.modelValue;
|
||||||
|
let mediaIds = new Set(newValue.map((item) => item.id));
|
||||||
|
|
||||||
|
mediaResult.forEach((item) => {
|
||||||
|
if (!mediaIds.has(item.id)) {
|
||||||
|
newValue.push(item);
|
||||||
|
mediaIds.add(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emits("update:modelValue", newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<!-- <style lang="scss">
|
||||||
.attachment-list {
|
.attachment-list {
|
||||||
border: 1px solid var(--base-color);
|
border: 1px solid var(--base-color);
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -154,4 +203,4 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style> -->
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
<component
|
<component
|
||||||
:is="`h${props.size}`"
|
:is="`h${props.size}`"
|
||||||
:id="id"
|
:id="id"
|
||||||
class="sm-header cursor-pointer"
|
:class="['sm-header', props.noCopy ? '' : 'cursor-pointer']"
|
||||||
@click.prevent="copyAnchor">
|
@click.prevent="copyAnchor">
|
||||||
{{ props.text }}
|
{{ props.text }}
|
||||||
<span class="pl-2 text-sky-5 opacity-75 hidden">#</span>
|
<span v-if="!props.noCopy" class="pl-2 text-sky-5 opacity-75 hidden"
|
||||||
|
>#</span
|
||||||
|
>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -28,6 +30,11 @@ const props = defineProps({
|
|||||||
default: "",
|
default: "",
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
noCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const computedHeaderId = (text: string): string => {
|
const computedHeaderId = (text: string): string => {
|
||||||
@@ -35,29 +42,32 @@ const computedHeaderId = (text: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const id = ref(
|
const id = ref(
|
||||||
props.id && props.id.length > 0 ? props.id : computedHeaderId(props.text)
|
props.id && props.id.length > 0 ? props.id : computedHeaderId(props.text),
|
||||||
);
|
);
|
||||||
|
|
||||||
const copyAnchor = () => {
|
const copyAnchor = () => {
|
||||||
const currentUrl = window.location.href.replace(/#.*/, "");
|
if (props.noCopy === false) {
|
||||||
const newUrl = currentUrl + "#" + id.value;
|
const currentUrl = window.location.href.replace(/#.*/, "");
|
||||||
|
const newUrl = currentUrl + "#" + id.value;
|
||||||
|
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(newUrl)
|
.writeText(newUrl)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
useToastStore().addToast({
|
useToastStore().addToast({
|
||||||
title: "Copied to Clipboard",
|
title: "Copied to Clipboard",
|
||||||
content: "The heading URL has been copied to the clipboard.",
|
content:
|
||||||
type: "success",
|
"The heading URL has been copied to the clipboard.",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
useToastStore().addToast({
|
||||||
|
title: "Copy to Clipboard",
|
||||||
|
content: "Failed to copy the heading URL to the clipboard.",
|
||||||
|
type: "danger",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
}
|
||||||
.catch(() => {
|
|
||||||
useToastStore().addToast({
|
|
||||||
title: "Copy to Clipboard",
|
|
||||||
content: "Failed to copy the heading URL to the clipboard.",
|
|
||||||
type: "danger",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="input-attachments">
|
<div class="input-attachments">
|
||||||
<label>Files</label>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="mediaItems.length == 0" class="attachments-none">
|
<li v-if="mediaItems.length == 0" class="attachments-none">
|
||||||
<ion-icon name="sad-outline"></ion-icon>
|
<ion-icon name="sad-outline"></ion-icon>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
:model-value="article.gallery" />
|
:model-value="article.gallery" />
|
||||||
<SMAttachments
|
<SMAttachments
|
||||||
v-if="article.attachments.length > 0"
|
v-if="article.attachments.length > 0"
|
||||||
:attachments="article.attachments || []" />
|
:model-value="article.attachments || []" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -136,6 +136,8 @@ const handleLoad = async () => {
|
|||||||
"large",
|
"large",
|
||||||
);
|
);
|
||||||
applicationStore.setDynamicTitle(article.value.title);
|
applicationStore.setDynamicTitle(article.value.title);
|
||||||
|
|
||||||
|
console.log(article.value);
|
||||||
} else {
|
} else {
|
||||||
pageStatus.value = 404;
|
pageStatus.value = 404;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,10 @@
|
|||||||
</p>
|
</p>
|
||||||
<SMImageGallery show-editor v-model:model-value="gallery" />
|
<SMImageGallery show-editor v-model:model-value="gallery" />
|
||||||
</div>
|
</div>
|
||||||
<SMInputAttachments v-model:model-value="attachments" />
|
<SMAttachments
|
||||||
|
class="mb-8"
|
||||||
|
show-editor
|
||||||
|
v-model:model-value="attachments" />
|
||||||
<div class="flex flex-justify-end">
|
<div class="flex flex-justify-end">
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -75,7 +78,7 @@ import SMEditor from "../../components/SMEditor.vue";
|
|||||||
import SMForm from "../../components/SMForm.vue";
|
import SMForm from "../../components/SMForm.vue";
|
||||||
import SMInput from "../../components/SMInput.vue";
|
import SMInput from "../../components/SMInput.vue";
|
||||||
import SMDropdown from "../../components/SMDropdown.vue";
|
import SMDropdown from "../../components/SMDropdown.vue";
|
||||||
import SMInputAttachments from "../../components/SMInputAttachments.vue";
|
import SMAttachments from "../../components/SMAttachments.vue";
|
||||||
import { api } from "../../helpers/api";
|
import { api } from "../../helpers/api";
|
||||||
import { ArticleResponse, UserCollection } from "../../helpers/api.types";
|
import { ArticleResponse, UserCollection } from "../../helpers/api.types";
|
||||||
import { SMDate } from "../../helpers/datetime";
|
import { SMDate } from "../../helpers/datetime";
|
||||||
@@ -181,12 +184,7 @@ const loadData = async () => {
|
|||||||
form.controls.content.value = data.article.content;
|
form.controls.content.value = data.article.content;
|
||||||
form.controls.hero.value = data.article.hero;
|
form.controls.hero.value = data.article.hero;
|
||||||
|
|
||||||
attachments.value = (data.article.attachments || []).map(
|
attachments.value = data.article.attachments;
|
||||||
function (attachment) {
|
|
||||||
return attachment.id.toString();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
gallery.value = data.article.gallery;
|
gallery.value = data.article.gallery;
|
||||||
} else {
|
} else {
|
||||||
pageError.value = 404;
|
pageError.value = 404;
|
||||||
@@ -211,6 +209,7 @@ const handleSubmit = async () => {
|
|||||||
content: form.controls.content.value,
|
content: form.controls.content.value,
|
||||||
hero: form.controls.hero.value.id,
|
hero: form.controls.hero.value.id,
|
||||||
gallery: gallery.value.map((item) => item.id),
|
gallery: gallery.value.map((item) => item.id),
|
||||||
|
attachments: attachments.value.map((item) => item.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
let article_id = "";
|
let article_id = "";
|
||||||
@@ -236,13 +235,6 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.put({
|
|
||||||
url: `/articles/${article_id}/attachments`,
|
|
||||||
body: {
|
|
||||||
attachments: attachments.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useToastStore().addToast({
|
useToastStore().addToast({
|
||||||
title: route.params.id ? "Article Updated" : "Article Created",
|
title: route.params.id ? "Article Updated" : "Article Created",
|
||||||
content: route.params.id
|
content: route.params.id
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Route::apiResource('media', MediaController::class);
|
|||||||
Route::get('media/{medium}/download', [MediaController::class, 'download']);
|
Route::get('media/{medium}/download', [MediaController::class, 'download']);
|
||||||
|
|
||||||
Route::apiResource('articles', ArticleController::class);
|
Route::apiResource('articles', ArticleController::class);
|
||||||
Route::apiAddendumResource('attachments', 'articles', ArticleController::class);
|
// Route::apiAddendumResource('attachments', 'articles', ArticleController::class);
|
||||||
|
|
||||||
Route::apiResource('events', EventController::class);
|
Route::apiResource('events', EventController::class);
|
||||||
Route::apiAddendumResource('attachments', 'events', EventController::class);
|
Route::apiAddendumResource('attachments', 'events', EventController::class);
|
||||||
|
|||||||
Reference in New Issue
Block a user