mirror of
https://github.com/flarum/core.git
synced 2025-08-23 16:43:21 +02:00
Compare commits
4 Commits
v2.0.0-bet
...
sm/4233
Author | SHA1 | Date | |
---|---|---|---|
|
8fd6dbc3fd | ||
|
1f09ff1942 | ||
|
a46ce07255 | ||
|
8e404e4415 |
2
extensions/emoji/js/dist/forum.js
generated
vendored
2
extensions/emoji/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/emoji/js/dist/forum.js.map
generated
vendored
2
extensions/emoji/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -70,6 +70,7 @@ export default function addComposerAutocomplete() {
|
|||||||
return (
|
return (
|
||||||
<Tooltip text={name}>
|
<Tooltip text={name}>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
key={emoji}
|
key={emoji}
|
||||||
onclick={() => applySuggestion(emoji)}
|
onclick={() => applySuggestion(emoji)}
|
||||||
onmouseenter={function () {
|
onmouseenter={function () {
|
||||||
|
2
extensions/mentions/js/dist/forum.js
generated
vendored
2
extensions/mentions/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/mentions/js/dist/forum.js.map
generated
vendored
2
extensions/mentions/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -17,7 +17,7 @@ export default class MentionsDropdownItem<CustomAttrs extends IMentionsDropdownI
|
|||||||
const className = classList('MentionsDropdownItem', 'PostPreview', `MentionsDropdown-${mentionable.type()}`);
|
const className = classList('MentionsDropdownItem', 'PostPreview', `MentionsDropdown-${mentionable.type()}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={className} {...attrs}>
|
<button className={className} type="button" {...attrs}>
|
||||||
<span className="PostPreview-content">{vnode.children}</span>
|
<span className="PostPreview-content">{vnode.children}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@@ -15,6 +15,7 @@ export default class PostQuoteButton extends Fragment {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="Button PostQuoteButton"
|
className="Button PostQuoteButton"
|
||||||
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
reply(this.post, this.content);
|
reply(this.post, this.content);
|
||||||
}}
|
}}
|
||||||
|
@@ -30,6 +30,9 @@ export default class Dialog extends Model {
|
|||||||
unreadCount() {
|
unreadCount() {
|
||||||
return Model.attribute<number>('unreadCount').call(this);
|
return Model.attribute<number>('unreadCount').call(this);
|
||||||
}
|
}
|
||||||
|
lastMessageId() {
|
||||||
|
return Model.attribute<number>('lastMessageId').call(this);
|
||||||
|
}
|
||||||
lastReadMessageId() {
|
lastReadMessageId() {
|
||||||
return Model.attribute<number>('lastReadMessageId').call(this);
|
return Model.attribute<number>('lastReadMessageId').call(this);
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,56 @@ const MessageControls = {
|
|||||||
|
|
||||||
return message.delete().then(() => {
|
return message.delete().then(() => {
|
||||||
context.attrs.state.remove(message);
|
context.attrs.state.remove(message);
|
||||||
|
|
||||||
|
const dialog = message.dialog();
|
||||||
|
|
||||||
|
if (dialog) {
|
||||||
|
const noMessagesLeft =
|
||||||
|
context.attrs.state.getAllItems().filter((m) => {
|
||||||
|
const mDialog = m.dialog();
|
||||||
|
|
||||||
|
if (!mDialog) return false;
|
||||||
|
|
||||||
|
return mDialog?.id() === dialog!.id();
|
||||||
|
}).length === 0;
|
||||||
|
|
||||||
|
if (noMessagesLeft) {
|
||||||
|
app.dialogs.remove(dialog!);
|
||||||
|
m.route.set(app.route('messages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(message.id()!) === dialog.lastMessageId()) {
|
||||||
|
const lastMessage = context.attrs.state
|
||||||
|
.getAllItems()
|
||||||
|
.filter((m) => {
|
||||||
|
const mDialog = m.dialog();
|
||||||
|
|
||||||
|
if (!mDialog) return false;
|
||||||
|
|
||||||
|
return mDialog.id() === dialog?.id();
|
||||||
|
})
|
||||||
|
.sort((a, b) => parseInt(a.id()!) - parseInt(b.id()!))
|
||||||
|
.pop();
|
||||||
|
|
||||||
|
if (lastMessage) {
|
||||||
|
dialog!.pushData({
|
||||||
|
relationships: {
|
||||||
|
...dialog!.data.relationships,
|
||||||
|
lastMessage: {
|
||||||
|
data: {
|
||||||
|
type: 'dialog-messages',
|
||||||
|
id: lastMessage.id()!,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dialog.pushAttributes({
|
||||||
|
lastMessageId: parseInt(lastMessage.id()!),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m.redraw();
|
m.redraw();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -68,9 +68,6 @@ class DialogResource extends Resource\AbstractDatabaseResource
|
|||||||
$connection = UserDialogState::query()->getConnection();
|
$connection = UserDialogState::query()->getConnection();
|
||||||
$grammar = UserDialogState::query()->getGrammar();
|
$grammar = UserDialogState::query()->getGrammar();
|
||||||
|
|
||||||
$table = $grammar->wrapTable('dialogs');
|
|
||||||
$column = $grammar->wrap('last_message_id');
|
|
||||||
|
|
||||||
UserDialogState::query()
|
UserDialogState::query()
|
||||||
->where('dialog_user.user_id', $context->getActor()->id)
|
->where('dialog_user.user_id', $context->getActor()->id)
|
||||||
->update([
|
->update([
|
||||||
@@ -121,6 +118,7 @@ class DialogResource extends Resource\AbstractDatabaseResource
|
|||||||
->get(function (Dialog $dialog) {
|
->get(function (Dialog $dialog) {
|
||||||
return $dialog->state->last_read_at;
|
return $dialog->state->last_read_at;
|
||||||
}),
|
}),
|
||||||
|
Schema\Integer::make('lastMessageId'),
|
||||||
Schema\Integer::make('lastReadMessageId')
|
Schema\Integer::make('lastReadMessageId')
|
||||||
->visible(fn (Dialog $dialog) => $dialog->state !== null)
|
->visible(fn (Dialog $dialog) => $dialog->state !== null)
|
||||||
->get(function (Dialog $dialog) {
|
->get(function (Dialog $dialog) {
|
||||||
|
@@ -70,6 +70,24 @@ class DialogMessage extends AbstractModel implements Formattable
|
|||||||
->toSql()
|
->toSql()
|
||||||
.')');
|
.')');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static::deleted(function (self $message) {
|
||||||
|
if ($message->dialog) {
|
||||||
|
if ($message->dialog->messages()->count() === 0) {
|
||||||
|
$message->dialog->delete();
|
||||||
|
} elseif ($message->dialog->first_message_id === $message->id) {
|
||||||
|
$message->dialog->setFirstMessage(
|
||||||
|
$message->dialog->messages()->oldest('id')->first()
|
||||||
|
);
|
||||||
|
$message->dialog->save();
|
||||||
|
} elseif ($message->dialog->last_message_id === $message->id) {
|
||||||
|
$message->dialog->setLastMessage(
|
||||||
|
$message->dialog->messages()->latest('id')->first()
|
||||||
|
);
|
||||||
|
$message->dialog->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dialog(): BelongsTo
|
public function dialog(): BelongsTo
|
||||||
|
2
extensions/statistics/js/dist/admin.js
generated
vendored
2
extensions/statistics/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/statistics/js/dist/admin.js.map
generated
vendored
2
extensions/statistics/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -279,6 +279,7 @@ export default class StatisticsWidget extends DashboardWidget {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classList('Button--ua-reset StatisticsWidget-entity', { active: this.selectedEntity === entity })}
|
className={classList('Button--ua-reset StatisticsWidget-entity', { active: this.selectedEntity === entity })}
|
||||||
|
type="button"
|
||||||
onclick={this.changeEntity.bind(this, entity)}
|
onclick={this.changeEntity.bind(this, entity)}
|
||||||
>
|
>
|
||||||
<h3 className="StatisticsWidget-heading">{app.translator.trans('flarum-statistics.admin.statistics.' + entity + '_heading')}</h3>
|
<h3 className="StatisticsWidget-heading">{app.translator.trans('flarum-statistics.admin.statistics.' + entity + '_heading')}</h3>
|
||||||
|
2
framework/core/js/dist/admin.js
generated
vendored
2
framework/core/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/admin.js.map
generated
vendored
2
framework/core/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum.js
generated
vendored
2
framework/core/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum.js.map
generated
vendored
2
framework/core/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum/components/PostStream.js
generated
vendored
2
framework/core/js/dist/forum/components/PostStream.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum/components/PostStream.js.map
generated
vendored
2
framework/core/js/dist/forum/components/PostStream.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum/components/PostStreamScrubber.js
generated
vendored
2
framework/core/js/dist/forum/components/PostStreamScrubber.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum/components/PostStreamScrubber.js.map
generated
vendored
2
framework/core/js/dist/forum/components/PostStreamScrubber.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -67,7 +67,13 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
|||||||
*/
|
*/
|
||||||
submitButton(): Mithril.Children {
|
submitButton(): Mithril.Children {
|
||||||
return (
|
return (
|
||||||
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onclick={this.saveSettings.bind(this)}
|
||||||
|
className="Button Button--primary"
|
||||||
|
loading={this.loading}
|
||||||
|
disabled={!this.isChanged()}
|
||||||
|
>
|
||||||
{app.translator.trans('core.admin.settings.submit_button')}
|
{app.translator.trans('core.admin.settings.submit_button')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
@@ -25,12 +25,12 @@ export default class PermissionsPage extends AdminPage {
|
|||||||
.all<Group>('groups')
|
.all<Group>('groups')
|
||||||
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
|
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
|
||||||
.map((group) => (
|
.map((group) => (
|
||||||
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
|
<button className="Button Group" type="button" onclick={() => app.modal.show(EditGroupModal, { group })}>
|
||||||
<GroupBadge group={group} className="Group-icon" label={null} />
|
<GroupBadge group={group} className="Group-icon" label={null} />
|
||||||
<span className="Group-name">{group.namePlural()}</span>
|
<span className="Group-name">{group.namePlural()}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
<button className="Button Group Group--add" onclick={() => app.modal.show(EditGroupModal)}>
|
<button className="Button Group Group--add" type="button" onclick={() => app.modal.show(EditGroupModal)}>
|
||||||
<Icon name="fas fa-plus" className="Group-icon" />
|
<Icon name="fas fa-plus" className="Group-icon" />
|
||||||
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
|
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -352,6 +352,7 @@ export default class UserListPage extends AdminPage {
|
|||||||
<button
|
<button
|
||||||
onclick={toggleEmailVisibility}
|
onclick={toggleEmailVisibility}
|
||||||
className="Button Button--text UserList-emailIconBtn"
|
className="Button Button--text UserList-emailIconBtn"
|
||||||
|
type="button"
|
||||||
title={app.translator.trans('core.admin.users.grid.columns.email.visibility_show')}
|
title={app.translator.trans('core.admin.users.grid.columns.email.visibility_show')}
|
||||||
>
|
>
|
||||||
<Icon name="far fa-eye-slash fa-fw" className="icon" />
|
<Icon name="far fa-eye-slash fa-fw" className="icon" />
|
||||||
|
@@ -21,7 +21,7 @@ export default class DetailedDropdownItem<
|
|||||||
> extends Component<CustomAttrs> {
|
> extends Component<CustomAttrs> {
|
||||||
view() {
|
view() {
|
||||||
return (
|
return (
|
||||||
<button className="DetailedDropdownItem hasIcon" onclick={this.attrs.onclick}>
|
<button type="button" className="DetailedDropdownItem hasIcon" onclick={this.attrs.onclick}>
|
||||||
<Icon name={this.attrs.active ? 'fas fa-check' : 'fas'} className="Button-icon" />
|
<Icon name={this.attrs.active ? 'fas fa-check' : 'fas'} className="Button-icon" />
|
||||||
<span className="DetailedDropdownItem-content">
|
<span className="DetailedDropdownItem-content">
|
||||||
<Icon name={this.attrs.icon} className="Button-icon" />
|
<Icon name={this.attrs.icon} className="Button-icon" />
|
||||||
|
@@ -139,6 +139,7 @@ export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttr
|
|||||||
getButton(children: Mithril.ChildArray): Mithril.Vnode<any, any> {
|
getButton(children: Mithril.ChildArray): Mithril.Vnode<any, any> {
|
||||||
let button = (
|
let button = (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
|
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label={this.attrs.accessibleToggleLabel}
|
aria-label={this.attrs.accessibleToggleLabel}
|
||||||
|
@@ -45,6 +45,7 @@ export default class SplitDropdown<CustomAttrs extends ISplitDropdownAttrs = ISp
|
|||||||
<>
|
<>
|
||||||
{button}
|
{button}
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
|
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label={this.attrs.accessibleToggleLabel}
|
aria-label={this.attrs.accessibleToggleLabel}
|
||||||
|
@@ -43,6 +43,7 @@ export default class AvatarEditor extends Component {
|
|||||||
<div className={classList(['AvatarEditor', 'Dropdown', this.attrs.className, this.loading && 'loading', this.isDraggedOver && 'dragover'])}>
|
<div className={classList(['AvatarEditor', 'Dropdown', this.attrs.className, this.loading && 'loading', this.isDraggedOver && 'dragover'])}>
|
||||||
<Avatar user={user} loading="eager" />
|
<Avatar user={user} loading="eager" />
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={user.avatarUrl() ? 'Dropdown-toggle' : 'Dropdown-toggle AvatarEditor--noAvatar'}
|
className={user.avatarUrl() ? 'Dropdown-toggle' : 'Dropdown-toggle AvatarEditor--noAvatar'}
|
||||||
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||||
ariaLabel={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
ariaLabel={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||||
|
@@ -40,6 +40,7 @@ export default class PostMeta<CustomAttrs extends IPostMetaAttrs = IPostMetaAttr
|
|||||||
items.add(
|
items.add(
|
||||||
'time',
|
'time',
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={classList({
|
className={classList({
|
||||||
'Button Button--text': true,
|
'Button Button--text': true,
|
||||||
'Dropdown-toggle Button--link': !!permalink,
|
'Dropdown-toggle Button--link': !!permalink,
|
||||||
|
@@ -58,7 +58,7 @@ export default class PostStreamScrubber extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames.join(' ')}>
|
<div className={classNames.join(' ')}>
|
||||||
<button className="Button Dropdown-toggle" data-toggle="dropdown">
|
<button type="button" className="Button Dropdown-toggle" data-toggle="dropdown">
|
||||||
{viewing} <Icon name={'fas fa-sort'} />
|
{viewing} <Icon name={'fas fa-sort'} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@@ -54,7 +54,7 @@ export default class ReplyPlaceholder<CustomAttrs extends IReplyPlaceholderAttrs
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className="Post ReplyPlaceholder" onclick={reply}>
|
<button type="button" className="Post ReplyPlaceholder" onclick={reply}>
|
||||||
<div className="Post-container">
|
<div className="Post-container">
|
||||||
<div className="Post-side">
|
<div className="Post-side">
|
||||||
<Avatar user={app.session.user} className="Post-avatar" />
|
<Avatar user={app.session.user} className="Post-avatar" />
|
||||||
|
Reference in New Issue
Block a user