1
0
mirror of https://github.com/flarum/core.git synced 2025-08-13 03:44:32 +02:00

Compare commits

...

11 Commits

Author SHA1 Message Date
Alexander Skvortsov
b475049269 Update framework/core/js/src/forum/components/LogInButtons.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:34 -04:00
Alexander Skvortsov
392b90710c Update framework/core/js/src/common/components/EditUserModal.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:30 -04:00
Alexander Skvortsov
5b43e3cfd7 Update framework/core/js/src/common/components/AlertManager.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:25 -04:00
Alexander Skvortsov
b9ed71700a Update framework/core/js/src/admin/components/ExtensionPage.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:20 -04:00
Alexander Skvortsov
6712fef3cf Update extensions/tags/js/src/common/helpers/tagsLabel.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:12 -04:00
Alexander Skvortsov
5fa968df2f Update extensions/tags/js/src/common/helpers/tagLabel.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:07 -04:00
Alexander Skvortsov
b88db06a74 Update extensions/tags/js/src/common/helpers/tagIcon.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:26:03 -04:00
Alexander Skvortsov
d34cf1d925 Update framework/core/js/src/forum/components/LogInModal.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:25:57 -04:00
Alexander Skvortsov
c6287e1520 Update framework/core/js/src/forum/components/SignUpModal.tsx
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-03-24 21:25:51 -04:00
Alexander Skvortsov
222d9ccff7 fix: return Mithril.Children in translator outputs
This avoids having `NestedStringArray` in generated dist typings which can make it hard to override content with null.
2022-03-24 17:48:06 -04:00
Alexander Skvortsov
c9fdd0753f fix: eliminate JSX.Element from generated typings
Either add JSDoc or TypeScript annotations so that `Mithril.Children` is always returned in favor of `JSX.Element`.
2022-03-24 17:48:01 -04:00
105 changed files with 391 additions and 126 deletions

View File

