mirror of
https://github.com/flarum/core.git
synced 2025-07-31 13:40:20 +02:00
refactor: convert page components to TypeScript (#3538)
* fix(a11y): color preview fields have no aria label * refactor: convert page components to TypeScript Co-authored-by: David Wheatley <hi@davwheat.dev> Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
This commit is contained in:
@@ -38,6 +38,8 @@ export interface AdminApplicationData extends ApplicationData {
|
||||
extensions: Record<string, Extension>;
|
||||
settings: Record<string, string>;
|
||||
modelStatistics: Record<string, { total: number }>;
|
||||
displayNameDrivers: string[];
|
||||
slugDrivers: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export default class AdminApplication extends Application {
|
||||
|
@@ -60,7 +60,7 @@ export type HTMLInputTypes =
|
||||
|
||||
export interface CommonSettingsItemOptions extends Mithril.Attributes {
|
||||
setting: string;
|
||||
label: Mithril.Children;
|
||||
label?: Mithril.Children;
|
||||
help?: Mithril.Children;
|
||||
className?: string;
|
||||
}
|
||||
@@ -137,6 +137,8 @@ export type AdminHeaderAttrs = AdminHeaderOptions & Partial<Omit<Mithril.Attribu
|
||||
export type SettingValue = string;
|
||||
export type MutableSettings = Record<string, Stream<SettingValue>>;
|
||||
|
||||
export type SaveSubmitEvent = SubmitEvent & { redraw: boolean };
|
||||
|
||||
export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends Page<CustomAttrs> {
|
||||
settings: MutableSettings = {};
|
||||
loading: boolean = false;
|
||||
@@ -162,7 +164,7 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
||||
*
|
||||
* Calls `this.saveSettings` when the button is clicked.
|
||||
*/
|
||||
submitButton(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
|
||||
submitButton(): Mithril.Children {
|
||||
return (
|
||||
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
|
||||
{app.translator.trans('core.admin.settings.submit_button')}
|
||||
@@ -385,7 +387,7 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
||||
/**
|
||||
* Saves the modified settings to the database.
|
||||
*/
|
||||
saveSettings(e: SubmitEvent & { redraw: boolean }) {
|
||||
saveSettings(e: SaveSubmitEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
app.alerts.clear();
|
||||
|
@@ -6,6 +6,7 @@ import EditCustomFooterModal from './EditCustomFooterModal';
|
||||
import UploadImageButton from './UploadImageButton';
|
||||
import AdminPage from './AdminPage';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import type Mithril from 'mithril';
|
||||
|
||||
export default class AppearancePage extends AdminPage {
|
||||
headerInfo() {
|
||||
@@ -77,7 +78,7 @@ export default class AppearancePage extends AdminPage {
|
||||
}
|
||||
|
||||
colorItems() {
|
||||
const items = new ItemList();
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add('helpText', <div className="helpText">{app.translator.trans('core.admin.appearance.colors_text')}</div>, 80);
|
||||
|
||||
@@ -88,11 +89,13 @@ export default class AppearancePage extends AdminPage {
|
||||
type: 'color-preview',
|
||||
setting: 'theme_primary_color',
|
||||
placeholder: '#aaaaaa',
|
||||
ariaLabel: app.translator.trans('core.admin.appearance.colors_primary_label'),
|
||||
})}
|
||||
{this.buildSettingComponent({
|
||||
type: 'color-preview',
|
||||
setting: 'theme_secondary_color',
|
||||
placeholder: '#aaaaaa',
|
||||
ariaLabel: app.translator.trans('core.admin.appearance.colors_secondary_label'),
|
||||
})}
|
||||
</div>,
|
||||
70
|
@@ -2,24 +2,27 @@ import app from '../../admin/app';
|
||||
import FieldSet from '../../common/components/FieldSet';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import AdminPage from './AdminPage';
|
||||
import type { IPageAttrs } from '../../common/components/Page';
|
||||
import type Mithril from 'mithril';
|
||||
|
||||
export default class BasicsPage extends AdminPage {
|
||||
oninit(vnode) {
|
||||
export type HomePageItem = { path: string; label: Mithril.Children };
|
||||
|
||||
export default class BasicsPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends AdminPage<CustomAttrs> {
|
||||
localeOptions: Record<string, string> = {};
|
||||
displayNameOptions: Record<string, string> = {};
|
||||
slugDriverOptions: Record<string, Record<string, string>> = {};
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.localeOptions = {};
|
||||
const locales = app.data.locales;
|
||||
for (const i in locales) {
|
||||
this.localeOptions[i] = `${locales[i]} (${i})`;
|
||||
}
|
||||
Object.keys(app.data.locales).forEach((i) => {
|
||||
this.localeOptions[i] = `${app.data.locales[i]} (${i})`;
|
||||
});
|
||||
|
||||
this.displayNameOptions = {};
|
||||
const displayNameDrivers = app.data.displayNameDrivers;
|
||||
displayNameDrivers.forEach(function (identifier) {
|
||||
app.data.displayNameDrivers.forEach((identifier) => {
|
||||
this.displayNameOptions[identifier] = identifier;
|
||||
}, this);
|
||||
});
|
||||
|
||||
this.slugDriverOptions = {};
|
||||
Object.keys(app.data.slugDrivers).forEach((model) => {
|
||||
this.slugDriverOptions[model] = {};
|
||||
|
||||
@@ -100,6 +103,7 @@ export default class BasicsPage extends AdminPage {
|
||||
|
||||
{Object.keys(this.slugDriverOptions).map((model) => {
|
||||
const options = this.slugDriverOptions[model];
|
||||
|
||||
if (Object.keys(options).length > 1) {
|
||||
return this.buildSettingComponent({
|
||||
type: 'select',
|
||||
@@ -109,6 +113,8 @@ export default class BasicsPage extends AdminPage {
|
||||
help: app.translator.trans('core.admin.basics.slug_driver_text', { model }),
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
|
||||
{this.submitButton()}
|
||||
@@ -119,11 +125,9 @@ export default class BasicsPage extends AdminPage {
|
||||
/**
|
||||
* Build a list of options for the default homepage. Each option must be an
|
||||
* object with `path` and `label` properties.
|
||||
*
|
||||
* @return {ItemList<{ path: string, label: import('mithril').Children }>}
|
||||
*/
|
||||
homePageItems() {
|
||||
const items = new ItemList();
|
||||
const items = new ItemList<HomePageItem>();
|
||||
|
||||
items.add('allDiscussions', {
|
||||
path: '/all',
|
@@ -140,7 +140,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
|
||||
{settings ? (
|
||||
<div className="Form">
|
||||
{settings.map(this.buildSettingComponent.bind(this))}
|
||||
<div className="Form-group">{this.submitButton(vnode)}</div>
|
||||
<div className="Form-group">{this.submitButton()}</div>
|
||||
</div>
|
||||
) : (
|
||||
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
|
||||
|
@@ -4,12 +4,30 @@ import Button from '../../common/components/Button';
|
||||
import Alert from '../../common/components/Alert';
|
||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||
import AdminPage from './AdminPage';
|
||||
import type { IPageAttrs } from '../../common/components/Page';
|
||||
import type { AlertIdentifier } from '../../common/states/AlertManagerState';
|
||||
import type Mithril from 'mithril';
|
||||
import type { SaveSubmitEvent } from './AdminPage';
|
||||
|
||||
export default class MailPage extends AdminPage {
|
||||
oninit(vnode) {
|
||||
export interface MailSettings {
|
||||
data: {
|
||||
attributes: {
|
||||
fields: Record<string, any>;
|
||||
sending: boolean;
|
||||
errors: any[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default class MailPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends AdminPage<CustomAttrs> {
|
||||
sendingTest = false;
|
||||
status?: { sending: boolean; errors: any };
|
||||
driverFields?: Record<string, any>;
|
||||
testEmailSuccessAlert?: AlertIdentifier;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.sendingTest = false;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -28,14 +46,14 @@ export default class MailPage extends AdminPage {
|
||||
this.status = { sending: false, errors: {} };
|
||||
|
||||
app
|
||||
.request({
|
||||
.request<MailSettings>({
|
||||
method: 'GET',
|
||||
url: app.forum.attribute('apiUrl') + '/mail/settings',
|
||||
})
|
||||
.then((response) => {
|
||||
this.driverFields = response['data']['attributes']['fields'];
|
||||
this.status.sending = response['data']['attributes']['sending'];
|
||||
this.status.errors = response['data']['attributes']['errors'];
|
||||
this.driverFields = response.data.attributes.fields;
|
||||
this.status!.sending = response.data.attributes.sending;
|
||||
this.status!.errors = response.data.attributes.errors;
|
||||
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
@@ -47,7 +65,7 @@ export default class MailPage extends AdminPage {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
const fields = this.driverFields[this.setting('mail_driver')()];
|
||||
const fields = this.driverFields![this.setting('mail_driver')()];
|
||||
const fieldKeys = Object.keys(fields);
|
||||
|
||||
return (
|
||||
@@ -60,10 +78,10 @@ export default class MailPage extends AdminPage {
|
||||
{this.buildSettingComponent({
|
||||
type: 'select',
|
||||
setting: 'mail_driver',
|
||||
options: Object.keys(this.driverFields).reduce((memo, val) => ({ ...memo, [val]: val }), {}),
|
||||
options: Object.keys(this.driverFields!).reduce((memo, val) => ({ ...memo, [val]: val }), {}),
|
||||
label: app.translator.trans('core.admin.email.driver_heading'),
|
||||
})}
|
||||
{this.status.sending ||
|
||||
{this.status!.sending ||
|
||||
Alert.component(
|
||||
{
|
||||
dismissible: false,
|
||||
@@ -84,7 +102,7 @@ export default class MailPage extends AdminPage {
|
||||
setting: field,
|
||||
options: fieldInfo,
|
||||
}),
|
||||
this.status.errors[field] && <p className="ValidationError">{this.status.errors[field]}</p>,
|
||||
this.status!.errors[field] && <p className="ValidationError">{this.status!.errors[field]}</p>,
|
||||
];
|
||||
})}
|
||||
</div>
|
||||
@@ -93,7 +111,7 @@ export default class MailPage extends AdminPage {
|
||||
{this.submitButton()}
|
||||
|
||||
<FieldSet label={app.translator.trans('core.admin.email.send_test_mail_heading')} className="MailPage-MailSettings">
|
||||
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user.email() })}</div>
|
||||
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user!.email() })}</div>
|
||||
{Button.component(
|
||||
{
|
||||
className: 'Button Button--primary',
|
||||
@@ -108,10 +126,11 @@ export default class MailPage extends AdminPage {
|
||||
}
|
||||
|
||||
sendTestEmail() {
|
||||
if (this.saving || this.sendingTest) return;
|
||||
if (this.sendingTest) return;
|
||||
|
||||
this.sendingTest = true;
|
||||
app.alerts.dismiss(this.testEmailSuccessAlert);
|
||||
|
||||
if (this.testEmailSuccessAlert) app.alerts.dismiss(this.testEmailSuccessAlert);
|
||||
|
||||
app
|
||||
.request({
|
||||
@@ -129,7 +148,7 @@ export default class MailPage extends AdminPage {
|
||||
});
|
||||
}
|
||||
|
||||
saveSettings(e) {
|
||||
super.saveSettings(e).then(this.refresh());
|
||||
saveSettings(e: SaveSubmitEvent) {
|
||||
return super.saveSettings(e).then(() => this.refresh());
|
||||
}
|
||||
}
|
@@ -20,8 +20,8 @@ export default class PermissionsPage extends AdminPage {
|
||||
return [
|
||||
<div className="PermissionsPage-groups">
|
||||
{app.store
|
||||
.all('groups')
|
||||
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
|
||||
.all<Group>('groups')
|
||||
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
|
||||
.map((group) => (
|
||||
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
|
||||
{GroupBadge.component({
|
@@ -1,16 +1,18 @@
|
||||
import app from '../../forum/app';
|
||||
import Page from '../../common/components/Page';
|
||||
import Page, { IPageAttrs } from '../../common/components/Page';
|
||||
import NotificationList from './NotificationList';
|
||||
import type Mithril from 'mithril';
|
||||
import extractText from '../../common/utils/extractText';
|
||||
|
||||
/**
|
||||
* The `NotificationsPage` component shows the notifications list. It is only
|
||||
* used on mobile devices where the notifications dropdown is within the drawer.
|
||||
*/
|
||||
export default class NotificationsPage extends Page {
|
||||
oninit(vnode) {
|
||||
export default class NotificationsPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends Page<CustomAttrs> {
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.history.push('notifications');
|
||||
app.history.push('notifications', extractText(app.translator.trans('core.forum.notifications.title')));
|
||||
|
||||
app.notifications.load();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import app from '../../forum/app';
|
||||
import UserPage from './UserPage';
|
||||
import UserPage, { IUserPageAttrs } from './UserPage';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import Switch from '../../common/components/Switch';
|
||||
import Button from '../../common/components/Button';
|
||||
@@ -8,18 +8,22 @@ import NotificationGrid from './NotificationGrid';
|
||||
import ChangePasswordModal from './ChangePasswordModal';
|
||||
import ChangeEmailModal from './ChangeEmailModal';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import extractText from '../../common/utils/extractText';
|
||||
import type Mithril from 'mithril';
|
||||
|
||||
/**
|
||||
* The `SettingsPage` component displays the user's settings control panel, in
|
||||
* the context of their user profile.
|
||||
*/
|
||||
export default class SettingsPage extends UserPage {
|
||||
oninit(vnode) {
|
||||
export default class SettingsPage<CustomAttrs extends IUserPageAttrs = IUserPageAttrs> extends UserPage<CustomAttrs> {
|
||||
discloseOnlineLoading?: boolean;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.show(app.session.user);
|
||||
this.show(app.session.user!);
|
||||
|
||||
app.setTitle(app.translator.trans('core.forum.settings.title'));
|
||||
app.setTitle(extractText(app.translator.trans('core.forum.settings.title')));
|
||||
}
|
||||
|
||||
content() {
|
||||
@@ -32,17 +36,17 @@ export default class SettingsPage extends UserPage {
|
||||
|
||||
/**
|
||||
* Build an item list for the user's settings controls.
|
||||
*
|
||||
* @return {ItemList<import('mithril').Children>}
|
||||
*/
|
||||
settingsItems() {
|
||||
const items = new ItemList();
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
['account', 'notifications', 'privacy'].forEach((section) => {
|
||||
const sectionItems = `${section}Items` as 'accountItems' | 'notificationsItems' | 'privacyItems';
|
||||
|
||||
items.add(
|
||||
section,
|
||||
<FieldSet className={`Settings-${section}`} label={app.translator.trans(`core.forum.settings.${section}_heading`)}>
|
||||
{this[`${section}Items`]().toArray()}
|
||||
{this[sectionItems]().toArray()}
|
||||
</FieldSet>
|
||||
);
|
||||
});
|
||||
@@ -52,11 +56,9 @@ export default class SettingsPage extends UserPage {
|
||||
|
||||
/**
|
||||
* Build an item list for the user's account settings.
|
||||
*
|
||||
* @return {ItemList<import('mithril').Children>}
|
||||
*/
|
||||
accountItems() {
|
||||
const items = new ItemList();
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'changePassword',
|
||||
@@ -77,11 +79,9 @@ export default class SettingsPage extends UserPage {
|
||||
|
||||
/**
|
||||
* Build an item list for the user's notification settings.
|
||||
*
|
||||
* @return {ItemList<import('mithril').Children>}
|
||||
*/
|
||||
notificationsItems() {
|
||||
const items = new ItemList();
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add('notificationGrid', <NotificationGrid user={this.user} />);
|
||||
|
||||
@@ -90,20 +90,18 @@ export default class SettingsPage extends UserPage {
|
||||
|
||||
/**
|
||||
* Build an item list for the user's privacy settings.
|
||||
*
|
||||
* @return {ItemList<import('mithril').Children>}
|
||||
*/
|
||||
privacyItems() {
|
||||
const items = new ItemList();
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'discloseOnline',
|
||||
<Switch
|
||||
state={this.user.preferences().discloseOnline}
|
||||
onchange={(value) => {
|
||||
state={this.user!.preferences()?.discloseOnline}
|
||||
onchange={(value: boolean) => {
|
||||
this.discloseOnlineLoading = true;
|
||||
|
||||
this.user.savePreferences({ discloseOnline: value }).then(() => {
|
||||
this.user!.savePreferences({ discloseOnline: value }).then(() => {
|
||||
this.discloseOnlineLoading = false;
|
||||
m.redraw();
|
||||
});
|
@@ -11,6 +11,8 @@ core:
|
||||
appearance:
|
||||
colored_header_label: Colored Header
|
||||
colors_heading: Colors
|
||||
colors_primary_label: Primary Color
|
||||
colors_secondary_label: Secondary Color
|
||||
colors_text: "Choose two colors to theme your forum with. The first will be used as a highlight color, while the second will be used to style background elements."
|
||||
custom_footer_heading: Custom Footer
|
||||
custom_footer_text: => core.ref.custom_footer_text
|
||||
|
Reference in New Issue
Block a user