Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions app/Http/Controllers/Admin/NodeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ public function store(StoreNodeRequest $request, Subject $subject)
->firstOrFail();
}

$slug = Str::slug($validated['name']);
$slug = !empty($validated['slug'])
? Str::slug($validated['slug'])
: Str::slug($validated['name']);

$exists = Node::where('subject_id', $subject->id)
->where('parent_id', $parent?->id)
Expand All @@ -104,7 +106,7 @@ public function store(StoreNodeRequest $request, Subject $subject)
if ($exists) {
return back()
->withErrors([
'name' => 'Folder already exists in this location.',
'slug' => 'Folder with this slug already exists in this location.',
])
->withInput();
}
Expand Down Expand Up @@ -143,8 +145,14 @@ public function update(UpdateNodeRequest $request, Subject $subject, Node $node)

if (array_key_exists('name', $validated)) {
$node->name = $validated['name'];
}

if (array_key_exists('name', $validated) || array_key_exists('slug', $validated) || array_key_exists('parent_id', $validated)) {
$rawSlug = !empty($validated['slug'])
? $validated['slug']
: ($validated['name'] ?? $node->name);

$slug = Str::slug($validated['name']);
$slug = Str::slug($rawSlug);

$parentId = array_key_exists('parent_id', $validated)
? $validated['parent_id']
Expand All @@ -158,7 +166,7 @@ public function update(UpdateNodeRequest $request, Subject $subject, Node $node)

if ($exists) {
return back()->withErrors([
'name' => 'Folder already exists in this location.',
'slug' => 'Folder with this slug already exists in this location.',
])->withInput();
}

Expand Down
1 change: 1 addition & 0 deletions app/Http/Requests/Node/StoreNodeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function rules(): array
{
return [
'name' => ['required', 'string', 'max:200', 'min:2'],
'slug' => ['nullable', 'string', 'max:200'],
'parent_id' => ['nullable', 'integer'],
'sort_order' => ['sometimes', 'integer'],
'redirect' => ['nullable', 'string'],
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Requests/Node/UpdateNodeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public function authorize(): bool
public function rules(): array
{
return [

'name' => ['sometimes', 'required', 'string', 'max:200', 'min:2'],
'slug' => ['sometimes', 'nullable', 'string', 'max:200'],
'parent_id' => ['sometimes', 'nullable', 'integer'],
'sort_order' => ['sometimes', 'nullable', 'integer'],
'redirect' => ['nullable', 'string'],
Expand Down
70 changes: 67 additions & 3 deletions resources/js/pages/admin/NodeCreateOrEdit.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3';
import { FolderOpen } from 'lucide-vue-next';
import { FolderOpen, ChevronDown, ChevronRight } from 'lucide-vue-next';
import { ref } from 'vue';

const props = defineProps({
subject: Object,
Expand All @@ -9,17 +10,20 @@ const props = defineProps({
node: Object,
});

const showSlug = ref(false);

const form = useForm({
name: props.node?.name || '',
slug: props.node?.slug || '',
parent_id: props.node?.parent_id || props.parent?.id || null,
sort_order: props.node?.sort_order ?? 0,
redirect: props.redirect,
});

function getInputClass(hasError) {
return hasError
? 'border-rose-500 focus:ring-rose-500/20'
: 'border-slate-300 dark:border-gray-600 focus:ring-blue-500/20 focus:border-blue-500';
? 'border-rose-500 focus:ring-rose-500/20 bg-white dark:bg-gray-900 text-slate-900 dark:text-gray-100 placeholder:text-slate-400 dark:placeholder:text-gray-500'
: 'border-slate-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-slate-900 dark:text-gray-100 placeholder:text-slate-400 dark:placeholder:text-gray-500 focus:ring-blue-500/20 focus:border-blue-500';
}

const submitForm = () => {
Expand Down Expand Up @@ -160,6 +164,66 @@ const goBack = () => {
Lower numbers will appear first in the list.
</p>
</div>

<div class="pt-1 md:col-span-3">
<button
type="button"
@click="showSlug = !showSlug"
class="inline-flex items-center gap-1 text-xs font-semibold text-slate-500 transition hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400"
>
<component
:is="
showSlug || form.errors.slug
? ChevronDown
: ChevronRight
"
class="h-3.5 w-3.5"
/>
<span
>Change slug?
<span
class="font-normal text-slate-400 dark:text-gray-500"
>(advanced)</span
></span
>
</button>

<div
v-if="showSlug || form.errors.slug"
class="mt-3 space-y-1.5 rounded-xl border border-slate-200/80 bg-slate-50/50 p-4 dark:border-gray-700 dark:bg-gray-800/50"
>
<label
for="slug"
class="block text-xs font-semibold text-slate-700 dark:text-gray-300"
>
Custom URL Slug
<span
class="text-[11px] font-normal text-slate-400 dark:text-gray-500"
>(Optional)</span
>
</label>
<input
v-model="form.slug"
type="text"
id="slug"
placeholder="e.g., chapter-1-intro (leave blank to auto-generate from name)"
class="w-full rounded-lg border px-3.5 py-2 text-sm transition outline-none"
:class="getInputClass(form.errors.slug)"
/>
<p
v-if="form.errors.slug"
class="mt-1 text-xs text-rose-600"
>
{{ form.errors.slug }}
</p>
<p
class="text-[11px] text-slate-400 dark:text-gray-500"
>
Leave empty to automatically generate from the
folder name.
</p>
</div>
</div>
</div>

<div
Expand Down