@@ -13,6 +13,9 @@ export default class FlagList extends Component {
this.state = this.attrs.state;
}
/**
* @return {import('mithril').Children}
*/
view() {
const flags = this.state.cache || [];

View File

@@ -24,6 +24,9 @@ export default class FlagPostModal extends Modal {
return app.translator.trans('flarum-flags.forum.flag_post.title');
}
/**
* @return {import('mithril').Children}
*/
content() {
if (this.success) {
return (

View File

@@ -11,6 +11,9 @@ export default class FlagsDropdown extends NotificationsDropdown {
super.initAttrs(attrs);
}
/**
* @return {import('mithril').Children}
*/
getMenu() {
return (
<div className={'Dropdown-menu ' + this.attrs.menuClassName} onclick={this.menuClick.bind(this)}>

View File

@@ -18,6 +18,9 @@ export default class FlagsPage extends Page {
this.bodyClass = 'App--flags';
}
/**
* @return {import('mithril').Children}
*/
view() {
return (
<div className="FlagsPage">

View File

@@ -64,7 +64,7 @@ export default class Updater<Attrs> extends Component<Attrs> {
super.oninit(vnode);
}
view() {
view(): Mithril.Children {
const extensions = this.getExtensionUpdates();
let coreUpdate: UpdatedPackage | undefined = this.getCoreUpdate();
let core: any;

View File

@@ -26,7 +26,7 @@ export default class WhyNotModal<Attrs extends WhyNotModalAttrs = WhyNotModalAtt
this.requestWhyNot();
}
content() {
content(): Mithril.Children {
return <div className="Modal-body">{this.loading ? <LoadingIndicator /> : <pre className="WhyNotModal-contents">{this.whyNot}</pre>}</div>;
}

View File

@@ -46,6 +46,9 @@ export default class StatisticsWidget extends DashboardWidget {
return 'StatisticsWidget';
}
/**
* @return {import('mithril').Children}
*/
content() {
const thisPeriod = this.periods[this.selectedPeriod];

View File

@@ -7,7 +7,7 @@ import { slug } from 'flarum/common/utils/string';
import Stream from 'flarum/common/utils/Stream';
import tagLabel from '../../common/helpers/tagLabel';
import type Mithril from 'mithril';
import Mithril from 'mithril';
import type Tag from '../../common/models/Tag';
import extractText from 'flarum/common/utils/extractText';
import { ModelIdentifier } from 'flarum/common/Model';
@@ -50,13 +50,13 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
return 'EditTagModal Modal--small';
}
title() {
title(): Mithril.Children {
return this.name()
? tagLabel(app.store.createRecord('tags', { attributes: this.submitData() }))
? tagLabel(app.store.createRecord<Tag>('tags', { attributes: this.submitData() }))
: app.translator.trans('flarum-tags.admin.edit_tag.title');
}
content() {
content(): Mithril.Children {
return (
<div className="Modal-body">
<div className="Form">
@@ -67,7 +67,7 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
}
fields() {
const items = new ItemList();
const items = new ItemList<Mithril.Children>();
items.add('name', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.name_label')}</label>

View File

@@ -51,6 +51,9 @@ export default class TagsPage extends ExtensionPage {
});
}
/**
* @return {import('mithril').Children}
*/
content() {
if (this.loading) {
return <LoadingIndicator />;
@@ -63,7 +66,7 @@ export default class TagsPage extends ExtensionPage {
const maxSecondaryTags = this.setting('flarum-tags.max_secondary_tags', 0);
const tags = sortTags(app.store.all('tags').filter(tag => !tag.parent()));
return (
<div className="TagsContent">
<div className="TagsContent-list">

View File

@@ -1,6 +1,9 @@
import { ComponentAttrs } from 'flarum/common/Component';
import classList from 'flarum/common/utils/classList';
import type Tag from '../models/Tag';
import type Mithril from 'mithril';
export default function tagIcon(tag, attrs = {}, settings = {}) {
export default function tagIcon(tag: Tag, attrs: ComponentAttrs = {}, settings: {useColor?: boolean} = {}): Mithril.Children {
const hasIcon = tag && tag.icon();
const { useColor = true } = settings;

View File

@@ -1,8 +1,13 @@
import app from 'flarum/common/app';
import extract from 'flarum/common/utils/extract';
import Link from 'flarum/common/components/Link';
import tagIcon from './tagIcon';
import type Tag from '../models/Tag';
import type { ComponentAttrs } from 'flarum/common/Component';
import type Mithril from 'mithril';
export default function tagLabel(tag, attrs = {}) {
export default function tagLabel(tag?: Tag, attrs: ComponentAttrs = {}): Mithril.Children {
attrs.style = attrs.style || {};
attrs.className = 'TagLabel ' + (attrs.className || '');
@@ -28,11 +33,15 @@ export default function tagLabel(tag, attrs = {}) {
attrs.className += ' untagged';
}
return (
m((link ? Link : 'span'), attrs,
<span className="TagLabel-text">
const children = (
<span className="TagLabel-text">
{tag && tag.icon() && tagIcon(tag, {}, {useColor: false})} {tagText}
</span>
)
);
if (link) {
return <Link {...attrs}>{children}</Link>
}
return <span {...attrs}>{children}</span>;
}

View File

@@ -1,15 +1,18 @@
import extract from 'flarum/common/utils/extract';
import tagLabel from './tagLabel';
import sortTags from '../utils/sortTags';
import type Tag from '../models/Tag';
import type { ComponentAttrs } from 'flarum/common/Component';
import type Mithril from 'mithril';
export default function tagsLabel(tags, attrs = {}) {
export default function tagsLabel(tags: Tag[], attrs: ComponentAttrs = {}): Mithril.Children {
const children = [];
const link = extract(attrs, 'link');
attrs.className = 'TagsLabel ' + (attrs.className || '');
if (tags) {
sortTags(tags).forEach(tag => {
sortTags(tags).forEach((tag: Tag) => {
if (tag || tags.length === 1) {
children.push(tagLabel(tag, {link}));
}

View File

@@ -139,6 +139,9 @@ export default class TagDiscussionModal extends Modal<TagDiscussionModalAttrs> {
return '';
}
/**
* @return {import('mithril').Children}
*/
content() {
if (this.tagsLoading || !this.tags) {
return <LoadingIndicator />;

View File

@@ -2,6 +2,9 @@ import Component from 'flarum/common/Component';
import tagIcon from '../../common/helpers/tagIcon';
export default class TagHero extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
const tag = this.attrs.model;
const color = tag.color();

View File

@@ -4,6 +4,9 @@ import classList from 'flarum/common/utils/classList';
import tagIcon from '../../common/helpers/tagIcon';
export default class TagLinkButton extends LinkButton {
/**
* @return {import('mithril').Children}
*/
view(vnode) {
const tag = this.attrs.model;
const active = this.constructor.isActive(this.attrs);

View File

@@ -35,6 +35,9 @@ export default class TagsPage extends Page {
});
}
/**
* @return {import('mithril').Children}
*/
view() {
if (this.loading) {
return <LoadingIndicator />;

View File

@@ -6,6 +6,9 @@ import classList from 'flarum/common/utils/classList';
* @TODO move to core
*/
export default class ToggleButton extends Component {
/**
* @return {import('mithril').Children}
*/
view(vnode) {
const { className, isToggled, ...attrs } = this.attrs;
const icon = isToggled ? 'far fa-check-circle' : 'far fa-circle';

View File

@@ -1,4 +1,6 @@
declare module '@askvortsov/rich-icu-message-formatter' {
import Mithril from 'mithril';
type IValues = Record<string, any>;
type ITypeHandler = (
@@ -10,16 +12,13 @@ declare module '@askvortsov/rich-icu-message-formatter' {
) => string;
type IRichHandler = (tag: any, values: IValues, contents: string) => any;
type ValueOrArray<T> = T | ValueOrArray<T>[];
type NestedStringArray = ValueOrArray<string>;
export class RichMessageFormatter {
locale: string | null;
constructor(locale: string | null, typeHandlers: Record<string, ITypeHandler>, richHandler: IRichHandler);
format(message: string, values: IValues): string;
process(message: string, values: IValues): NestedStringArray;
rich(message: string, values: IValues): NestedStringArray;
process(message: string, values: IValues): Mithril.Children;
rich(message: string, values: IValues): Mithril.Children;
}
export function mithrilRichHandler(tag: any, values: IValues, contents: string): any;

View File

@@ -1,9 +1,15 @@
import Component from '../../common/Component';
import Component, { ComponentAttrs } from '../../common/Component';
import classList from '../../common/utils/classList';
import icon from '../../common/helpers/icon';
import type Mithril from 'mithril';
export default class AdminHeader extends Component {
view(vnode) {
export interface IAdminHeaderAttrs extends ComponentAttrs {
icon: string;
description: string;
}
export default class AdminHeader<CustomAttrs extends IAdminHeaderAttrs = IAdminHeaderAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
return [
<div className={classList(['AdminHeader', this.attrs.className])}>
<div className="container">

View File

@@ -14,6 +14,9 @@ export default class AdminNav extends Component {
this.query = Stream('');
}
/**
* @return {import('mithril').Children}
*/
view() {
return (
<SelectDropdown className="AdminNav App-titleControl AdminNav-Main" buttonClassName="Button">

View File

@@ -17,6 +17,9 @@ export default class AppearancePage extends AdminPage {
};
}
/**
* @return {import('mithril').Children}
*/
content() {
return [
<div className="Form">

View File

@@ -38,6 +38,9 @@ export default class BasicsPage extends AdminPage {
};
}
/**
* @return {import('mithril').Children}
*/
content() {
return [
<div className="Form">

View File

@@ -10,6 +10,9 @@ export default class EditCustomCssModal extends SettingsModal {
return app.translator.trans('core.admin.edit_css.title');
}
/**
* @return {import('mithril').Children}
*/
form() {
return [
<p>

View File

@@ -10,6 +10,9 @@ export default class EditCustomFooterModal extends SettingsModal {
return app.translator.trans('core.admin.edit_footer.title');
}
/**
* @return {import('mithril').Children}
*/
form() {
return [
<p>{app.translator.trans('core.admin.edit_footer.customize_text')}</p>,

View File

@@ -10,6 +10,9 @@ export default class EditCustomHeaderModal extends SettingsModal {
return app.translator.trans('core.admin.edit_header.title');
}
/**
* @return {import('mithril').Children}
*/
form() {
return [
<p>{app.translator.trans('core.admin.edit_header.customize_text')}</p>,

View File

@@ -41,6 +41,9 @@ export default class EditGroupModal extends Modal {
];
}
/**
* @return {import('mithril').Children}
*/
content() {
return (
<div className="Modal-body">

View File

@@ -54,7 +54,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
return this.extension.id + '-Page';
}
view(vnode: Mithril.VnodeDOM<Attrs, this>) {
view(vnode: Mithril.VnodeDOM<Attrs, this>): Mithril.Children {
if (!this.extension) return null;
return (
@@ -71,7 +71,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
);
}
header() {
header(): Mithril.Children {
const isEnabled = this.isEnabled();
return [
@@ -106,8 +106,8 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
];
}
sections(vnode: Mithril.VnodeDOM<Attrs, this>) {
const items = new ItemList();
sections(vnode: Mithril.VnodeDOM<Attrs, this>): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
items.add('content', this.content(vnode));
@@ -131,7 +131,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
return items;
}
content(vnode: Mithril.VnodeDOM<Attrs, this>) {
content(vnode: Mithril.VnodeDOM<Attrs, this>): Mithril.Children {
const settings = app.extensionData.getSettings(this.extension.id);
return (
@@ -150,7 +150,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
);
}
topItems() {
topItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
items.add('version', <span className="ExtensionVersion">{this.extension.version}</span>);

View File

@@ -16,6 +16,9 @@ export default class ExtensionsWidget extends DashboardWidget {
return 'ExtensionsWidget';
}
/**
* @return {import('mithril').Children}
*/
content() {
const categories = app.extensionCategories;
@@ -26,6 +29,9 @@ export default class ExtensionsWidget extends DashboardWidget {
);
}
/**
* @return {import('mithril').Children}
*/
extensionCategory(category) {
return (
<div className="ExtensionList-Category">
@@ -35,6 +41,9 @@ export default class ExtensionsWidget extends DashboardWidget {
);
}
/**
* @return {import('mithril').Children}
*/
extensionWidget(extension) {
return (
<li className={'ExtensionListItem ' + (!isExtensionEnabled(extension.id) ? 'disabled' : '')}>

View File

@@ -7,6 +7,9 @@ import listItems from '../../common/helpers/listItems';
* default skin, these are shown just to the right of the forum title.
*/
export default class HeaderPrimary extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}

View File

@@ -9,6 +9,9 @@ import listItems from '../../common/helpers/listItems';
* The `HeaderSecondary` component displays secondary header controls.
*/
export default class HeaderSecondary extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}

View File

@@ -42,6 +42,9 @@ export default class MailPage extends AdminPage {
});
}
/**
* @return {import('mithril').Children}
*/
content() {
if (this.loading) {
return <LoadingIndicator />;

View File

@@ -33,7 +33,7 @@ export interface ScopeItem {
export interface IPermissionGridAttrs extends ComponentAttrs {}
export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = IPermissionGridAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
view(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
const scopes = this.scopeItems().toArray();
const permissionCells = (permission: PermissionGridEntry | { children: PermissionGridEntry[] }) => {

View File

@@ -16,6 +16,9 @@ export default class PermissionsPage extends AdminPage {
};
}
/**
* @return {import('mithril').Children}
*/
content() {
return [
<div className="PermissionsPage-groups">

View File

@@ -38,7 +38,7 @@ export default class ReadmeModal<CustomAttrs extends IReadmeModalAttrs = IReadme
});
}
content() {
content(): Mithril.Children {
const text = app.translator.trans('core.admin.extension.readme.no_readme');
return (

View File

@@ -18,10 +18,16 @@ export default class SessionDropdown extends Dropdown {
attrs.menuClassName = 'Dropdown-menu--right';
}
/**
* @return {import('mithril').Children}
*/
view(vnode) {
return super.view({ ...vnode, children: this.items().toArray() });
}
/**
* @returns {import('mithril').Children}
*/
getButtonContent() {
const user = app.session.user;

View File

@@ -16,6 +16,9 @@ export default class SettingsModal extends Modal {
return '';
}
/**
* @return {import('mithril').Children}
*/
content() {
return (
<div className="Modal-body">
@@ -28,6 +31,9 @@ export default class SettingsModal extends Modal {
);
}
/**
* @return {import('mithril').Children}
*/
submitButton() {
return (
<Button type="submit" className="Button Button--primary" loading={this.loading} disabled={!this.changed()}>

View File

@@ -11,6 +11,9 @@ export default class StatusWidget extends DashboardWidget {
return 'StatusWidget';
}
/**
* @return {import('mithril').Children}
*/
content() {
return <ul>{listItems(this.items().toArray())}</ul>;
}

View File

@@ -4,6 +4,9 @@ import Button from '../../common/components/Button';
export default class UploadImageButton extends Button {
loading = false;
/**
* @return {import('mithril').Children}
*/
view(vnode) {
this.attrs.loading = this.loading;
this.attrs.className = (this.attrs.className || '') + ' Button';

View File

@@ -77,7 +77,7 @@ export default class UserListPage extends AdminPage {
/**
* Component to render.
*/
content() {
content(): Mithril.Children {
if (typeof this.pageData === 'undefined') {
this.loadPage(0);

View File

@@ -22,7 +22,7 @@ export interface AlertAttrs extends ComponentAttrs {
* some controls, and may be dismissible.
*/
export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<T> {
view(vnode: Mithril.VnodeDOM<T, this>) {
view(vnode: Mithril.VnodeDOM<T, this>): Mithril.Children {
const attrs = Object.assign({}, this.attrs);
const type = extract(attrs, 'type');

View File

@@ -1,31 +0,0 @@
import Component from '../Component';
/**
* The `AlertManager` component provides an area in which `Alert` components can
* be shown and dismissed.
*/
export default class AlertManager extends Component {
oninit(vnode) {
super.oninit(vnode);
this.state = this.attrs.state;
}
view() {
return (
<div class="AlertManager">
{Object.entries(this.state.getActiveAlerts()).map(([key, alert]) => {
const urgent = alert.attrs.type === 'error';
return (
<div class="AlertManager-alert" role="alert" aria-live={urgent ? 'assertive' : 'polite'}>
<alert.componentClass {...alert.attrs} ondismiss={this.state.dismiss.bind(this.state, key)}>
{alert.children}
</alert.componentClass>
</div>
);
})}
</div>
);
}
}

View File

@@ -0,0 +1,39 @@
import type Mithril from 'mithril';
import Component, { ComponentAttrs } from '../Component';
import type AlertManagerState from '../states/AlertManagerState';
export interface IAlertManagerAttrs extends ComponentAttrs {
state: AlertManagerState;
}
/**
* The `AlertManager` component provides an area in which `Alert` components can
* be shown and dismissed.
*/
export default class AlertManager<CustomAttrs extends IAlertManagerAttrs = IAlertManagerAttrs> extends Component<CustomAttrs> {
alertsState!: AlertManagerState;
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
super.oninit(vnode);
this.alertsState = this.attrs.state;
}
view(): Mithril.Children {
return (
<div class="AlertManager">
{Object.entries(this.alertsState.getActiveAlerts()).map(([key, alert]) => {
const urgent = alert.attrs.type === 'error';
return (
<div class="AlertManager-alert" role="alert" aria-live={urgent ? 'assertive' : 'polite'}>
<alert.componentClass {...alert.attrs} ondismiss={this.alertsState.dismiss.bind(this.alertsState, parseInt(key))}>
{alert.children}
</alert.componentClass>
</div>
);
})}
</div>
);
}
}

View File

@@ -17,6 +17,9 @@ import classList from '../utils/classList';
* All other attrs will be assigned as attributes on the badge element.
*/
export default class Badge extends Component {
/**
* @returns {import('mithril').Children}
*/
view() {
const { type, icon: iconName, label, color, style = {}, ...attrs } = this.attrs;

View File

@@ -67,7 +67,7 @@ export interface IButtonAttrs extends ComponentAttrs {
* styles can be applied by providing `className="Button"` to the Button component.
*/
export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>): Mithril.Children {
let { type, title, 'aria-label': ariaLabel, icon: iconName, disabled, loading, className, class: _class, ...attrs } = this.attrs;
// If no `type` attr provided, set to "button"

View File

@@ -17,6 +17,9 @@ import withAttr from '../utils/withAttr';
* - `children` A text label to display next to the checkbox.
*/
export default class Checkbox extends Component {
/**
* @returns {import('mithril').Children}
*/
view(vnode) {
// Sometimes, false is stored in the DB as '0'. This is a temporary
// conversion layer until a more robust settings encoding is introduced

View File

@@ -5,7 +5,7 @@ import classList from '../utils/classList';
import icon from '../helpers/icon';
export default class ColorPreviewInput extends Component {
view(vnode: Mithril.Vnode<ComponentAttrs, this>) {
view(vnode: Mithril.Vnode<ComponentAttrs, this>): Mithril.Children {
const { className, id, ...attrs } = this.attrs;
attrs.type ||= 'text';

View File

@@ -36,6 +36,9 @@ export default class Dropdown extends Component {
this.showing = false;
}
/**
* @returns {import('mithril').Children}
*/
view(vnode) {
const items = vnode.children ? listItems(vnode.children) : [];
const renderItems = this.attrs.lazyDraw ? this.showing : true;
@@ -134,6 +137,9 @@ export default class Dropdown extends Component {
];
}
/**
* @returns {import('mithril').Children}
*/
getMenu(items) {
return <ul className={'Dropdown-menu dropdown-menu ' + this.attrs.menuClassName}>{items}</ul>;
}

View File

@@ -45,11 +45,11 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
return 'EditUserModal Modal--small';
}
title() {
title(): Mithril.Children {
return app.translator.trans('core.lib.edit_user.title');
}
content() {
content(): Mithril.Children {
const fields = this.fields().toArray();
return (
<div className="Modal-body">
@@ -58,8 +58,8 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
);
}
fields() {
const items = new ItemList();
fields(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
if (this.attrs.user.canEditCredentials()) {
items.add(

View File

@@ -11,6 +11,9 @@ import listItems from '../helpers/listItems';
* The children should be an array of items to show in the fieldset.
*/
export default class FieldSet extends Component {
/**
* @returns {import('mithril').Children}
*/
view(vnode) {
return (
<fieldset className={this.attrs.className}>

View File

@@ -10,6 +10,9 @@ import extract from '../utils/extract';
* `true` for the link to be external.
*/
export default class Link extends Component {
/**
* @returns {import('mithril').Children}
*/
view(vnode) {
let { options = {}, ...attrs } = vnode.attrs;

View File

@@ -1,6 +1,7 @@
import app from '../../common/app';
import Component, { ComponentAttrs } from '../Component';
import classList from '../utils/classList';
import type Mithril from 'mithril';
export interface LoadingIndicatorAttrs extends ComponentAttrs {
/**
@@ -55,7 +56,7 @@ export interface LoadingIndicatorAttrs extends ComponentAttrs {
* All other attrs will be assigned as attributes on the DOM element.
*/
export default class LoadingIndicator extends Component<LoadingIndicatorAttrs> {
view() {
view(): Mithril.Children {
const { display = 'block', size = 'medium', containerClassName, className, ...attrs } = this.attrs;
const completeClassName = classList('LoadingIndicator', className);

View File

@@ -81,7 +81,7 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
/**
* @todo split into FormModal and Modal in 2.0
*/
view() {
view(): Mithril.Children {
if (this.alertAttrs) {
this.alertAttrs.dismissible = false;
}

View File

@@ -19,6 +19,9 @@ import LinkButton from './LinkButton';
* there is no more history to pop.
*/
export default class Navigation extends Component {
/**
* @returns {import('mithril').Children}
*/
view() {
const { history, pane } = app;

View File

@@ -1,19 +0,0 @@
import Component from '../Component';
/**
* The `Placeholder` component displays a muted text with some call to action,
* usually used as an empty state.
*
* ### Attrs
*
* - `text`
*/
export default class Placeholder extends Component {
view() {
return (
<div className="Placeholder">
<p>{this.attrs.text}</p>
</div>
);
}
}

View File

@@ -0,0 +1,20 @@
import type Mithril from 'mithril';
import Component, { ComponentAttrs } from '../Component';
interface IPlaceholderAttrs extends ComponentAttrs {
text: Mithril.Children;
}
/**
* The `Placeholder` component displays a muted text with some call to action,
* usually used as an empty state.
*/
export default class Placeholder<CustomAttrs extends IPlaceholderAttrs = IPlaceholderAttrs> extends Component<CustomAttrs> {
view(): Mithril.Children {
return (
<div className="Placeholder">
<p>{this.attrs.text}</p>
</div>
);
}
}

View File

@@ -1,3 +1,4 @@
import type Mithril from 'mithril';
import type RequestError from '../utils/RequestError';
import Modal, { IInternalModalAttrs } from './Modal';
@@ -15,7 +16,7 @@ export default class RequestErrorModal<CustomAttrs extends IRequestErrorModalAtt
return this.attrs.error.xhr ? `${this.attrs.error.xhr.status} ${this.attrs.error.xhr.statusText}` : '';
}
content() {
content(): Mithril.Children {
const { error, formattedError } = this.attrs;
let responseText;

View File

@@ -16,6 +16,9 @@ import classList from '../utils/classList';
* Other attributes are passed directly to the `<select>` element rendered to the DOM.
*/
export default class Select extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
const {
options,

View File

@@ -41,6 +41,9 @@ export default class SelectDropdown extends Dropdown {
attrs.className += ' Dropdown--select';
}
/**
* @return {import('mithril').Children}
*/
getButtonContent(children) {
const activeChild = children.find(isActive);
let label = (activeChild && activeChild.children) || this.attrs.defaultLabel;

View File

@@ -1,14 +1,13 @@
import type Mithril from 'mithril';
import Component from '../Component';
/**
* The `Separator` component defines a menu separator item.
*/
class Separator extends Component {
view() {
export default class Separator extends Component {
static isListItem = true;
view(): Mithril.Children {
return <li className="Dropdown-separator" />;
}
}
Separator.isListItem = true;
export default Separator;

View File

@@ -14,6 +14,9 @@ export default class SplitDropdown extends Dropdown {
attrs.menuClassName += ' Dropdown-menu--right';
}
/**
* @return {import('mithril').Children}
*/
getButton(children) {
// Make a copy of the attrs of the first child component. We will assign
// these attrs to a new button, so that it has exactly the same behaviour as

View File

@@ -38,6 +38,9 @@ export default class TextEditor extends Component {
this.disabled = !!this.attrs.disabled;
}
/**
* @return {import('mithril').Children}
*/
view() {
return (
<div className="TextEditor">

View File

@@ -12,6 +12,9 @@ import Tooltip from './Tooltip';
* - `title` - Tooltip for the button
*/
export default class TextEditorButton extends Button {
/**
* @return {import('mithril').Children}
*/
view(vnode) {
const originalView = super.view(vnode);

View File

@@ -13,7 +13,7 @@ export interface AlertState {
}
export default class AlertManagerState {
protected activeAlerts: { [id: number]: AlertState } = {};
protected activeAlerts: { [id: AlertIdentifier]: AlertState } = {};
protected alertId = 0;
getActiveAlerts() {

View File

@@ -36,6 +36,9 @@ export default class AvatarEditor extends Component {
this.isDraggedOver = false;
}
/**
* @return {import('mithril').Children}
*/
view() {
const user = this.attrs.user;

View File

@@ -41,6 +41,9 @@ export default class ChangeEmailModal extends Modal {
return app.translator.trans('core.forum.change_email.title');
}
/**
* @return {import('mithril').Children}
*/
content() {
if (this.success) {
return (

View File

@@ -15,6 +15,9 @@ export default class ChangePasswordModal extends Modal {
return app.translator.trans('core.forum.change_password.title');
}
/**
* @return {import('mithril').Children}
*/
content() {
return (
<div className="Modal-body">

View File

@@ -33,6 +33,9 @@ export default class Composer extends Component {
this.prevPosition = this.state.position;
}
/**
* @return {import('mithril').Children}
*/
view() {
const body = this.state.body;
const classes = {

View File

@@ -47,6 +47,9 @@ export default class ComposerBody extends Component {
this.composer.fields.content(this.attrs.originalContent || '');
}
/**
* @return {import('mithril').Children}
*/
view() {
return (
<ConfirmDocumentUnload when={this.hasChanges.bind(this)}>

View File

@@ -20,6 +20,9 @@ export default class ComposerPostPreview extends Component {
attrs.surround = attrs.surround || ((preview) => preview());
}
/**
* @return {import('mithril').Children}
*/
view() {
return <div className={this.attrs.className} />;
}

View File

@@ -10,6 +10,9 @@ import listItems from '../../common/helpers/listItems';
* - `discussion`
*/
export default class DiscussionHero extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
return (
<header className="Hero DiscussionHero">

View File

@@ -13,6 +13,9 @@ import Placeholder from '../../common/components/Placeholder';
* - `state` A DiscussionListState object that represents the discussion lists's state.
*/
export default class DiscussionList extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
/**
* @type {import('../states/DiscussionListState').default}

View File

@@ -62,7 +62,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
};
}
view() {
view(): Mithril.Children {
const discussion = this.attrs.discussion;
const user = discussion.user();
const isUnread = discussion.isUnread();
@@ -164,10 +164,8 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
/**
* Determine whether or not the number of replies should be shown instead of
* the number of unread posts.
*
* @return {boolean}
*/
showRepliesCount() {
showRepliesCount(): boolean {
return this.attrs.params.sort === 'replies';
}
@@ -210,7 +208,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
return items;
}
replyCountItem() {
replyCountItem(): Mithril.Children {
const discussion = this.attrs.discussion;
const showUnread = !this.showRepliesCount() && discussion.isUnread();

View File

@@ -17,6 +17,9 @@ const hotEdge = (e) => {
* - `state` A DiscussionListState object that represents the discussion lists's state.
*/
export default class DiscussionListPane extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
if (!this.attrs.state.hasItems()) {
return;

View File

@@ -79,7 +79,7 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
}
}
view() {
view(): Mithril.Children {
return (
<div className="DiscussionPage">
<DiscussionListPane state={app.discussions} />

View File

@@ -15,6 +15,9 @@ export default class DiscussionRenamedPost extends EventPost {
return 'fas fa-pencil-alt';
}
/**
* @return {import('mithril').Children}
*/
description(data) {
const renamed = app.translator.trans('core.forum.post_stream.discussion_renamed_text', data);
const oldName = app.translator.trans('core.forum.post_stream.discussion_renamed_old_tooltip', data);
@@ -22,6 +25,9 @@ export default class DiscussionRenamedPost extends EventPost {
return <span title={extractText(oldName)}>{renamed}</span>;
}
/**
* @return {Record<string, unknown>}
*/
descriptionData() {
const post = this.attrs.post;
const oldTitle = post.content()[0];

View File

@@ -24,6 +24,9 @@ export default class DiscussionsUserPage extends UserPage {
this.state.refresh();
}
/**
* @return {import('mithril').Children}
*/
content() {
return <div className="DiscussionsUserPage">{DiscussionList.component({ state: this.state })}</div>;
}

View File

@@ -36,7 +36,7 @@ export default class ForgotPasswordModal<CustomAttrs extends IForgotPasswordModa
return app.translator.trans('core.forum.forgot_password.title');
}
content() {
content(): Mithril.Children {
if (this.success) {
return (
<div className="Modal-body">

View File

@@ -7,6 +7,9 @@ import listItems from '../../common/helpers/listItems';
* default skin, these are shown just to the right of the forum title.
*/
export default class HeaderPrimary extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}

View File

@@ -16,6 +16,9 @@ import Search from '../components/Search';
* right side of the header.
*/
export default class HeaderSecondary extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
return <ul className="Header-controls">{listItems(this.items().toArray())}</ul>;
}

View File

@@ -45,6 +45,9 @@ export default class IndexPage extends Page {
this.scrollTopOnCreate = false;
}
/**
* @return {import('mithril').Children}
*/
view() {
return (
<div className="IndexPage">

View File

@@ -1,3 +1,4 @@
import type Mithril from 'mithril';
import Component from '../../common/Component';
import avatar from '../../common/helpers/avatar';
@@ -6,7 +7,7 @@ import avatar from '../../common/helpers/avatar';
* indicating that the post is loading.
*/
export default class LoadingPost extends Component {
view() {
view(): Mithril.Children {
return (
<div className="Post CommentPost LoadingPost">
<header className="Post-header">

View File

@@ -1,3 +1,4 @@
import type Mithril from 'mithril';
import Component from '../../common/Component';
import ItemList from '../../common/utils/ItemList';
@@ -5,16 +6,14 @@ import ItemList from '../../common/utils/ItemList';
* The `LogInButtons` component displays a collection of social login buttons.
*/
export default class LogInButtons extends Component {
view() {
view(): Mithril.Children {
return <div className="LogInButtons">{this.items().toArray()}</div>;
}
/**
* Build a list of LogInButton components.
*
* @return {ItemList<import('mithril').Children>}
*/
items() {
return new ItemList();
return new ItemList<Mithril.Children>();
}
}

View File

@@ -42,20 +42,20 @@ export default class LogInModal<CustomAttrs extends ILoginModalAttrs = ILoginMod
return 'LogInModal Modal--small';
}
title() {
title(): Mithril.Children {
return app.translator.trans('core.forum.log_in.title');
}
content() {
content(): Mithril.Children {
return [<div className="Modal-body">{this.body()}</div>, <div className="Modal-footer">{this.footer()}</div>];
}
body() {
body(): Mithril.Children {
return [<LogInButtons />, <div className="Form Form--centered">{this.fields().toArray()}</div>];
}
fields() {
const items = new ItemList();
const items = new ItemList<Mithril.Children>();
const identificationLabel = extractText(app.translator.trans('core.forum.log_in.username_or_email_placeholder'));
const passwordLabel = extractText(app.translator.trans('core.forum.log_in.password_placeholder'));
@@ -124,7 +124,7 @@ export default class LogInModal<CustomAttrs extends ILoginModalAttrs = ILoginMod
return items;
}
footer() {
footer(): Mithril.Children {
return [
<p className="LogInModal-forgotPassword">
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>

View File

@@ -18,7 +18,7 @@ export interface INotificationAttrs extends ComponentAttrs {
* Subclasses should implement the `icon`, `href`, and `content` methods.
*/
export default abstract class Notification<CustomAttrs extends INotificationAttrs = INotificationAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
view(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
const notification = this.attrs.notification;
const href = this.href();

View File

@@ -38,6 +38,9 @@ export default class NotificationGrid extends Component {
this.types = this.notificationTypes().toArray();
}
/**
* @return {import('mithril').Children}
*/
view() {
const preferences = this.attrs.user.preferences();

View File

@@ -12,6 +12,9 @@ import ItemList from '../../common/utils/ItemList';
* notifications, grouped by discussion.
*/
export default class NotificationList extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
const state = this.attrs.state;

View File

@@ -18,6 +18,9 @@ export default class NotificationsDropdown extends Dropdown {
super.initAttrs(attrs);
}
/**
* @return {import('mithril').Children}
*/
getButton() {
const newNotifications = this.getNewCount();
const vdom = super.getButton();
@@ -30,6 +33,9 @@ export default class NotificationsDropdown extends Dropdown {
return vdom;
}
/**
* @return {import('mithril').Children}
*/
getButtonContent() {
const unread = this.getUnreadCount();
@@ -40,6 +46,9 @@ export default class NotificationsDropdown extends Dropdown {
];
}
/**
* @return {import('mithril').Children}
*/
getMenu() {
return (
<div className={classList('Dropdown-menu', this.attrs.menuClassName)} onclick={this.menuClick.bind(this)}>

View File

@@ -17,6 +17,9 @@ export default class NotificationsPage extends Page {
this.bodyClass = 'App--notifications';
}
/**
* @return {import('mithril').Children}
*/
view() {
return (
<div className="NotificationsPage">

View File

@@ -45,7 +45,7 @@ export default abstract class Post<CustomAttrs extends IPostAttrs = IPostAttrs>
);
}
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
view(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
const attrs = this.elementAttrs();
attrs.className = this.classes(attrs.className as string | undefined).join(' ');

View File

@@ -16,6 +16,9 @@ export default class PostEdited extends Component {
super.oninit(vnode);
}
/**
* @return {import('mithril').Children}
*/
view() {
const post = this.attrs.post;
const editedUser = post.editedUser();

View File

@@ -13,6 +13,9 @@ import fullTime from '../../common/helpers/fullTime';
* - `post`
*/
export default class PostMeta extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
const post = this.attrs.post;
const time = post.createdAt();

View File

@@ -14,6 +14,9 @@ import highlight from '../../common/helpers/highlight';
* - `post`
*/
export default class PostPreview extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
const post = this.attrs.post;
const user = post.user();

View File

@@ -26,6 +26,9 @@ export default class PostStream extends Component {
this.scrollListener = new ScrollListener(this.onscroll.bind(this));
}
/**
* @return {import('mithril').Children}
*/
view() {
let lastTime;

View File

@@ -23,6 +23,9 @@ export default class PostStreamScrubber extends Component {
this.scrollListener = new ScrollListener(this.updateScrubberValues.bind(this, { fromScroll: true, forceHeightChange: true }));
}
/**
* @return {import('mithril').Children}
*/
view() {
const count = this.stream.count();

View File

@@ -15,6 +15,9 @@ import listItems from '../../common/helpers/listItems';
* - `post`
*/
export default class PostUser extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
const post = this.attrs.post;
const user = post.user();

View File

@@ -45,6 +45,9 @@ export default class PostsUserPage extends UserPage {
this.loadUser(m.route.param('username'));
}
/**
* @return {import('mithril').Children}
*/
content() {
if (this.posts.length === 0 && !this.loading) {
return (

View File

@@ -19,10 +19,16 @@ export default class RenameDiscussionModal extends Modal {
return 'RenameDiscussionModal Modal--small';
}
/**
* @return {import('mithril').Children}
*/
title() {
return app.translator.trans('core.forum.rename_discussion.title');
}
/**
* @return {import('mithril').Children}
*/
content() {
return (
<div className="Modal-body">

View File

@@ -15,6 +15,9 @@ import listItems from '../../common/helpers/listItems';
* - `discussion`
*/
export default class ReplyPlaceholder extends Component {
/**
* @return {import('mithril').Children}
*/
view() {
if (app.composer.composingReplyTo(this.attrs.discussion)) {
return (

View File

@@ -117,7 +117,7 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone
this.searchState = this.attrs.state;
}
view() {
view(): Mithril.Children {
const currentSearch = this.searchState.getInitialSearch();
// Initialize search sources in the view rather than the constructor so

View File

@@ -22,10 +22,16 @@ export default class SessionDropdown extends Dropdown {
attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label');
}
/**
* @return {import('mithril').Children}
*/
view(vnode) {
return super.view({ ...vnode, children: this.items().toArray() });
}
/**
* @return {import('mithril').Children}
*/
getButtonContent() {
const user = app.session.user;

Some files were not shown because too many files have changed in this diff Show More