mirror of
https://github.com/flarum/core.git
synced 2025-08-06 00:17:31 +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:
@@ -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())
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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'),
|
||||||
];
|
];
|
||||||
|
@@ -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>
|
||||||
|
@@ -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={() => {
|
||||||
|
@@ -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
|
||||||
);
|
);
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user