mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Make admin content list draggable for reordering
This commit is contained in:
@@ -11,6 +11,8 @@ use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AdminContentController extends BasePageController
|
||||
@@ -24,12 +26,23 @@ class AdminContentController extends BasePageController
|
||||
{
|
||||
$this->setAdminPrefs();
|
||||
|
||||
$contentList = Content::query()
|
||||
->orderByRaw('contenttype, COALESCE(ordinal, 1000000)')
|
||||
->get();
|
||||
$contentList = Content::query()->ordered()->get();
|
||||
$contentGroups = $contentList
|
||||
->groupBy('contenttype')
|
||||
->sortKeys()
|
||||
->map(function ($items, $contentType) {
|
||||
/** @var Collection<int, Content> $items */
|
||||
return [
|
||||
'contenttype' => (int) $contentType,
|
||||
'label' => $items->first()?->getContentTypeLabel() ?? 'Unknown',
|
||||
'items' => $items,
|
||||
];
|
||||
})
|
||||
->values();
|
||||
|
||||
$this->viewData = array_merge($this->viewData, [
|
||||
'contentlist' => $contentList,
|
||||
'contentGroups' => $contentGroups,
|
||||
'meta_title' => 'Content List',
|
||||
'title' => 'Content List',
|
||||
]);
|
||||
@@ -154,6 +167,63 @@ class AdminContentController extends BasePageController
|
||||
], 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a new global content order.
|
||||
*/
|
||||
public function reorder(Request $request): mixed
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'contenttype' => ['required', 'integer'],
|
||||
'ordered_ids' => ['required', 'array', 'min:1'],
|
||||
'ordered_ids.*' => ['integer', 'distinct', 'exists:content,id'],
|
||||
]);
|
||||
|
||||
$contentType = (int) $validated['contenttype'];
|
||||
$orderedIds = array_map('intval', $validated['ordered_ids']);
|
||||
$expectedIds = Content::query()
|
||||
->where('contenttype', $contentType)
|
||||
->pluck('id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->all();
|
||||
|
||||
if (
|
||||
count($orderedIds) !== count($expectedIds)
|
||||
|| array_diff($orderedIds, $expectedIds) !== []
|
||||
|| array_diff($expectedIds, $orderedIds) !== []
|
||||
) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Reorder payload must include every content row for the selected group.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$timestamp = now();
|
||||
$ordinals = [];
|
||||
|
||||
DB::transaction(function () use ($contentType, $orderedIds, $timestamp, &$ordinals): void {
|
||||
foreach ($orderedIds as $index => $contentId) {
|
||||
$ordinal = $index + 1;
|
||||
|
||||
Content::query()
|
||||
->whereKey($contentId)
|
||||
->where('contenttype', $contentType)
|
||||
->update([
|
||||
'ordinal' => $ordinal,
|
||||
'updated_at' => $timestamp,
|
||||
]);
|
||||
|
||||
$ordinals[$contentId] = $ordinal;
|
||||
}
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Content order updated successfully',
|
||||
'contenttype' => $contentType,
|
||||
'ordinals' => $ordinals,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete content by ID.
|
||||
*/
|
||||
@@ -200,11 +270,6 @@ class AdminContentController extends BasePageController
|
||||
// Normalize URL
|
||||
$data = $this->normalizeContentUrl($data);
|
||||
|
||||
// If ordinal is 1, increment all existing ordinals
|
||||
if (($data['ordinal'] ?? 0) === 1) {
|
||||
Content::query()->where('ordinal', '>', 0)->increment('ordinal');
|
||||
}
|
||||
|
||||
return Content::query()->insertGetId([
|
||||
'role' => $data['role'] ?? Content::ROLE_EVERYONE,
|
||||
'title' => $data['title'] ?? '',
|
||||
@@ -214,7 +279,7 @@ class AdminContentController extends BasePageController
|
||||
'metakeywords' => $data['metakeywords'] ?? '',
|
||||
'contenttype' => $data['contenttype'] ?? Content::TYPE_USEFUL,
|
||||
'status' => $data['status'] ?? Content::STATUS_ENABLED,
|
||||
'ordinal' => $data['ordinal'] ?? 0,
|
||||
'ordinal' => $this->nextBottomOrdinal((int) ($data['contenttype'] ?? Content::TYPE_USEFUL)),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
@@ -230,8 +295,14 @@ class AdminContentController extends BasePageController
|
||||
// Normalize URL
|
||||
$data = $this->normalizeContentUrl($data);
|
||||
|
||||
$content = Content::query()->findOrFail($data['id']);
|
||||
$updatedContentType = (int) ($data['contenttype'] ?? $content->contenttype);
|
||||
$ordinal = $content->contenttype === $updatedContentType
|
||||
? $content->ordinal
|
||||
: $this->nextBottomOrdinal($updatedContentType);
|
||||
|
||||
return Content::query()
|
||||
->where('id', $data['id'])
|
||||
->whereKey($content->id)
|
||||
->update([
|
||||
'role' => $data['role'] ?? Content::ROLE_EVERYONE,
|
||||
'title' => $data['title'] ?? '',
|
||||
@@ -239,9 +310,9 @@ class AdminContentController extends BasePageController
|
||||
'body' => $data['body'] ?? '',
|
||||
'metadescription' => $data['metadescription'] ?? '',
|
||||
'metakeywords' => $data['metakeywords'] ?? '',
|
||||
'contenttype' => $data['contenttype'] ?? Content::TYPE_USEFUL,
|
||||
'contenttype' => $updatedContentType,
|
||||
'status' => $data['status'] ?? Content::STATUS_ENABLED,
|
||||
'ordinal' => $data['ordinal'] ?? 0,
|
||||
'ordinal' => $ordinal,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
@@ -286,4 +357,20 @@ class AdminContentController extends BasePageController
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ordinal that places new content at the bottom of the list.
|
||||
*/
|
||||
protected function nextBottomOrdinal(int $contentType): int
|
||||
{
|
||||
$maximumOrdinal = Content::query()
|
||||
->where('contenttype', $contentType)
|
||||
->max('ordinal');
|
||||
|
||||
if ($maximumOrdinal === null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int) $maximumOrdinal + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,7 @@ class ContentController extends BasePageController
|
||||
*/
|
||||
protected function getActiveContent(): Collection // @phpstan-ignore class.notFound, missingType.generics, return.phpDocType
|
||||
{
|
||||
return Content::active()
|
||||
->orderByRaw('contenttype, COALESCE(ordinal, 1000000)')
|
||||
->get();
|
||||
return Content::active()->ordered()->get(); // @phpstan-ignore method.notFound
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +101,7 @@ class ContentController extends BasePageController
|
||||
{
|
||||
return Content::query()
|
||||
->where('id', '<>', 1)
|
||||
->orderByRaw('contenttype, COALESCE(ordinal, 1000000)')
|
||||
->ordered() // @phpstan-ignore method.notFound
|
||||
->get();
|
||||
}
|
||||
|
||||
@@ -133,6 +131,9 @@ class ContentController extends BasePageController
|
||||
*/
|
||||
protected function getIndexContent(): ?Content
|
||||
{
|
||||
return Content::active()->ofType(Content::TYPE_INDEX)->first();
|
||||
return Content::active()
|
||||
->ofType(Content::TYPE_INDEX)
|
||||
->ordered() // @phpstan-ignore method.notFound
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -35,6 +35,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Content ofType($type)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Content forRole($role)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Content frontPage()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Content ordered()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*
|
||||
@@ -121,6 +122,16 @@ class Content extends Model
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Order content by type and ordinal within each type.
|
||||
*/
|
||||
public function scopeOrdered(Builder $query): Builder // @phpstan-ignore missingType.generics
|
||||
{
|
||||
return $query
|
||||
->orderBy('contenttype')
|
||||
->orderByRaw('COALESCE(ordinal, 1000000) ASC, id ASC');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Get front page content.
|
||||
*/
|
||||
@@ -128,7 +139,7 @@ class Content extends Model
|
||||
{
|
||||
return $query->active() // @phpstan-ignore method.notFound
|
||||
->ofType(self::TYPE_INDEX)
|
||||
->orderByRaw('ordinal ASC, COALESCE(ordinal, 1000000), id');
|
||||
->ordered(); // @phpstan-ignore method.notFound
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,7 +64,7 @@ class GlobalDataComposer
|
||||
$viewData['usefulLinks'] = Cache::remember('content_useful_links', self::CACHE_TTL, function () {
|
||||
return Content::active()
|
||||
->ofType(Content::TYPE_USEFUL)
|
||||
->orderBy('ordinal')
|
||||
->ordered() // @phpstan-ignore method.notFound
|
||||
->get();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user