Skip to content

Commit 963c1fb

Browse files
Cyperghostdtdesign
andauthored
Implement sorting for attachments (#6259)
* Implement sorting for attachments * Simplify and fix the attachment sorting --------- Co-authored-by: Alexander Ebert <ebert@woltlab.com>
1 parent 82415aa commit 963c1fb

5 files changed

Lines changed: 174 additions & 1 deletion

File tree

ts/WoltLabSuite/Core/Component/Attachment/List.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { CkeditorDropEvent } from "../File/Upload";
33
import { createAttachmentFromFile } from "./Entry";
44
import { listenToCkeditor } from "../Ckeditor/Event";
55
import { getTabMenu } from "../Message/MessageTabMenu";
6+
import Sortable from "sortablejs";
7+
import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
8+
import { postObject } from "WoltLabSuite/Core/Api/PostObject";
69

710
function fileToAttachment(fileList: HTMLElement, file: WoltlabCoreFileElement, editor: HTMLElement): void {
811
fileList.append(createAttachmentFromFile(file, editor));
@@ -44,6 +47,36 @@ export function setup(editorId: string): void {
4447
uploadButton.insertAdjacentElement("afterend", fileList);
4548
}
4649

50+
new Sortable(fileList, {
51+
direction: "vertical",
52+
dragClass: ".fileList__item",
53+
ghostClass: "fileList__item--ghost",
54+
handle: ".fileList__item__file",
55+
animation: 150,
56+
fallbackOnBody: true,
57+
onChange(event) {
58+
const file = event.item.querySelector("woltlab-core-file")!;
59+
const thumbnail = file.thumbnails.find((thumbnail) => thumbnail.identifier === "tiny");
60+
if (thumbnail !== undefined) {
61+
file.thumbnail = thumbnail;
62+
} else if (file.link) {
63+
file.previewUrl = file.link;
64+
}
65+
},
66+
onEnd: promiseMutex(async (event) => {
67+
if (event.oldIndex === event.newIndex) {
68+
return;
69+
}
70+
71+
const attachmentIDs = Array.from(fileList.querySelectorAll("woltlab-core-file"))
72+
.map((file) => file.data?.attachmentID)
73+
.filter((attachmentID) => attachmentID !== undefined);
74+
const context = JSON.parse(uploadButton.dataset.context!);
75+
76+
await postObject(`${window.WSC_RPC_API_URL}core/attachments/show-order`, { ...context, attachmentIDs });
77+
}),
78+
});
79+
4780
let showOrder = -1;
4881
uploadButton.addEventListener("uploadStart", (event: CustomEvent<WoltlabCoreFileElement>) => {
4982
fileToAttachment(fileList, event.detail, editor);

wcfsetup/install/files/js/WoltLabSuite/Core/Component/Attachment/List.js

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) {
238238
$event->register(new \wcf\system\endpoint\controller\core\smilies\ChangeShowOrder());
239239
$event->register(new \wcf\system\endpoint\controller\core\smilies\categories\GetSmileyShowOrder());
240240
$event->register(new \wcf\system\endpoint\controller\core\smilies\categories\ChangeSmileyShowOrder());
241+
$event->register(new \wcf\system\endpoint\controller\core\attachments\ChangeShowOrder());
241242
}
242243
);
243244

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace wcf\system\endpoint\controller\core\attachments;
4+
5+
use Laminas\Diactoros\Response\JsonResponse;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use wcf\http\Helper;
9+
use wcf\system\attachment\AttachmentHandler;
10+
use wcf\system\endpoint\IController;
11+
use wcf\system\endpoint\PostRequest;
12+
use wcf\system\exception\PermissionDeniedException;
13+
use wcf\system\WCF;
14+
15+
/**
16+
* API endpoint for changing the show order of attachments.
17+
*
18+
* @author Olaf Braun
19+
* @copyright 2001-2025 WoltLab GmbH
20+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
21+
* @since 6.2
22+
*/
23+
#[PostRequest('/core/attachments/show-order')]
24+
final class ChangeShowOrder implements IController
25+
{
26+
#[\Override]
27+
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
28+
{
29+
$parameters = Helper::mapApiParameters($request, ChangeShowOrderParameters::class);
30+
31+
$attachmentHandler = new AttachmentHandler(
32+
$parameters->objectType,
33+
$parameters->objectID,
34+
$parameters->tmpHash,
35+
$parameters->parentObjectID,
36+
);
37+
38+
$this->assertAttachmentsCanBeSorted($attachmentHandler, $parameters->attachmentIDs);
39+
40+
$this->saveShowOrder($parameters->attachmentIDs);
41+
42+
return new JsonResponse([]);
43+
}
44+
45+
/**
46+
* @param list<int> $attachmentIDs
47+
*/
48+
private function assertAttachmentsCanBeSorted(AttachmentHandler $attachmentHandler, array $attachmentIDs): void
49+
{
50+
if (!$attachmentHandler->canUpload()) {
51+
throw new PermissionDeniedException();
52+
}
53+
54+
$attachmentList = $attachmentHandler->getAttachmentList();
55+
foreach ($attachmentIDs as $attachmentID) {
56+
if (!\in_array($attachmentID, $attachmentList->getObjectIDs(), true)) {
57+
throw new PermissionDeniedException();
58+
}
59+
}
60+
}
61+
62+
/**
63+
* @param list<int> $attachmentIDs
64+
*/
65+
private function saveShowOrder(array $attachmentIDs): void
66+
{
67+
WCF::getDB()->beginTransaction();
68+
69+
$sql = "UPDATE wcf1_attachment
70+
SET showOrder = ?
71+
WHERE attachmentID = ?";
72+
$statement = WCF::getDB()->prepare($sql);
73+
74+
foreach ($attachmentIDs as $showOrder => $attachmentID) {
75+
$statement->execute([
76+
$showOrder + 1,
77+
$attachmentID,
78+
]);
79+
}
80+
81+
WCF::getDB()->commitTransaction();
82+
}
83+
}
84+
85+
/** @internal */
86+
final class ChangeShowOrderParameters
87+
{
88+
public function __construct(
89+
/** @var non-empty-string */
90+
public readonly string $objectType,
91+
/** @var non-negative-int */
92+
public readonly int $objectID,
93+
/** @var non-negative-int */
94+
public readonly int $parentObjectID,
95+
public readonly string $tmpHash,
96+
/** @var list<positive-int> */
97+
public readonly array $attachmentIDs,
98+
) {
99+
}
100+
}

wcfsetup/install/files/style/ui/fileList.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
color: var(--wcfStatusErrorText);
4040
}
4141

42+
.fileList__item--ghost {
43+
opacity: 0.54;
44+
}
45+
4246
.fileList__item .innerError {
4347
grid-area: error;
4448
}
@@ -106,3 +110,9 @@ woltlab-core-file img {
106110
.woltlabCoreFileUpload__input::-webkit-file-upload-button {
107111
cursor: pointer;
108112
}
113+
114+
@media (hover: hover) {
115+
.fileList__item__file:hover {
116+
cursor: move;
117+
}
118+
}

0 commit comments

Comments
 (0)