mirror of
https://github.com/flarum/core.git
synced 2025-08-07 08:56:38 +02:00
forum: add NotificationGrid and SettingsPage - missing modals
This commit is contained in:
2
js/dist/admin.js
vendored
2
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
6
js/dist/forum.js
vendored
6
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -5,6 +5,14 @@ import computed from '../utils/computed';
|
||||
import GroupBadge from '../components/GroupBadge';
|
||||
import Group from './Group';
|
||||
|
||||
export interface UserPreferences {
|
||||
discloseOnline?: boolean;
|
||||
indexProfile?: boolean;
|
||||
locale?: string;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export default class User extends Model {
|
||||
username = Model.attribute('username') as () => string;
|
||||
|
||||
@@ -14,7 +22,7 @@ export default class User extends Model {
|
||||
password = Model.attribute('password') as () => string;
|
||||
|
||||
avatarUrl = Model.attribute('avatarUrl') as () => string;
|
||||
preferences = Model.attribute('preferences') as () => string;
|
||||
preferences = Model.attribute('preferences') as () => UserPreferences;
|
||||
groups = Model.hasMany('groups') as () => Group[];
|
||||
|
||||
joinTime = Model.attribute('joinTime', Model.transformDate) as () => Date;
|
||||
|
@@ -7,6 +7,8 @@ import HeaderSecondary from './components/HeaderSecondary';
|
||||
import Page from './components/Page';
|
||||
import IndexPage from './components/IndexPage';
|
||||
import PostsUserPage from './components/PostsUserPage';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
|
||||
import User from '../common/models/User';
|
||||
import Post from '../common/models/Post';
|
||||
import Discussion from '../common/models/Discussion';
|
||||
@@ -22,7 +24,7 @@ export default class Forum extends Application {
|
||||
'user.posts': { path: '/u/:username', component: PostsUserPage },
|
||||
'user.discussions': { path: '/u/:username/discussions', component: PostsUserPage },
|
||||
|
||||
settings: { path: '/settings', component: PostsUserPage },
|
||||
settings: { path: '/settings', component: SettingsPage },
|
||||
|
||||
'index.filter': { path: '/:filter', component: IndexPage },
|
||||
};
|
||||
|
208
js/src/forum/components/NotificationGrid.tsx
Normal file
208
js/src/forum/components/NotificationGrid.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
import Component, { ComponentProps } from '../../common/Component';
|
||||
import Checkbox from '../../common/components/Checkbox';
|
||||
import icon from '../../common/helpers/icon';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import User from '../../common/models/User';
|
||||
|
||||
export interface NotificationGridProps extends ComponentProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export type NotificationItem = {
|
||||
/**
|
||||
* The name of the notification type/method.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The icon to display in the column header/notificatio grid row.
|
||||
*/
|
||||
icon: string;
|
||||
|
||||
/**
|
||||
* The label to display in the column header/notification grid row.
|
||||
*/
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `NotificationGrid` component displays a table of notification types and
|
||||
* methods, allowing the user to toggle each combination.
|
||||
*/
|
||||
export default class NotificationGrid extends Component<NotificationGridProps> {
|
||||
/**
|
||||
* Information about the available notification methods.
|
||||
*/
|
||||
methods = this.notificationMethods().toArray();
|
||||
|
||||
/**
|
||||
* A map of notification type-method combinations to the checkbox instances
|
||||
* that represent them.
|
||||
*/
|
||||
inputs = {};
|
||||
|
||||
/**
|
||||
* Information about the available notification types.
|
||||
*/
|
||||
types = this.notificationTypes().toArray();
|
||||
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
// For each of the notification type-method combinations, create and store a
|
||||
// new checkbox component instance, which we will render in the view.
|
||||
this.types.forEach(type =>
|
||||
this.methods.forEach(method => {
|
||||
const key = this.preferenceKey(type.name, method.name);
|
||||
const preference = this.props.user.preferences()[key];
|
||||
|
||||
this.inputs[key] = new Checkbox({
|
||||
state: !!preference,
|
||||
disabled: typeof preference === 'undefined',
|
||||
onchange: () => this.toggle([key]),
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<table className="NotificationGrid">
|
||||
<thead>
|
||||
<tr>
|
||||
<td />
|
||||
{this.methods.map(method => (
|
||||
<th className="NotificationGrid-groupToggle" onclick={this.toggleMethod.bind(this, method.name)}>
|
||||
{icon(method.icon)} {method.label}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{this.types.map(type => (
|
||||
<tr>
|
||||
<td className="NotificationGrid-groupToggle" onclick={this.toggleType.bind(this, type.name)}>
|
||||
{icon(type.icon)} {type.label}
|
||||
</td>
|
||||
{this.methods.map(method => (
|
||||
<td className="NotificationGrid-checkbox">{this.inputs[this.preferenceKey(type.name, method.name)].render()}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
this.$('thead .NotificationGrid-groupToggle').bind('mouseenter mouseleave', function(e) {
|
||||
const i = parseInt($(this).index(), 10) + 1;
|
||||
$(this)
|
||||
.parents('table')
|
||||
.find('td:nth-child(' + i + ')')
|
||||
.toggleClass('highlighted', e.type === 'mouseenter');
|
||||
});
|
||||
|
||||
this.$('tbody .NotificationGrid-groupToggle').bind('mouseenter mouseleave', function(e) {
|
||||
$(this)
|
||||
.parent()
|
||||
.find('td')
|
||||
.toggleClass('highlighted', e.type === 'mouseenter');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the state of the given preferences, based on the value of the first
|
||||
* one.
|
||||
*/
|
||||
toggle(keys: string[]) {
|
||||
const user = this.props.user;
|
||||
const preferences = user.preferences();
|
||||
const enabled = !preferences[keys[0]];
|
||||
|
||||
keys.forEach(key => {
|
||||
const control = this.inputs[key];
|
||||
|
||||
control.loading = true;
|
||||
control.props.state = enabled;
|
||||
preferences[key] = control.props.state = enabled;
|
||||
});
|
||||
|
||||
m.redraw();
|
||||
|
||||
user.save({ preferences }).then(() => {
|
||||
keys.forEach(key => (this.inputs[key].loading = false));
|
||||
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle all notification types for the given method.
|
||||
*/
|
||||
toggleMethod(method: string) {
|
||||
const keys = this.types.map(type => this.preferenceKey(type.name, method)).filter(key => !this.inputs[key].props.disabled);
|
||||
|
||||
this.toggle(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle all notification methods for the given type.
|
||||
*/
|
||||
toggleType(type: string) {
|
||||
const keys = this.methods.map(method => this.preferenceKey(type, method.name)).filter(key => !this.inputs[key].props.disabled);
|
||||
|
||||
this.toggle(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the preference key for the given notification type-method
|
||||
* combination.
|
||||
*/
|
||||
preferenceKey(type: string, method: string): string {
|
||||
return `notify_${type}_${method}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the notification methods to display in the grid.
|
||||
*
|
||||
* @see {NotificationItem}
|
||||
*/
|
||||
notificationMethods() {
|
||||
const items = new ItemList<NotificationItem>();
|
||||
|
||||
items.add('alert', {
|
||||
name: 'alert',
|
||||
icon: 'fas fa-bell',
|
||||
label: app.translator.trans('core.forum.settings.notify_by_web_heading'),
|
||||
});
|
||||
|
||||
items.add('email', {
|
||||
name: 'email',
|
||||
icon: 'far fa-envelope',
|
||||
label: app.translator.trans('core.forum.settings.notify_by_email_heading'),
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the notification types to display in the grid.
|
||||
*
|
||||
* @see {NotificationItem}
|
||||
*/
|
||||
notificationTypes() {
|
||||
const items = new ItemList<NotificationItem>();
|
||||
|
||||
items.add('discussionRenamed', {
|
||||
name: 'discussionRenamed',
|
||||
icon: 'fas fa-pencil-alt',
|
||||
label: app.translator.trans('core.forum.settings.notify_discussion_renamed_label'),
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
134
js/src/forum/components/SettingsPage.tsx
Normal file
134
js/src/forum/components/SettingsPage.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import Button from '../../common/components/Button';
|
||||
import FieldSet from '../../common/components/FieldSet';
|
||||
import Switch from '../../common/components/Switch';
|
||||
import UserPage from './UserPage';
|
||||
import NotificationGrid from './NotificationGrid';
|
||||
|
||||
export default class SettingsPage extends UserPage {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.show(app.session.user);
|
||||
app.setTitle(app.translator.transText('core.forum.settings.title'));
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="SettingsPage">
|
||||
<ul>{listItems(this.settingsItems().toArray())}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the user's settings controls.
|
||||
*/
|
||||
settingsItems(): ItemList {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add(
|
||||
'account',
|
||||
FieldSet.component({
|
||||
label: app.translator.trans('core.forum.settings.account_heading'),
|
||||
className: 'Settings-account',
|
||||
children: this.accountItems().toArray(),
|
||||
})
|
||||
);
|
||||
|
||||
items.add(
|
||||
'notifications',
|
||||
FieldSet.component({
|
||||
label: app.translator.trans('core.forum.settings.notifications_heading'),
|
||||
className: 'Settings-notifications',
|
||||
children: this.notificationsItems().toArray(),
|
||||
})
|
||||
);
|
||||
|
||||
items.add(
|
||||
'privacy',
|
||||
FieldSet.component({
|
||||
label: app.translator.trans('core.forum.settings.privacy_heading'),
|
||||
className: 'Settings-privacy',
|
||||
children: this.privacyItems().toArray(),
|
||||
})
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the user's account settings.
|
||||
*/
|
||||
accountItems(): ItemList {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add(
|
||||
'changePassword',
|
||||
Button.component({
|
||||
children: app.translator.trans('core.forum.settings.change_password_button'),
|
||||
className: 'Button',
|
||||
onclick: () => app.modal.show(new ChangePasswordModal()),
|
||||
})
|
||||
);
|
||||
|
||||
items.add(
|
||||
'changeEmail',
|
||||
Button.component({
|
||||
children: app.translator.trans('core.forum.settings.change_email_button'),
|
||||
className: 'Button',
|
||||
onclick: () => app.modal.show(new ChangeEmailModal()),
|
||||
})
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the user's notification settings.
|
||||
*/
|
||||
notificationsItems(): ItemList {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add('notificationGrid', NotificationGrid.component({ user: this.user }));
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a callback that will save a value to the given preference.
|
||||
*/
|
||||
preferenceSaver(key: string): Function {
|
||||
return (value, component) => {
|
||||
if (component) component.loading = true;
|
||||
m.redraw();
|
||||
|
||||
this.user.savePreferences({ [key]: value }).then(() => {
|
||||
if (component) component.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the user's privacy settings.
|
||||
*/
|
||||
privacyItems(): ItemList {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add(
|
||||
'discloseOnline',
|
||||
Switch.component({
|
||||
children: app.translator.trans('core.forum.settings.privacy_disclose_online_label'),
|
||||
state: this.user.preferences().discloseOnline,
|
||||
onchange: (value, component) => {
|
||||
this.user.pushAttributes({ lastSeenAt: null });
|
||||
this.preferenceSaver('discloseOnline')(value, component);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user