1
0
mirror of https://github.com/flarum/core.git synced 2025-08-06 08:27:42 +02:00

fix: messages inconsistencies (#4174)

* fix: messages inconsistencies

* fix

* chore: message at the page

* fix: permission grid styling broken

* fix
This commit is contained in:
Sami Mazouz
2025-01-31 12:57:45 +01:00
committed by GitHub
parent 7136ad01d5
commit 89ff984446
15 changed files with 97 additions and 40 deletions

View File

@@ -51,7 +51,7 @@ return [
(new Extend\ApiResource(Resource\UserResource::class)) (new Extend\ApiResource(Resource\UserResource::class))
->fields(fn () => [ ->fields(fn () => [
Schema\Boolean::make('canSendAnyMessage') Schema\Boolean::make('canSendAnyMessage')
->get(fn (object $model, Context $context) => $context->getActor()->can('sendAnyMessage')), ->get(fn (User $user, Context $context) => $user->can('sendAnyMessage')),
Schema\Integer::make('messageCount') Schema\Integer::make('messageCount')
->get(function (object $model, Context $context) { ->get(function (object $model, Context $context) {
return Dialog::whereVisibleTo($context->getActor()) return Dialog::whereVisibleTo($context->getActor())

View File

@@ -19,3 +19,9 @@ declare module 'flarum/forum/states/ComposerState' {
composingMessageTo(dialog: Dialog): boolean; composingMessageTo(dialog: Dialog): boolean;
} }
} }
declare module 'flarum/common/models/User' {
export default interface User {
canSendAnyMessage(): boolean;
}
}

View File

@@ -1,9 +1,13 @@
import DialogMessage from './models/DialogMessage'; import DialogMessage from './models/DialogMessage';
import Dialog from './models/Dialog'; import Dialog from './models/Dialog';
import Extend from 'flarum/common/extenders'; import Extend from 'flarum/common/extenders';
import User from 'flarum/common/models/User';
export default [ export default [
new Extend.Store() new Extend.Store()
.add('dialogs', Dialog) // .add('dialogs', Dialog) //
.add('dialog-messages', DialogMessage), // .add('dialog-messages', DialogMessage), //
new Extend.Model(User) //
.attribute<boolean>('canSendAnyMessage'),
]; ];

View File

@@ -42,11 +42,14 @@ export default class DialogSection<CustomAttrs extends IDialogStreamAttrs = IDia
<div className="DialogSection-header"> <div className="DialogSection-header">
<Avatar user={recipient} /> <Avatar user={recipient} />
<div className="DialogSection-header-info"> <div className="DialogSection-header-info">
{(recipient && ( <h2 className="DialogSection-header-info-title">
<Link href={app.route.user(recipient!)}> {(recipient && <Link href={app.route.user(recipient!)}>{username(recipient)}</Link>) || username(recipient)}
<h2>{username(recipient)}</h2> {recipient && recipient.canSendAnyMessage() ? null : (
</Link> <span className="DialogSection-header-info-helperText">
)) || <h2>{username(recipient)}</h2>} {app.translator.trans('flarum-messages.forum.dialog_section.cannot_reply_text')}
</span>
)}
</h2>
<div className="badges">{listItems(recipient?.badges().toArray() || [])}</div> <div className="badges">{listItems(recipient?.badges().toArray() || [])}</div>
</div> </div>
<div className="DialogSection-header-actions">{this.actionItems().toArray()}</div> <div className="DialogSection-header-actions">{this.actionItems().toArray()}</div>

View File

@@ -106,9 +106,9 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
messages.forEach((message, index) => items.push(this.messageItem(message, index))); messages.forEach((message, index) => items.push(this.messageItem(message, index)));
if (ReplyPlaceholder) { if (app.session.user!.canSendAnyMessage() && ReplyPlaceholder) {
items.push( items.push(
<div className="MessageStream-item" key="reply" /*data-index={this.attrs.state.count()}*/> <div className="MessageStream-item" key="reply">
<ReplyPlaceholder <ReplyPlaceholder
discussion={this.attrs.dialog} discussion={this.attrs.dialog}
onclick={() => { onclick={() => {

View File

@@ -14,8 +14,6 @@ export default class MessagesSidebar<CustomAttrs extends IMessagesSidebarAttrs =
items(): ItemList<Mithril.Children> { items(): ItemList<Mithril.Children> {
const items = super.items(); const items = super.items();
const canSendAnyMessage = app.session.user!.attribute<boolean>('canSendAnyMessage');
items.remove('newDiscussion'); items.remove('newDiscussion');
items.add( items.add(
@@ -27,9 +25,11 @@ export default class MessagesSidebar<CustomAttrs extends IMessagesSidebarAttrs =
onclick={() => { onclick={() => {
return this.newMessageAction(); return this.newMessageAction();
}} }}
disabled={!canSendAnyMessage} disabled={!app.session.user!.canSendAnyMessage()}
> >
{app.translator.trans('flarum-messages.forum.messages_page.new_message_button')} {app.session.user!.canSendAnyMessage()
? app.translator.trans('flarum-messages.forum.messages_page.send_message_button')
: app.translator.trans('flarum-messages.forum.messages_page.cannot_send_message_button')}
</Button>, </Button>,
10 10
); );

View File

@@ -8,6 +8,7 @@ import Button from 'flarum/common/components/Button';
import type Dialog from '../common/models/Dialog'; import type Dialog from '../common/models/Dialog';
import DialogsDropdown from './components/DialogsDropdown'; import DialogsDropdown from './components/DialogsDropdown';
import DialogListState from './states/DialogListState'; import DialogListState from './states/DialogListState';
import type User from 'flarum/common/models/User';
export { default as extend } from './extend'; export { default as extend } from './extend';
@@ -44,14 +45,14 @@ app.initializers.add('flarum-messages', () => {
}); });
extend(HeaderSecondary.prototype, 'items', function (items) { extend(HeaderSecondary.prototype, 'items', function (items) {
if (app.session.user?.attribute<boolean>('canSendAnyMessage')) { if (app.session.user?.canSendAnyMessage()) {
items.add('messages', <DialogsDropdown state={app.dropdownDialogs} />, 15); items.add('messages', <DialogsDropdown state={app.dropdownDialogs} />, 15);
} }
}); });
// @ts-ignore // @ts-ignore
extend(UserControls, 'userControls', (items, user) => { extend(UserControls, 'userControls', (items, user: User) => {
if (app.session.user?.attribute<boolean>('canSendAnyMessage')) { if (app.session.user?.canSendAnyMessage()) {
items.add( items.add(
'sendMessage', 'sendMessage',
<Button <Button
@@ -66,6 +67,7 @@ app.initializers.add('flarum-messages', () => {
.then(() => app.composer.show()); .then(() => app.composer.show());
}); });
}} }}
helperText={user.canSendAnyMessage() ? null : app.translator.trans('flarum-messages.forum.user_controls.cannot_reply_text')}
> >
{app.translator.trans('flarum-messages.forum.user_controls.send_message_button')} {app.translator.trans('flarum-messages.forum.user_controls.send_message_button')}
</Button> </Button>

View File

@@ -232,6 +232,17 @@
gap: 6px; gap: 6px;
} }
.DialogSection-header-info-title {
display: flex;
flex-direction: column;
}
.DialogSection-header-info-helperText {
font-size: 0.8rem;
font-weight: normal;
color: var(--control-color);
}
.DialogSection-back { .DialogSection-back {
display: flex; display: flex;

View File

@@ -22,6 +22,7 @@ flarum-messages:
dialog_section: dialog_section:
back_label: Go back back_label: Go back
cannot_reply_text: This user cannot reply
controls: controls:
details_button: Details details_button: Details
controls_toggle_label: Dialog control actions controls_toggle_label: Dialog control actions
@@ -42,14 +43,14 @@ flarum-messages:
oldest_button: Oldest oldest_button: Oldest
messages_page: messages_page:
empty_text: You have no messages yet. When you send or receive messages, they cannot_send_message_button: Can't Send a Message
will appear here. empty_text: No new messages
hero: hero:
title: Messages title: Messages
subtitle: Your private conversations with other users subtitle: Your private conversations with other users
mark_all_as_read_tooltip: Mark all as read mark_all_as_read_tooltip: Mark all as read
new_message_button: Send a Message
refresh_tooltip: Refresh refresh_tooltip: Refresh
send_message_button: Send a Message
stream: stream:
load_previous_button: Load previous messages load_previous_button: Load previous messages
start_of_the_conversation: Start of the conversation start_of_the_conversation: Start of the conversation
@@ -64,6 +65,7 @@ flarum-messages:
user_controls: user_controls:
send_message_button: Send a message send_message_button: Send a message
cannot_reply_text: This user cannot reply
notifications: notifications:
message_received_text: Message Received notification from {user} message_received_text: Message Received notification from {user}

View File

@@ -78,14 +78,14 @@ export default function () {
'tag', 'tag',
<Dropdown <Dropdown
className="Dropdown--restrictByTag" className="Dropdown--restrictByTag"
buttonClassName="Button Button--text" buttonClassName="Button Button--link"
label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')} label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')}
icon="fas fa-plus" icon="fas fa-plus"
caretIcon={null} caretIcon={null}
> >
{tags.map((tag) => ( {tags.map((tag) => (
<Button icon={true} onclick={() => tag.save({ isRestricted: true })}> <Button icon={tagIcon(tag, { className: 'Button-icon' })} onclick={() => tag.save({ isRestricted: true })}>
{[tagIcon(tag, { className: 'Button-icon' }), ' ', tag.name()]} {tag.name()}
</Button> </Button>
))} ))}
</Dropdown> </Dropdown>

View File

@@ -11,8 +11,10 @@ export interface IButtonAttrs extends ComponentAttrs {
* Class(es) of an optional icon to be rendered within the button. * Class(es) of an optional icon to be rendered within the button.
* *
* If provided, the button will gain a `has-icon` class. * If provided, the button will gain a `has-icon` class.
*
* You may also provide a rendered icon element directly.
*/ */
icon?: string; icon?: string | boolean | Mithril.Children;
/** /**
* Disables button from user input. * Disables button from user input.
* *
@@ -42,6 +44,12 @@ export interface IButtonAttrs extends ComponentAttrs {
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type
*/ */
type?: string; type?: string;
/**
* Helper text. Displayed under the button label.
*
* Default: `null`
*/
helperText?: Mithril.Children;
} }
/** /**
@@ -56,7 +64,7 @@ export interface IButtonAttrs extends ComponentAttrs {
*/ */
export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> extends Component<CustomAttrs> { export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>) { view(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
let { type, 'aria-label': ariaLabel, icon: iconName, disabled, loading, className, class: _class, ...attrs } = this.attrs; let { type, 'aria-label': ariaLabel, icon: iconName, disabled, loading, className, class: _class, helperText, ...attrs } = this.attrs;
// If no `type` attr provided, set to "button" // If no `type` attr provided, set to "button"
type ||= 'button'; type ||= 'button';
@@ -74,6 +82,7 @@ export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> ext
hasIcon: iconName, hasIcon: iconName,
disabled: disabled || loading, disabled: disabled || loading,
loading: loading, loading: loading,
hasSubContent: !!this.getButtonSubContent(),
}); });
const buttonAttrs = { const buttonAttrs = {
@@ -104,12 +113,21 @@ export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> ext
* Get the template for the button's content. * Get the template for the button's content.
*/ */
protected getButtonContent(children: Mithril.Children): Mithril.ChildArray { protected getButtonContent(children: Mithril.Children): Mithril.ChildArray {
const iconName = this.attrs.icon; const icon = this.attrs.icon;
return [ return [
iconName && <Icon name={iconName} className="Button-icon" />, icon && (typeof icon === 'string' || icon === true ? <Icon name={icon} className="Button-icon" /> : icon),
children && <span className="Button-label">{children}</span>, children && (
<span className="Button-label">
<span className="Button-labelText">{children}</span>
{this.getButtonSubContent()}
</span>
),
this.attrs.loading && <LoadingIndicator size="small" display="inline" />, this.attrs.loading && <LoadingIndicator size="small" display="inline" />,
]; ];
} }
protected getButtonSubContent(): Mithril.Children {
return this.attrs.helperText ? <span className="Button-helperText">{this.attrs.helperText}</span> : null;
}
} }

View File

@@ -19,6 +19,8 @@ export interface IDropdownAttrs extends ComponentAttrs {
caretIcon?: string; caretIcon?: string;
/** The label of the dropdown toggle button. Defaults to 'Controls'. */ /** The label of the dropdown toggle button. Defaults to 'Controls'. */
label: Mithril.Children; label: Mithril.Children;
/** The helper text to display under the button label. */
helperText: Mithril.Children;
/** The label used to describe the dropdown toggle button to assistive readers. Defaults to 'Toggle dropdown menu'. */ /** The label used to describe the dropdown toggle button to assistive readers. Defaults to 'Toggle dropdown menu'. */
accessibleToggleLabel?: string; accessibleToggleLabel?: string;
/** An optional tooltip to show when hovering over the dropdown toggle button. */ /** An optional tooltip to show when hovering over the dropdown toggle button. */
@@ -157,11 +159,18 @@ export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttr
getButtonContent(children: Mithril.ChildArray): Mithril.ChildArray { getButtonContent(children: Mithril.ChildArray): Mithril.ChildArray {
return [ return [
this.attrs.icon ? <Icon name={this.attrs.icon} className="Button-icon" /> : '', this.attrs.icon ? <Icon name={this.attrs.icon} className="Button-icon" /> : '',
<span className="Button-label">{this.attrs.label}</span>, <span className="Button-label">
<span className="Button-labelText">{this.attrs.label}</span>
{this.getButtonSubContent()}
</span>,
this.attrs.caretIcon ? <Icon name={this.attrs.caretIcon} className="Button-caret" /> : '', this.attrs.caretIcon ? <Icon name={this.attrs.caretIcon} className="Button-caret" /> : '',
]; ];
} }
protected getButtonSubContent(): Mithril.Children {
return this.attrs.helperText ? <span className="Button-helperText">{this.attrs.helperText}</span> : null;
}
getMenu(items: Mithril.Vnode<any, any>[]): Mithril.Vnode<any, any> { getMenu(items: Mithril.Vnode<any, any>[]): Mithril.Vnode<any, any> {
return <ul className={'Dropdown-menu dropdown-menu ' + this.attrs.menuClassName}>{items}</ul>; return <ul className={'Dropdown-menu dropdown-menu ' + this.attrs.menuClassName}>{items}</ul>;
} }

View File

@@ -94,23 +94,19 @@
} }
} }
.Dropdown { .Dropdown {
display: block;
.Dropdown-toggle { .Dropdown-toggle {
width: 100%; width: 100%;
display: block; margin: -2px 0;
text-align: left;
float: none;
} }
.Dropdown-menu { .Dropdown-menu {
margin: 0; margin: 6px 0 0;
} }
} }
.Button { .Button {
text-decoration: none; text-decoration: none;
.Badge { .Badge {
margin: -3px 2px -3px 0; margin: 0 2px 0 0;
} }
} }
td:not(:hover) .Select-caret, td:not(:hover) .Select-caret,
@@ -126,12 +122,8 @@
margin: -1px 0; margin: -1px 0;
} }
.PermissionDropdown { .PermissionDropdown {
.Dropdown-toggle {
padding: 5px 0;
margin: -5px 0;
}
.Badge { .Badge {
margin: -3px 3px -3px 0; margin: 0 3px 0 0;
box-shadow: none; box-shadow: none;
} }
} }

View File

@@ -259,6 +259,12 @@
line-height: inherit; line-height: inherit;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: flex;
flex-direction: column;
}
.Button-helperText {
font-size: 0.73rem;
color: var(--muted-more-color);
} }
.Button-icon { .Button-icon {
line-height: inherit; line-height: inherit;

View File

@@ -27,8 +27,8 @@
> a, > button, > span { > a, > button, > span {
padding: 8px 15px; padding: 8px 15px;
display: flex; display: flex;
align-items: center;
gap: 9px; gap: 9px;
align-items: center;
width: 100%; width: 100%;
color: var(--text-color); color: var(--text-color);
border-radius: 0; border-radius: 0;
@@ -51,6 +51,10 @@
flex-shrink: 0; flex-shrink: 0;
} }
&.hasSubContent {
align-items: flex-start;
}
&.disabled { &.disabled {
opacity: 0.4; opacity: 0.4;
background: none !important; background: none !important;