1
0
mirror of https://github.com/flarum/core.git synced 2025-07-30 21:20:24 +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:
Sami Mazouz
2022-08-08 22:11:58 +01:00
committed by GitHub
parent 44825f1b94
commit 1948f25151
10 changed files with 95 additions and 63 deletions

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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

View File

@@ -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',

View File

@@ -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>

View File

@@ -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());
}
}

View File

@@ -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({

View File

@@ -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();

View File

@@ -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();
});

View File

@@ -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