diff --git a/extensions/messages/extend.php b/extensions/messages/extend.php
index 2f2394922..6f7037ced 100644
--- a/extensions/messages/extend.php
+++ b/extensions/messages/extend.php
@@ -52,6 +52,8 @@ return [
->fields(fn () => [
Schema\Boolean::make('canSendAnyMessage')
->get(fn (User $user, Context $context) => $user->can('sendAnyMessage')),
+ Schema\Boolean::make('canDeleteOwnMessages')
+ ->visible(fn (User $user, Context $context) => $context->getActor()->is($user)),
Schema\Integer::make('messageCount')
->get(function (object $model, Context $context) {
return Dialog::whereVisibleTo($context->getActor())
diff --git a/extensions/messages/js/src/admin/extend.ts b/extensions/messages/js/src/admin/extend.ts
deleted file mode 100644
index 735aaaf2b..000000000
--- a/extensions/messages/js/src/admin/extend.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import Extend from 'flarum/common/extenders';
-import commonExtend from '../common/extend';
-import app from 'flarum/admin/app';
-
-export default [
- ...commonExtend,
-
- new Extend.Admin().permission(
- () => ({
- icon: 'fas fa-envelope-open-text',
- label: app.translator.trans('flarum-messages.admin.permissions.send_messages'),
- permission: 'dialog.sendMessage',
- allowGuest: false,
- }),
- 'start',
- 95
- ),
-];
diff --git a/extensions/messages/js/src/admin/extend.tsx b/extensions/messages/js/src/admin/extend.tsx
new file mode 100644
index 000000000..41a28e150
--- /dev/null
+++ b/extensions/messages/js/src/admin/extend.tsx
@@ -0,0 +1,45 @@
+import Extend from 'flarum/common/extenders';
+import commonExtend from '../common/extend';
+import app from 'flarum/admin/app';
+import SettingDropdown from 'flarum/admin/components/SettingDropdown';
+
+export default [
+ ...commonExtend,
+
+ new Extend.Admin()
+ .permission(
+ () => ({
+ icon: 'fas fa-envelope-open-text',
+ label: app.translator.trans('flarum-messages.admin.permissions.send_messages_label'),
+ permission: 'dialog.sendMessage',
+ allowGuest: false,
+ }),
+ 'start',
+ 95
+ )
+ .permission(
+ () => ({
+ icon: 'far fa-trash-alt',
+ label: app.translator.trans('flarum-messages.admin.permissions.delete_own_messages_label'),
+ id: 'flarum-messages.allow_delete_own_messages',
+ setting: () => {
+ const minutes = parseInt(app.data.settings['flarum-messages.allow_delete_own_messages'], 10);
+
+ return (
+
+ );
+ },
+ }),
+ 'reply',
+ 80
+ ),
+];
diff --git a/extensions/messages/js/src/common/extend.ts b/extensions/messages/js/src/common/extend.ts
index 59286e0b2..e5bf90f5b 100644
--- a/extensions/messages/js/src/common/extend.ts
+++ b/extensions/messages/js/src/common/extend.ts
@@ -9,5 +9,6 @@ export default [
.add('dialog-messages', DialogMessage), //
new Extend.Model(User) //
- .attribute('canSendAnyMessage'),
+ .attribute('canSendAnyMessage')
+ .attribute('canDeleteOwnMessage'),
];
diff --git a/extensions/messages/js/src/common/models/DialogMessage.ts b/extensions/messages/js/src/common/models/DialogMessage.ts
index 4e88179b0..187fc531b 100644
--- a/extensions/messages/js/src/common/models/DialogMessage.ts
+++ b/extensions/messages/js/src/common/models/DialogMessage.ts
@@ -36,4 +36,8 @@ export default class DialogMessage extends Model {
user() {
return Model.hasOne('user').call(this);
}
+
+ canDelete() {
+ return Model.attribute('canDelete').call(this);
+ }
}
diff --git a/extensions/messages/js/src/forum/components/Message.tsx b/extensions/messages/js/src/forum/components/Message.tsx
index b62cbb523..c241c3e9e 100644
--- a/extensions/messages/js/src/forum/components/Message.tsx
+++ b/extensions/messages/js/src/forum/components/Message.tsx
@@ -9,9 +9,12 @@ import Comment from 'flarum/forum/components/Comment';
import PostUser from 'flarum/forum/components/PostUser';
import PostMeta from 'flarum/forum/components/PostMeta';
import classList from 'flarum/common/utils/classList';
+import MessageControls from '../utils/MessageControls';
+import type MessageStreamState from '../states/MessageStreamState';
export interface IMessageAttrs extends IAbstractPostAttrs {
message: DialogMessage;
+ state: MessageStreamState;
}
/**
@@ -29,7 +32,7 @@ export default abstract class Message
{this.timeGap(message)}
-
+
);
}
diff --git a/extensions/messages/js/src/forum/utils/MessageControls.tsx b/extensions/messages/js/src/forum/utils/MessageControls.tsx
new file mode 100644
index 000000000..2ccf3b6f8
--- /dev/null
+++ b/extensions/messages/js/src/forum/utils/MessageControls.tsx
@@ -0,0 +1,67 @@
+import ItemList from 'flarum/common/utils/ItemList';
+import Separator from 'flarum/common/components/Separator';
+import type Mithril from 'mithril';
+import type DialogMessage from '../../common/models/DialogMessage';
+import type Message from '../components/Message';
+import Button from 'flarum/common/components/Button';
+import app from 'flarum/forum/app';
+import extractText from 'flarum/common/utils/extractText';
+
+const MessageControls = {
+ controls(message: DialogMessage, context: Message) {
+ const items = new ItemList();
+
+ Object.entries(this.sections()).forEach(([section, method]) => {
+ const controls = method.call(this, message, context).toArray();
+
+ if (controls.length) {
+ controls.forEach((item) => items.add(item.itemName, item));
+ items.add(section + 'Separator', );
+ }
+ });
+
+ return items;
+ },
+
+ sections() {
+ return {
+ user: this.userControls,
+ moderation: this.moderationControls,
+ destructive: this.destructiveControls,
+ };
+ },
+
+ userControls(message: DialogMessage, context: Message) {
+ return new ItemList();
+ },
+
+ moderationControls(message: DialogMessage, context: Message) {
+ return new ItemList();
+ },
+
+ destructiveControls(message: DialogMessage, context: Message) {
+ const items = new ItemList();
+
+ if (message.canDelete()) {
+ items.add(
+ 'delete',
+
+ );
+ }
+
+ return items;
+ },
+
+ deleteAction(message: DialogMessage, context: Message) {
+ if (!confirm(extractText(app.translator.trans('flarum-messages.forum.message_controls.delete_confirmation')))) return;
+
+ return message.delete().then(() => {
+ context.attrs.state.remove(message);
+ m.redraw();
+ });
+ },
+};
+
+export default MessageControls;
diff --git a/extensions/messages/locale/en.yml b/extensions/messages/locale/en.yml
index 18ecc76d2..ea8769e22 100644
--- a/extensions/messages/locale/en.yml
+++ b/extensions/messages/locale/en.yml
@@ -3,7 +3,8 @@ flarum-messages:
# Translations in this namespace are used by the admin interface.
admin:
permissions:
- send_messages: Send private messages
+ send_messages_label: Send private messages
+ delete_own_messages_label: Delete own messages
# Translations in this namespace are used by the forum user interface.
forum:
@@ -42,6 +43,10 @@ flarum-messages:
newest_button: Newest
oldest_button: Oldest
+ message_controls:
+ delete_button: Delete
+ delete_confirmation: Are you sure you want to delete this message? This action cannot be undone.
+
messages_page:
cannot_send_message_button: Can't Send a Message
empty_text: No new messages
diff --git a/extensions/messages/src/Access/DialogMessagePolicy.php b/extensions/messages/src/Access/DialogMessagePolicy.php
index f49c8b0a5..5e14dfb55 100644
--- a/extensions/messages/src/Access/DialogMessagePolicy.php
+++ b/extensions/messages/src/Access/DialogMessagePolicy.php
@@ -9,14 +9,36 @@
namespace Flarum\Messages\Access;
+use Carbon\Carbon;
use Flarum\Messages\DialogMessage;
+use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Access\AbstractPolicy;
use Flarum\User\User;
class DialogMessagePolicy extends AbstractPolicy
{
- public function update(User $actor, DialogMessage $dialogMessage): bool
+ public function __construct(
+ protected SettingsRepositoryInterface $settings
+ ) {
+ }
+
+ public function update(User $actor, DialogMessage $message): ?bool
{
+ return null;
+ }
+
+ public function delete(User $actor, DialogMessage $message): bool|null|string
+ {
+ if ($message->user_id === $actor->id) {
+ $allowHiding = $this->settings->get('flarum-messages.allow_delete_own_messages');
+
+ if ($allowHiding === '-1'
+ || ($allowHiding === 'reply' && $message->number >= $message->dialog->lastMessage->number)
+ || (is_numeric($allowHiding) && $message->created_at->diffInMinutes(new Carbon, true) < $allowHiding)) {
+ return $this->allow();
+ }
+ }
+
return false;
}
}
diff --git a/extensions/messages/src/Api/Resource/DialogMessageResource.php b/extensions/messages/src/Api/Resource/DialogMessageResource.php
index 356c0d65e..b5dbd54f4 100644
--- a/extensions/messages/src/Api/Resource/DialogMessageResource.php
+++ b/extensions/messages/src/Api/Resource/DialogMessageResource.php
@@ -78,6 +78,11 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
return $actor->can('sendAnyMessage');
}
}),
+ Endpoint\Delete::make()
+ ->authenticated()
+ ->visible(function (DialogMessage $message, Context $context): bool {
+ return $context->getActor()->can('delete', $message);
+ }),
Endpoint\Index::make()
->authenticated()
->defaultInclude([
@@ -166,6 +171,12 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
->items(1)
->set(fn () => null),
+ // Read-only.
+ Schema\Boolean::make('canDelete')
+ ->get(function (DialogMessage $message, Context $context) {
+ return $context->getActor()->can('delete', $message);
+ }),
+
Schema\Relationship\ToOne::make('user')
->type('users')
->includable(),
diff --git a/framework/core/js/src/admin/components/SettingDropdown.tsx b/framework/core/js/src/admin/components/SettingDropdown.tsx
index 0205ff532..76f5a0a4a 100644
--- a/framework/core/js/src/admin/components/SettingDropdown.tsx
+++ b/framework/core/js/src/admin/components/SettingDropdown.tsx
@@ -12,6 +12,7 @@ export type SettingDropdownOption = {
export interface ISettingDropdownAttrs extends ISelectDropdownAttrs {
setting?: string;
options: Array;
+ default: any;
}
export default class SettingDropdown extends SelectDropdown {
@@ -33,7 +34,7 @@ export default class SettingDropdown {
- const active = app.data.settings[this.attrs.setting!] === value;
+ const active = (app.data.settings[this.attrs.setting!] ?? this.attrs.default) === value;
return (