1
0
mirror of https://github.com/flarum/core.git synced 2025-09-07 06:31:19 +02:00

Compare commits

...

40 Commits

Author SHA1 Message Date
David Wheatley
3db258390d style fixes 2022-01-04 13:20:37 +00:00
David Wheatley
8b3ddd856e styling fixes 2022-01-01 17:21:27 +01:00
David Wheatley
d8841bba61 fix: drag from inside to outside modal closes it (fixes #2715) 2022-01-01 16:56:06 +01:00
David Wheatley
58441b1d1f refactor: Modals rewrite to use native HTML Dialog element 2022-01-01 16:38:52 +01:00
Sami Mazouz
ed3ea05c1a fix: Until reply renaming permission of discussions broken in php 8 (#3243)
* test: `until reply` rename discussion ability
* fix: `Until reply` renaming of discussions broken in php 8
2021-12-31 20:19:26 +01:00
flarum-bot
9fd6e5d0a2 Bundled output for commit 2541cdec94
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-29 09:43:23 +00:00
David Wheatley
2541cdec94 fix: prevent unwarranted a11y warnings on custom Button subclasses (#3238)
* fix: prevent unwarranted a11y warnings on UploadImageButton

* chore: format

* refactor

* fix: remove attr
2021-12-29 10:37:58 +01:00
David Wheatley
8fac735be0 fix: error in funding composer.json block bricks frontend (#3239)
* fix: error in funding `composer.json` block bricks frontend

* simplify

* Apply fixes from StyleCI

[ci skip] [skip ci]

Co-authored-by: luceos <luceos@users.noreply.github.com>
2021-12-29 03:25:03 +01:00
flarum-bot
53c3d13047 Bundled output for commit 993500aae4
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-28 20:13:43 +00:00
David Wheatley
993500aae4 fix(a11y): fix a11y warning in alert, and other a11y fixes (#3237)
* fix(a11y): fix a11y warning in alert, and other a11y fixes

* chore: correct import

* chore: use `class`
2021-12-28 21:08:34 +01:00
flarum-bot
0e9c169e06 Bundled output for commit 94f9e7f9de
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-28 19:56:55 +00:00
David Wheatley
94f9e7f9de fix: don't fire deprecation warnings for Mithril-originating action (#3236)
* fix: don't fire deprecation warnings for Mithril-originating actions

* Add comment
2021-12-28 20:51:13 +01:00
flarum-bot
4b25b90b48 Bundled output for commit a4fbf16eef
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-28 19:40:54 +00:00
David Wheatley
a4fbf16eef fix: mark render method in RouteResolver as optional (#3235) 2021-12-28 20:36:11 +01:00
David Wheatley
809620750f perf: include request info in first accesstoken touch (#3233) 2021-12-28 10:17:11 +01:00
flarum-bot
066cf02b88 Bundled output for commit cff672424b
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-28 01:30:26 +00:00
Alexander Skvortsov
cff672424b Merge pull request #3228 from flarum/as/v1.2_frontend_fixes
Some v1.2 frontend fixes
2021-12-27 20:24:53 -05:00
David Wheatley
2e94e31bb6 perf: only update last time when current value outdated (#3230)
* perf: only update last seen time when current > 120s ago

* perf: only update `last_activity_at` every 2 mins

* docs: add comment

* fix: add missing param

* test: add tests

* tests: attempt tests fix

* fix(tests): call `$this->app()`

* chore: extract hard-coded values out to private consts

* chore: increase diff

* Apply suggestions from code review
2021-12-28 00:39:42 +01:00
Alexander Skvortsov
b7f2fe2429 Fix consecutive shows of same modal with different attrs
We need to specify a unique key for each modal so that the modals are fully destroyed and recreated. For instance, this fixes the signup modal being empty with OAuth register flows.
2021-12-27 18:28:11 -05:00
flarum-bot
64dab138c4 Bundled output for commit 206aa227f2
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-27 23:18:49 +00:00
Alexander Skvortsov
a55b61e058 Use translations for page titles in frontend
This gives more flexibility for customization, and allows overriding title structure via translations / linguist.
2021-12-27 18:15:12 -05:00
Alexander Skvortsov
206aa227f2 Only retain scroll position if coming from discussion (#3229)
Fixes https://discuss.flarum.org/d/29596-make-tag-hero-visible/8.

If a user has just switched from one tag to another, they want to see the entire new discussion list, and information about which tag that discussion list corresponds to. There's no good reason to not display the hero header when switching tags.
2021-12-27 18:13:29 -05:00
flarum-bot
e926758060 Bundled output for commit ad8ac4e342
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-27 20:37:36 +00:00
David Wheatley
ad8ac4e342 fix: posts tab on users page broken 2021-12-27 21:32:04 +01:00
flarum-bot
2e5cd6f5c3 Bundled output for commit d5cd0bd339
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-27 19:59:19 +00:00
David Wheatley
d5cd0bd339 fix: returning null breaking CommentPosts 2021-12-27 20:54:00 +01:00
flarum-bot
09d1e289de Bundled output for commit d65deeccb5
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-27 19:09:19 +00:00
David Wheatley
d65deeccb5 chore: maintenance pre-1.2 release (#3213)
* chore: bump js dependencies

* chore: bump Yarn to 3.1.1

* chore: re-patch TS binary

* chore: don't show diffs for yarn lockfile
2021-12-27 19:04:42 +00:00
flarum-bot
3d62a6af27 Bundled output for commit d268894e61
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-27 19:02:33 +00:00
David Wheatley
d268894e61 chore: 1.2 JS clean-up (#3214)
* fix: `extend.ts` TS error

* docs: fix incorrect JS docblocks

* chore: simplify some code

* chore: remove usages of prefixed JS function

* chore: consistent empty return types

* chore: format

* fix: typing errors

* chore: remove unneeded `@public` docblock modifiers

* Apply suggestions from code review

* Update js/src/forum/utils/slidable.js

Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com>
2021-12-27 18:58:18 +00:00
Alexander Skvortsov
d89031f057 Fix drawer focus trap making login form unclickable on mobile
Adding `clickOutsideDeactivates` seems to fix the issue, contrary to what the focus-trap documentation implies about it being unnecessary.
2021-12-26 22:45:58 -05:00
Alexander Skvortsov
0c95d28e94 Fix Search error when user can't search
If there are no search sources, HTML for the Search component won't be rendered, so trying to attach listeners to it will likely error.

In this PR, we don't attach such listeners/logic if there are no sources. We also stop asserting that sources is defined to help avoid other similar issues in the future.
2021-12-26 20:04:48 -05:00
flarum-bot
4df72e5ac6 Bundled output for commit a2f417e9c5
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-26 06:29:40 +00:00
David Wheatley
a2f417e9c5 fix: incorrect return type on pushPayload (#3226) 2021-12-26 01:25:16 -05:00
flarum-bot
dc661bf144 Bundled output for commit 7a27f494c6
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-22 18:58:42 +00:00
David Wheatley
7a27f494c6 fix: hide WelcomeHero when content is empty (#3219) 2021-12-22 13:54:21 -05:00
Sebastian Kessler
edde6be301 docs: fix broken contribution link in README; add screenshot (#3211) 2021-12-20 14:55:25 -05:00
flarum-bot
96fdaac3ef Bundled output for commit e57655553f
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2021-12-20 16:32:17 +00:00
David Wheatley
e57655553f fix: breaking change in Search component - renaming of state property (#3212)
* fix: breaking change in search component's public api

* fix: add setter

* feat: add deprecation warning helper

This reduces bundle size as a result of deprecation warning in our JS, as well as maintaining a consistent format across warnings.

* feat: fire deprecation warning on usage of `Search.state`

* chore: use consistent deprecation warning across core

* fix: `/pull` not `/issue`

* chore: format
2021-12-20 16:28:28 +00:00
David Wheatley
a1cc456f3a fix(postmeta): use app baseUrl instead of location.origin (#3216) 2021-12-20 16:25:04 +00:00
160 changed files with 1818 additions and 1379 deletions

1
.gitattributes vendored
View File

@@ -13,5 +13,6 @@ tests export-ignore
js/dist/* -diff
js/dist/* linguist-generated
js/dist-typings/* linguist-generated
js/yarn.lock -diff
* text=auto eol=lf

View File

@@ -9,7 +9,6 @@
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
</p>
## About Flarum
**[Flarum](https://flarum.org/) is a delightfully simple discussion platform for your website.** It's fast and easy to use, with all the features you need to run a successful community. It is designed to be:
@@ -20,13 +19,15 @@
* **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarums architecture is amazingly flexible, with a powerful Extension API.
![Screenshot of a Flarum instance, showing multiple discussions and tags.](https://flarum.org/assets/img/home-screenshot.png)
## Installation
This repository contains Flarum's core code. If you want to set up a forum, visit the [Flarum skeleton repository](https://github.com/flarum/flarum).
This repository contains Flarum's core code. If you want to set up a forum, visit the [Flarum skeleton repository](https://github.com/flarum/flarum). For support, refer to the [documentation](https://docs.flarum.org/), and ask questions on [Flarum Discuss](https://discuss.flarum.org/) (our community forum) or [Discord server](https://flarum.org/discord/).
## Contributing
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://flarum.org/docs/contributing.html)** to learn how you can help.
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://docs.flarum.org/contributing)** to learn how you can help.
## Security Vulnerabilities

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,3 @@
yarnPath: .yarn/releases/yarn-3.1.0.cjs
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.1.1.cjs

View File

@@ -4,8 +4,8 @@ declare var _default: {
Store: typeof import("../common/Store").default;
'utils/BasicEditorDriver': typeof import("../common/utils/BasicEditorDriver").default;
'utils/evented': {
handlers: Object;
getHandlers(event: string): any[];
handlers: Record<string, unknown>;
getHandlers(event: string): Function[];
trigger(event: string, ...args: any[]): void;
on(event: string, handler: Function): void;
one(event: string, handler: Function): void;

View File

@@ -5,9 +5,9 @@ export default class AdminNav extends Component<import("../../common/Component")
/**
* Build an item list of main links to show in the admin navigation.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
extensionItems(): ItemList<any>;
}
import Component from "../../common/Component";

View File

@@ -7,10 +7,12 @@ export default class BasicsPage extends AdminPage<import("../../common/component
* Build a list of options for the default homepage. Each option must be an
* object with `path` and `label` properties.
*
* @return {ItemList}
* @public
* @return {ItemList<{ path: string, label: import('mithril').Children }>}
*/
public homePageItems(): ItemList<any>;
homePageItems(): ItemList<{
path: string;
label: import('mithril').Children;
}>;
}
import AdminPage from "./AdminPage";
import ItemList from "../../common/utils/ItemList";

View File

@@ -3,14 +3,14 @@ export default class DashboardWidget extends Component<import("../../common/Comp
/**
* Get the class name to apply to the widget.
*
* @return {String}
* @return {string}
*/
className(): string;
/**
* Get the content of the widget.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
*/
content(): any;
content(): import('mithril').Children;
}
import Component from "../../common/Component";

View File

@@ -8,9 +8,9 @@ export default class HeaderPrimary extends Component<import("../../common/Compon
/**
* Build an item list for the controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -6,9 +6,9 @@ export default class HeaderSecondary extends Component<import("../../common/Comp
/**
* Build an item list for the controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -13,16 +13,16 @@ export default class UploadImageButton extends Button<import("../../common/compo
/**
* After a successful upload/removal, reload the page.
*
* @param {Object} response
* @param {object} response
* @protected
*/
protected success(response: Object): void;
protected success(response: object): void;
/**
* If upload/removal fails, stop loading.
*
* @param {Object} response
* @param {object} response
* @protected
*/
protected failure(response: Object): void;
protected failure(response: object): void;
}
import Button from "../../common/components/Button";

View File

@@ -73,6 +73,8 @@ export interface RouteResolver<Attrs extends ComponentAttrs, Comp extends Compon
*
* Returns the component class, and **not** a Vnode or JSX
* expression.
*
* @see https://mithril.js.org/route.html#routeresolveronmatch
*/
onmatch(this: this, args: RouteArgs, requestedPath: string, route: string): {
new (): Comp;
@@ -80,9 +82,14 @@ export interface RouteResolver<Attrs extends ComponentAttrs, Comp extends Compon
/**
* A function which renders the provided component.
*
* If not specified, the route will default to rendering the
* component on its own, inside of a fragment.
*
* Returns a Mithril Vnode or other children.
*
* @see https://mithril.js.org/route.html#routeresolverrender
*/
render(this: this, vnode: Mithril.Vnode<Attrs, Comp>): Mithril.Children;
render?(this: this, vnode: Mithril.Vnode<Attrs, Comp>): Mithril.Children;
}
/**
* The `App` class provides a container for an application, as well as various
@@ -215,9 +222,6 @@ export default class Application {
* Make an AJAX request, handling any low-level errors that may occur.
*
* @see https://mithril.js.org/request.html
*
* @param options
* @return {Promise}
*/
request<ResponseType>(originalOptions: FlarumRequestOptions<ResponseType>): Promise<ResponseType>;
/**

View File

@@ -28,8 +28,8 @@ export default abstract class Fragment {
* containing all of the `li` elements inside the DOM element of this
* fragment.
*
* @param {String} [selector] a jQuery-compatible selector string
* @returns {jQuery} the jQuery object for the DOM node
* @param [selector] a jQuery-compatible selector string
* @returns the jQuery object for the DOM node
* @final
*/
$(selector?: string): JQuery;

View File

@@ -78,7 +78,7 @@ export default class Store {
* within the 'data' key of the payload.
*/
pushPayload<M extends Model>(payload: ApiPayloadSingle): ApiResponseSingle<M>;
pushPayload<Ms extends Model[]>(payload: ApiPayloadPlural): ApiResponseSingle<Ms[number]>;
pushPayload<Ms extends Model[]>(payload: ApiPayloadPlural): ApiResponsePlural<Ms[number]>;
/**
* Create a model to represent a resource object (or update an existing one),
* and push it into the store.

View File

@@ -4,8 +4,8 @@ declare var _default: {
Store: typeof Store;
'utils/BasicEditorDriver': typeof BasicEditorDriver;
'utils/evented': {
handlers: Object;
getHandlers(event: string): any[];
handlers: Record<string, unknown>;
getHandlers(event: string): Function[];
trigger(event: string, ...args: any[]): void;
on(event: string, handler: Function): void;
one(event: string, handler: Function): void;

View File

@@ -15,14 +15,14 @@ export default class Checkbox extends Component<import("../Component").Component
/**
* Get the template for the checkbox's display (tick/cross icon).
*
* @return {*}
* @return {import('mithril').Children}
* @protected
*/
protected getDisplay(): any;
protected getDisplay(): import('mithril').Children;
/**
* Run a callback when the state of the checkbox is changed.
*
* @param {Boolean} checked
* @param {boolean} checked
* @protected
*/
protected onchange(checked: boolean): void;

View File

@@ -22,17 +22,17 @@ export default class Dropdown extends Component<import("../Component").Component
/**
* Get the template for the button.
*
* @return {*}
* @return {import('mithril').Children}
* @protected
*/
protected getButton(children: any): any;
protected getButton(children: any): import('mithril').Children;
/**
* Get the template for the button's content.
*
* @return {*}
* @return {import('mithril').Children}
* @protected
*/
protected getButtonContent(children: any): any;
protected getButtonContent(children: any): import('mithril').Children;
getMenu(items: any): JSX.Element;
}
import Component from "../Component";

View File

@@ -16,10 +16,10 @@ export default class LinkButton extends Button<import("./Button").IButtonAttrs>
/**
* Determine whether a component with the given attrs is 'active'.
*
* @param {Object} attrs
* @return {Boolean}
* @param {object} attrs
* @return {boolean}
*/
static isActive(attrs: Object): boolean;
static isActive(attrs: object): boolean;
constructor();
}
import Button from "./Button";

View File

@@ -18,23 +18,23 @@ export default class Navigation extends Component<import("../Component").Compone
/**
* Get the back button.
*
* @return {Object}
* @return {import('mithril').Children}
* @protected
*/
protected getBackButton(): Object;
protected getBackButton(): import('mithril').Children;
/**
* Get the pane pinned toggle button.
*
* @return {Object|String}
* @return {import('mithril').Children}
* @protected
*/
protected getPaneButton(): Object | string;
protected getPaneButton(): import('mithril').Children;
/**
* Get the drawer toggle button.
*
* @return {Object|String}
* @return {import('mithril').Children}
* @protected
*/
protected getDrawerButton(): Object | string;
protected getDrawerButton(): import('mithril').Children;
}
import Component from "../Component";

View File

@@ -7,9 +7,10 @@ export default class SplitDropdown extends Dropdown {
* Get the first child. If the first child is an array, the first item in that
* array will be returned.
*
* @return {*}
* @param {unknown[] | unknown} children
* @return {unknown}
* @protected
*/
protected getFirstChild(children: any): any;
protected getFirstChild(children: unknown[] | unknown): unknown;
}
import Dropdown from "./Dropdown";

View File

@@ -36,19 +36,19 @@ export default class TextEditor extends Component<import("../Component").Compone
/**
* Build an item list for the text editor controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
controlItems(): ItemList<any>;
controlItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the toolbar controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
toolbarItems(): ItemList<any>;
toolbarItems(): ItemList<import('mithril').Children>;
/**
* Handle input into the textarea.
*
* @param {String} value
* @param {string} value
*/
oninput(value: string): void;
/**

View File

@@ -23,7 +23,7 @@
* @param methods The name or names of the method(s) to extend
* @param callback A callback which mutates the method's output
*/
export declare function extend<T extends object, K extends KeyOfType<T, Function>>(object: T, methods: K | K[], callback: (this: T, val: ReturnType<T[K]>, ...args: Parameters<T[K]>) => void): void;
export declare function extend<T extends Record<string, any>, K extends KeyOfType<T, Function>>(object: T, methods: K | K[], callback: (this: T, val: ReturnType<T[K]>, ...args: Parameters<T[K]>) => void): void;
/**
* Override an object's method by replacing it with a new function, so that the
* new function will be run every time the object's method is called.
@@ -51,4 +51,4 @@ export declare function extend<T extends object, K extends KeyOfType<T, Function
* @param methods The name or names of the method(s) to override
* @param newMethod The method to replace it with
*/
export declare function override<T extends object, K extends KeyOfType<T, Function>>(object: T, methods: K | K[], newMethod: (this: T, orig: T[K], ...args: Parameters<T[K]>) => void): void;
export declare function override<T extends Record<any, any>, K extends KeyOfType<T, Function>>(object: T, methods: K | K[], newMethod: (this: T, orig: T[K], ...args: Parameters<T[K]>) => void): void;

View File

@@ -10,3 +10,17 @@
* can fix.
*/
export default function fireDebugWarning(...args: Parameters<typeof console.warn>): void;
/**
* Fire a Flarum deprecation warning which is shown in the JS console.
*
* These warnings are only shown when the forum is in debug mode, and the function exists to
* reduce bundle size caused by multiple warnings across our JavaScript.
*
* @param message The message to display. (Short, but sweet, please!)
* @param githubId The PR or Issue ID with more info in relation to this change.
* @param [removedFrom] The version in which this feature will be completely removed. (default: 2.0)
* @param [repo] The repo which the issue or PR is located in. (default: flarum/core)
*
* @see {@link fireDebugWarning}
*/
export declare function fireDeprecationWarning(message: string, githubId: string, removedFrom?: string, repo?: string): void;

View File

@@ -6,7 +6,7 @@
* punctuateSeries(['Toby', 'Franz', 'Dominion']) // Toby, Franz, and Dominion
* ```
*
* @param {Array} items
* @return {VirtualElement}
* @param {import('mithril').Children[]} items
* @return {import('mithril').Children}')}
*/
export default function punctuateSeries(items: any[]): any;
export default function punctuateSeries(items: import('mithril').Children[]): import('mithril').Children;

View File

@@ -20,7 +20,7 @@ export default class AlertManagerState {
/**
* Show an Alert in the alerts area.
*
* @returns The alert's ID, which can be used to dismiss the alert.
* @return The alert's ID, which can be used to dismiss the alert.
*/
show(children: Mithril.Children): AlertIdentifier;
show(attrs: AlertAttrs, children: Mithril.Children): AlertIdentifier;

View File

@@ -23,7 +23,13 @@ export default class ModalManagerState {
modal: null | {
componentClass: UnsafeModalClass;
attrs?: Record<string, unknown>;
key: number;
};
/**
* Used to force re-initialization of modals if a modal
* is replaced by another of the same type.
*/
private key;
private closeTimeout?;
/**
* Shows a modal dialog.
@@ -45,7 +51,7 @@ export default class ModalManagerState {
/**
* Checks if a modal is currently open.
*
* @returns `true` if a modal dialog is currently open, otherwise `false`.
* @return `true` if a modal dialog is currently open, otherwise `false`.
*/
isModalOpen(): boolean;
}

View File

@@ -5,12 +5,11 @@ export default class PageState {
/**
* Determine whether the page matches the given class and data.
*
* @param {object} type The page class to check against. Subclasses are
* accepted as well.
* @param {object} data
* @param {object} type The page class to check against. Subclasses are accepted as well.
* @param {Record<string, unknown>} data
* @return {boolean}
*/
matches(type: object, data?: object): boolean;
matches(type: object, data?: Record<string, unknown>): boolean;
get(key: any): any;
set(key: any, value: any): void;
}

View File

@@ -34,20 +34,15 @@ export default class Drawer {
* Check whether or not the drawer is currently open.
*
* @return {boolean}
* @public
*/
public isOpen(): boolean;
isOpen(): boolean;
/**
* Hide the drawer.
*
* @public
*/
public hide(): void;
hide(): void;
/**
* Show the drawer.
*
* @public
*/
public show(): void;
show(): void;
$backdrop: JQuery<HTMLElement> | undefined;
}

View File

@@ -192,7 +192,7 @@ export default class ItemList<T> {
*
* @param content The item's content (objects only)
* @param key The item's key
* @returns Proxied content
* @return Proxied content
*
* @internal
*/

View File

@@ -6,7 +6,6 @@ export default class ScrollListener {
/**
* @param {(top: number) => void} callback The callback to run when the scroll position
* changes.
* @public
*/
constructor(callback: (top: number) => void);
callback: (top: number) => void;
@@ -20,21 +19,15 @@ export default class ScrollListener {
protected loop(): void;
/**
* Run the callback, whether there was a scroll event or not.
*
* @public
*/
public update(): void;
update(): void;
/**
* Start listening to and handling the window's scroll position.
*
* @public
*/
public start(): void;
start(): void;
active: (() => void) | null | undefined;
/**
* Stop listening to and handling the window's scroll position.
*
* @public
*/
public stop(): void;
stop(): void;
}

View File

@@ -8,7 +8,7 @@
* position can be anchor to an element that is in or below the viewport, so
* the content in the viewport will stay the same.
*
* @param {DOMElement} element The element to anchor the scroll position to.
* @param {Function} callback The callback to run that will change page content.
* @param {HTMLElement | SVGElement | Element} element The element to anchor the scroll position to.
* @param {() => void} callback The callback to run that will change page content.
*/
export default function anchorScroll(element: any, callback: Function): void;
export default function anchorScroll(element: HTMLElement | SVGElement | Element, callback: () => void): void;

View File

@@ -3,9 +3,8 @@ import Model from '../Model';
* The `computed` utility creates a function that will cache its output until
* any of the dependent values are dirty.
*
* @param {...String} dependentKeys The keys of the dependent values.
* @param {function} compute The function which computes the value using the
* @param dependentKeys The keys of the dependent values.
* @param compute The function which computes the value using the
* dependent values.
* @return {Function}
*/
export default function computed<T, M = Model>(...args: [...string[], (this: M, ...args: unknown[]) => T]): () => T;

View File

@@ -1,79 +1,97 @@
declare namespace _default {
const handlers: Object;
const handlers: Record<string, unknown>;
/**
* Get all of the registered handlers for an event.
*
* @param {String} event The name of the event.
* @return {Array}
* @param {string} event The name of the event.
* @return {Function[]}
* @protected
*
* @deprecated
*/
function getHandlers(event: string): any[];
function getHandlers(event: string): Function[];
/**
* Get all of the registered handlers for an event.
*
* @param {String} event The name of the event.
* @return {Array}
* @param {string} event The name of the event.
* @return {Function[]}
* @protected
*
* @deprecated
*/
function getHandlers(event: string): any[];
function getHandlers(event: string): Function[];
/**
* Trigger an event.
*
* @param {String} event The name of the event.
* @param {...*} args Arguments to pass to event handlers.
* @public
* @param {string} event The name of the event.
* @param {any[]} args Arguments to pass to event handlers.
*
* @deprecated
*/
function trigger(event: string, ...args: any[]): void;
/**
* Trigger an event.
*
* @param {String} event The name of the event.
* @param {...*} args Arguments to pass to event handlers.
* @public
* @param {string} event The name of the event.
* @param {any[]} args Arguments to pass to event handlers.
*
* @deprecated
*/
function trigger(event: string, ...args: any[]): void;
/**
* Register an event handler.
*
* @param {String} event The name of the event.
* @param {function} handler The function to handle the event.
* @param {string} event The name of the event.
* @param {Function} handler The function to handle the event.
*
* @deprecated
*/
function on(event: string, handler: Function): void;
/**
* Register an event handler.
*
* @param {String} event The name of the event.
* @param {function} handler The function to handle the event.
* @param {string} event The name of the event.
* @param {Function} handler The function to handle the event.
*
* @deprecated
*/
function on(event: string, handler: Function): void;
/**
* Register an event handler so that it will run only once, and then
* unregister itself.
*
* @param {String} event The name of the event.
* @param {function} handler The function to handle the event.
* @param {string} event The name of the event.
* @param {Function} handler The function to handle the event.
*
* @deprecated
*/
function one(event: string, handler: Function): void;
/**
* Register an event handler so that it will run only once, and then
* unregister itself.
*
* @param {String} event The name of the event.
* @param {function} handler The function to handle the event.
* @param {string} event The name of the event.
* @param {Function} handler The function to handle the event.
*
* @deprecated
*/
function one(event: string, handler: Function): void;
/**
* Unregister an event handler.
*
* @param {String} event The name of the event.
* @param {function} handler The function that handles the event.
* @param {string} event The name of the event.
* @param {Function} handler The function that handles the event.
*
* @deprecated
*/
function off(event: string, handler: Function): void;
/**
* Unregister an event handler.
*
* @param {String} event The name of the event.
* @param {function} handler The function that handles the event.
* @param {string} event The name of the event.
* @param {Function} handler The function that handles the event.
*
* @deprecated
*/
function off(event: string, handler: Function): void;
}

View File

@@ -5,8 +5,8 @@
* @example
* class MyClass extends mixin(ExistingClass, evented, etc) {}
*
* @param {Class} Parent The class to extend the new class from.
* @param {...Object} mixins The objects to mix in.
* @return {Class} A new class that extends Parent and contains the mixins.
* @param {object} Parent The class to extend the new class from.
* @param {Record<string, any>[]} mixins The objects to mix in.
* @return {object} A new class that extends Parent and contains the mixins.
*/
export default function mixin(Parent: any, ...mixins: Object[]): any;
export default function mixin(Parent: object, ...mixins: Record<string, any>[]): object;

View File

@@ -4,8 +4,8 @@ declare var _default: {
Store: typeof import("../common/Store").default;
'utils/BasicEditorDriver': typeof BasicEditorDriver;
'utils/evented': {
handlers: Object;
getHandlers(event: string): any[];
handlers: Record<string, unknown>;
getHandlers(event: string): Function[];
trigger(event: string, ...args: any[]): void;
on(event: string, handler: Function): void;
one(event: string, handler: Function): void;
@@ -92,38 +92,38 @@ declare var _default: {
'states/PaginatedListState': typeof import("../common/states/PaginatedListState").default;
} & {
'utils/PostControls': {
controls(post: any, context: any): import("../common/utils/ItemList").default<any>;
userControls(post: any, context: any): import("../common/utils/ItemList").default<any>;
moderationControls(post: any, context: any): import("../common/utils/ItemList").default<any>;
destructiveControls(post: any, context: any): import("../common/utils/ItemList").default<any>;
editAction(): Promise<any>;
hideAction(): Promise<any>;
restoreAction(): Promise<any>;
deleteAction(context: any): Promise<any>;
controls(post: import("../common/models/Post").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
userControls(post: import("../common/models/Post").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
moderationControls(post: import("../common/models/Post").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
destructiveControls(post: import("../common/models/Post").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
editAction(): Promise<void>;
hideAction(): Promise<void>;
restoreAction(): Promise<void>;
deleteAction(context: any): Promise<void>;
};
'utils/KeyboardNavigatable': typeof KeyboardNavigatable;
'utils/slidable': typeof slidable;
'utils/History': typeof History;
'utils/DiscussionControls': {
controls(discussion: any, context: any): import("../common/utils/ItemList").default<any>;
userControls(discussion: any, context: any): import("../common/utils/ItemList").default<any>;
moderationControls(discussion: any): import("../common/utils/ItemList").default<any>;
destructiveControls(discussion: any): import("../common/utils/ItemList").default<any>;
replyAction(goToLast: boolean, forceRefresh: boolean): Promise<any>;
hideAction(): Promise<any>;
restoreAction(): Promise<any>;
deleteAction(): Promise<any>;
renameAction(): Promise<any>;
controls(discussion: import("../common/models/Discussion").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
userControls(discussion: import("../common/models/Discussion").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
moderationControls(discussion: import("../common/models/Discussion").default): import("../common/utils/ItemList").default<import("mithril").Children>;
destructiveControls(discussion: import("../common/models/Discussion").default): import("../common/utils/ItemList").default<import("mithril").Children>;
replyAction(goToLast: boolean, forceRefresh: boolean): Promise<void>;
hideAction(): Promise<void>;
restoreAction(): Promise<void>;
deleteAction(): Promise<void>;
renameAction(): any;
};
'utils/alertEmailConfirmation': typeof alertEmailConfirmation;
'utils/UserControls': {
controls(user: any, context: any): import("../common/utils/ItemList").default<any>;
userControls(): import("../common/utils/ItemList").default<any>;
moderationControls(user: any): import("../common/utils/ItemList").default<any>;
destructiveControls(user: any): import("../common/utils/ItemList").default<any>;
deleteAction(user: any): void;
showDeletionAlert(user: any, type: string): void;
editAction(user: any): void;
controls(user: import("../common/models/User").default, context: import("../common/Component").default<any, any>): import("../common/utils/ItemList").default<import("mithril").Children>;
userControls(): import("../common/utils/ItemList").default<import("mithril").Children>;
moderationControls(user: import("../common/models/User").default): import("../common/utils/ItemList").default<import("mithril").Children>;
destructiveControls(user: import("../common/models/User").default): import("../common/utils/ItemList").default<import("mithril").Children>;
deleteAction(user: import("../common/models/User").default): void;
showDeletionAlert(user: import("../common/models/User").default, type: string): void;
editAction(user: import("../common/models/User").default): void;
};
'utils/Pane': typeof Pane;
'utils/BasicEditorDriver': typeof BasicEditorDriver;

View File

@@ -24,36 +24,36 @@ export default class AvatarEditor extends Component<import("../../common/Compone
/**
* Get the items in the edit avatar dropdown menu.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
controlItems(): ItemList<any>;
controlItems(): ItemList<import('mithril').Children>;
/**
* Enable dragover style
*
* @param {Event} e
* @param {DragEvent} e
*/
enableDragover(e: Event): void;
enableDragover(e: DragEvent): void;
/**
* Disable dragover style
*
* @param {Event} e
* @param {DragEvent} e
*/
disableDragover(e: Event): void;
disableDragover(e: DragEvent): void;
/**
* Upload avatar when file is dropped into dropzone.
*
* @param {Event} e
* @param {DragEvent} e
*/
dropUpload(e: Event): void;
dropUpload(e: DragEvent): void;
/**
* If the user doesn't have an avatar, there's no point in showing the
* controls dropdown, because only one option would be viable: uploading.
* Thus, when the avatar editor's dropdown toggle button is clicked, we prompt
* the user to upload an avatar immediately.
*
* @param {Event} e
* @param {MouseEvent} e
*/
quickUpload(e: Event): void;
quickUpload(e: MouseEvent): void;
/**
* Upload avatar using file picker
*/
@@ -72,17 +72,17 @@ export default class AvatarEditor extends Component<import("../../common/Compone
* After a successful upload/removal, push the updated user data into the
* store, and force a recomputation of the user's avatar color.
*
* @param {Object} response
* @param {object} response
* @protected
*/
protected success(response: Object): void;
protected success(response: object): void;
/**
* If avatar upload/removal fails, stop loading.
*
* @param {Object} response
* @param {object} response
* @protected
*/
protected failure(response: Object): void;
protected failure(response: object): void;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -32,9 +32,9 @@ export default class CommentPost extends Post {
/**
* Build an item list for the post's header.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
headerItems(): ItemList<any>;
headerItems(): ItemList<import('mithril').Children>;
}
import Post from "./Post";
import ItemList from "../../common/utils/ItemList";

View File

@@ -21,9 +21,9 @@ export default class Composer extends Component<import("../../common/Component")
/**
* Resize the composer according to mouse movement.
*
* @param {Event} e
* @param {MouseEvent} e
*/
onmousemove(e: Event): void;
onmousemove(e: MouseEvent): void;
/**
* Finish resizing the composer when the mouse is released.
*/
@@ -83,23 +83,23 @@ export default class Composer extends Component<import("../../common/Component")
/**
* Build an item list for the composer's controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
controlItems(): ItemList<any>;
controlItems(): ItemList<import('mithril').Children>;
/**
* Initialize default Composer height.
*/
initializeHeight(): void;
/**
* Default height of the Composer in case none is saved.
* @returns {Integer}
* @returns {number}
*/
defaultHeight(): any;
defaultHeight(): number;
/**
* Save a new Composer height and update the DOM.
* @param {Integer} height
* @param {number} height
*/
changeHeight(height: any): void;
changeHeight(height: number): void;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -27,15 +27,15 @@ export default class ComposerBody extends Component<import("../../common/Compone
/**
* Check if there is any unsaved data.
*
* @return {String}
* @return {boolean}
*/
hasChanges(): string;
hasChanges(): boolean;
/**
* Build an item list for the composer's header.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
headerItems(): ItemList<any>;
headerItems(): ItemList<import('mithril').Children>;
/**
* Handle the submit event of the text editor.
*

View File

@@ -21,14 +21,14 @@ export default class DiscussionComposer extends ComposerBody {
* Handle the title input's keydown event. When the return key is pressed,
* move the focus to the start of the text editor.
*
* @param {Event} e
* @param {KeyboardEvent} e
*/
onkeydown(e: Event): void;
onkeydown(e: KeyboardEvent): void;
/**
* Get the data to submit to the server when the discussion is saved.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
data(): Object;
data(): Record<string, unknown>;
}
import ComposerBody from "./ComposerBody";

View File

@@ -10,9 +10,9 @@ export default class DiscussionHero extends Component<import("../../common/Compo
/**
* Build an item list for the contents of the discussion hero.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -23,7 +23,7 @@ export default class DiscussionListItem extends Component<import("../../common/C
/**
* Determine whether or not the discussion is currently being viewed.
*
* @return {Boolean}
* @return {boolean}
*/
active(): boolean;
/**
@@ -31,14 +31,14 @@ export default class DiscussionListItem extends Component<import("../../common/C
* should be displayed instead of information about the most recent reply to
* the discussion.
*
* @return {Boolean}
* @return {boolean}
*/
showFirstPost(): boolean;
/**
* Determine whether or not the number of replies should be shown instead of
* the number of unread posts.
*
* @return {Boolean}
* @return {boolean}
*/
showRepliesCount(): boolean;
/**
@@ -49,9 +49,9 @@ export default class DiscussionListItem extends Component<import("../../common/C
* Build an item list of info for a discussion listing. By default this is
* just the first/last post indicator.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
infoItems(): ItemList<any>;
infoItems(): ItemList<import('mithril').Children>;
replyCountItem(): JSX.Element;
}
import Component from "../../common/Component";

View File

@@ -31,34 +31,24 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
view(): JSX.Element;
/**
* List of components shown while the discussion is loading.
*
* @returns {ItemList}
*/
loadingItems(): ItemList<unknown>;
loadingItems(): ItemList<Mithril.Children>;
/**
* Function that renders the `sidebarItems` ItemList.
*
* @returns {import('mithril').Children}
*/
sidebar(): JSX.Element;
sidebar(): Mithril.Children;
/**
* Renders the discussion's hero.
*
* @returns {import('mithril').Children}
*/
hero(): JSX.Element;
hero(): Mithril.Children;
/**
* List of items rendered as the main page content.
*
* @returns {ItemList}
*/
pageContent(): ItemList<unknown>;
pageContent(): ItemList<Mithril.Children>;
/**
* List of items rendered inside the main page content container.
*
* @returns {ItemList}
*/
mainContent(): ItemList<unknown>;
mainContent(): ItemList<Mithril.Children>;
/**
* Load the discussion from the API or use the preloaded one.
*/
@@ -66,15 +56,8 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* Get the parameters that should be passed in the API request to get the
* discussion.
*
* @return {Object}
*/
requestParams(): {
bySlug: boolean;
page: {
near: number;
};
};
requestParams(): Record<string, unknown>;
/**
* Initialize the component to display the given discussion.
*/
@@ -82,7 +65,7 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* Build an item list for the contents of the sidebar.
*/
sidebarItems(): ItemList<Mithril.Vnode<{}, {}>>;
sidebarItems(): ItemList<Mithril.Children>;
/**
* When the posts that are visible in the post stream change (i.e. the user
* scrolls up or down), then we update the URL and mark the posts as read.

View File

@@ -17,8 +17,8 @@ export default class EditPostComposer extends ComposerBody {
/**
* Get the data to submit to the server when the post is saved.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
data(): Object;
data(): Record<string, unknown>;
}
import ComposerBody from "./ComposerBody";

View File

@@ -13,27 +13,27 @@ export default class EventPost extends Post {
/**
* Get the name of the event icon.
*
* @return {String}
* @return {string}
*/
icon(): string;
/**
* Get the description text for the event.
*
* @param {Object} data
* @return {String|Object} The description to render in the DOM
* @param {Record<string, unknown>} data
* @return {import('mithril').Children} The description to render in the DOM
*/
description(data: Object): string | Object;
description(data: Record<string, unknown>): import('mithril').Children;
/**
* Get the translation key for the description of the event.
*
* @return {String}
* @return {string}
*/
descriptionKey(): string;
/**
* Get the translation data for the description of the event.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
descriptionData(): Object;
descriptionData(): Record<string, unknown>;
}
import Post from "./Post";

View File

@@ -7,9 +7,9 @@ export default class HeaderPrimary extends Component<import("../../common/Compon
/**
* Build an item list for the controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -10,49 +10,47 @@ export default class IndexPage extends Page<import("../../common/components/Page
/**
* Get the component to display as the hero.
*
* @return {MithrilComponent}
* @return {import('mithril').Children}
*/
hero(): any;
hero(): import('mithril').Children;
/**
* Build an item list for the sidebar of the index page. By default this is a
* "New Discussion" button, and then a DropdownSelect component containing a
* list of navigation items.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
sidebarItems(): ItemList<any>;
sidebarItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the navigation in the sidebar of the index page. By
* default this is just the 'All Discussions' link.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
navItems(): ItemList<any>;
navItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the part of the toolbar which is concerned with how
* the results are displayed. By default this is just a select box to change
* the way discussions are sorted.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
viewItems(): ItemList<any>;
viewItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the part of the toolbar which is about taking action
* on the results. By default this is just a "mark all as read" button.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
actionItems(): ItemList<any>;
actionItems(): ItemList<import('mithril').Children>;
/**
* Open the composer for a new discussion or prompt the user to login.
*
* @return {Promise}
* @return {Promise<void>}
*/
newDiscussionAction(): Promise<any>;
newDiscussionAction(): Promise<void>;
/**
* Mark all discussions as read.
*
* @return void
*/
markAllAsRead(): void;
}

View File

@@ -6,10 +6,9 @@ export default class LogInButtons extends Component<import("../../common/Compone
/**
* Build a list of LogInButton components.
*
* @return {ItemList}
* @public
* @return {ItemList<import('mithril').Children>}
*/
public items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -13,31 +13,31 @@ export default class Notification extends Component<import("../../common/Compone
/**
* Get the name of the icon that should be displayed in the notification.
*
* @return {String}
* @return {string}
* @abstract
*/
icon(): string;
/**
* Get the URL that the notification should link to.
*
* @return {String}
* @return {string}
* @abstract
*/
href(): string;
/**
* Get the content of the notification.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
* @abstract
*/
content(): any;
content(): import('mithril').Children;
/**
* Get the excerpt of the notification.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
* @abstract
*/
excerpt(): any;
excerpt(): import('mithril').Children;
/**
* Mark the notification as read.
*/

View File

@@ -11,47 +11,55 @@ export default class NotificationGrid extends Component<import("../../common/Com
/**
* Information about the available notification methods.
*
* @type {Array}
* @type {({ name: string, icon: string, label: import('mithril').Children })[]}
*/
methods: any[] | undefined;
methods: {
name: string;
icon: string;
label: import('mithril').Children;
}[] | undefined;
/**
* A map of which notification checkboxes are loading.
*
* @type {Object}
* @type {Record<string, boolean>}
*/
loading: Object | undefined;
loading: Record<string, boolean> | undefined;
/**
* Information about the available notification types.
*
* @type {Array}
* @type {({ name: string, icon: string, label: import('mithril').Children })[]}
*/
types: any[] | undefined;
types: {
name: string;
icon: string;
label: import('mithril').Children;
}[] | undefined;
/**
* Toggle the state of the given preferences, based on the value of the first
* one.
*
* @param {Array} keys
* @param {string[]} keys
*/
toggle(keys: any[]): void;
toggle(keys: string[]): void;
/**
* Toggle all notification types for the given method.
*
* @param {String} method
* @param {string} method
*/
toggleMethod(method: string): void;
/**
* Toggle all notification methods for the given type.
*
* @param {String} type
* @param {string} type
*/
toggleType(type: string): void;
/**
* Get the name of the preference key for the given notification type-method
* combination.
*
* @param {String} type
* @param {String} method
* @return {String}
* @param {string} type
* @param {string} method
* @return {string}
*/
preferenceKey(type: string, method: string): string;
/**
@@ -63,9 +71,13 @@ export default class NotificationGrid extends Component<import("../../common/Com
* - `icon` The icon to display in the column header.
* - `label` The label to display in the column header.
*
* @return {ItemList}
* @return {ItemList<{ name: string, icon: string, label: import('mithril').Children }>}
*/
notificationMethods(): ItemList<any>;
notificationMethods(): ItemList<{
name: string;
icon: string;
label: import('mithril').Children;
}>;
/**
* Build an item list for the notification types to display in the grid.
*
@@ -75,9 +87,14 @@ export default class NotificationGrid extends Component<import("../../common/Com
* - `icon` The icon to display in the notification grid row.
* - `label` The label to display in the notification grid row.
*
* @return {ItemList}
* @return {ItemList<{ name: string, icon: string, label: import('mithril').Children}>}
*/
notificationTypes(): ItemList<any>;
notificationTypes(): ItemList<{
name: string;
icon: string;
label: import('mithril').Children;
}>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";
import icon from "../../common/helpers/icon";

View File

@@ -25,34 +25,34 @@ export default class Post extends Component<import("../../common/Component").Com
/**
* Get attributes for the post element.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
elementAttrs(): Object;
elementAttrs(): Record<string, unknown>;
/**
* Get the post's content.
*
* @return {Array}
* @return {import('mithril').Children}
*/
content(): any[];
content(): import('mithril').Children;
/**
* Get the post's classes.
*
* @param existing string
* @param {string} existing
* @returns {string[]}
*/
classes(existing: any): string[];
classes(existing: string): string[];
/**
* Build an item list for the post's actions.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
actionItems(): ItemList<any>;
actionItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the post's footer.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
footerItems(): ItemList<any>;
footerItems(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import SubtreeRetainer from "../../common/utils/SubtreeRetainer";

View File

@@ -12,9 +12,9 @@ export default class PostMeta extends Component<import("../../common/Component")
/**
* Get the permalink for the given post.
*
* @param {Post} post
* @returns {String}
* @param {import('../../common/models/Post').default} post
* @returns {string}
*/
getPermalink(post: any): string;
getPermalink(post: import('../../common/models/Post').default): string;
}
import Component from "../../common/Component";

View File

@@ -20,17 +20,17 @@ export default class PostStream extends Component<import("../../common/Component
triggerScroll(): void;
/**
*
* @param {Integer} top
* @param {number} top
*/
onscroll(top?: any): void;
onscroll(top?: number): void;
calculatePositionTimeout: NodeJS.Timeout | undefined;
/**
* Check if either extreme of the post stream is in the viewport,
* and if so, trigger loading the next/previous page.
*
* @param {Integer} top
* @param {number} top
*/
loadPostsIfNeeded(top?: any): void;
loadPostsIfNeeded(top?: number): void;
updateScrubber(top?: number): void;
/**
* Work out which posts (by number) are currently visible in the viewport, and
@@ -41,43 +41,43 @@ export default class PostStream extends Component<import("../../common/Component
* Get the distance from the top of the viewport to the point at which we
* would consider a post to be the first one visible.
*
* @return {Integer}
* @return {number}
*/
getMarginTop(): any;
getMarginTop(): number;
/**
* Scroll down to a certain post by number and 'flash' it.
*
* @param {Integer} number
* @param {Boolean} animate
* @return {jQuery.Deferred}
* @param {number} number
* @param {boolean} animate
* @return {JQueryDeferred}
*/
scrollToNumber(number: any, animate: boolean): any;
scrollToNumber(number: number, animate: boolean): JQueryDeferred<any>;
/**
* Scroll down to a certain post by index.
*
* @param {Integer} index
* @param {Boolean} animate
* @param {Boolean} reply Whether or not to scroll to the reply placeholder.
* @return {jQuery.Deferred}
* @param {number} index
* @param {boolean} animate
* @param {boolean} reply Whether or not to scroll to the reply placeholder.
* @return {JQueryDeferred}
*/
scrollToIndex(index: any, animate: boolean, reply: boolean): any;
scrollToIndex(index: number, animate: boolean, reply: boolean): JQueryDeferred<any>;
/**
* Scroll down to the given post.
*
* @param {jQuery} $item
* @param {Boolean} animate
* @param {Boolean} force Whether or not to force scrolling to the item, even
* @param {JQuery} $item
* @param {boolean} animate
* @param {boolean} force Whether or not to force scrolling to the item, even
* if it is already in the viewport.
* @param {Boolean} reply Whether or not to scroll to the reply placeholder.
* @return {jQuery.Deferred}
* @param {boolean} reply Whether or not to scroll to the reply placeholder.
* @return {JQueryDeferred}
*/
scrollToItem($item: JQueryStatic, animate: boolean, force: boolean, reply: boolean): any;
scrollToItem($item: JQuery, animate: boolean, force: boolean, reply: boolean): JQueryDeferred<any>;
/**
* 'Flash' the given post, drawing the user's attention to it.
*
* @param {jQuery} $item
* @param {JQuery} $item
*/
flashItem($item: JQueryStatic): void;
flashItem($item: JQuery): void;
}
import Component from "../../common/Component";
import ScrollListener from "../../common/utils/ScrollListener";

View File

@@ -19,9 +19,13 @@ export default class PostStreamScrubber extends Component<import("../../common/C
* Update the scrollbar's position to reflect the current values of the
* index/visible properties.
*
* @param {Boolean} animate
* @param {Partial<{fromScroll: boolean, forceHeightChange: boolean, animate: boolean}>} options
*/
updateScrubberValues(options?: {}): void;
updateScrubberValues(options?: Partial<{
fromScroll: boolean;
forceHeightChange: boolean;
animate: boolean;
}>): void;
adjustingHeight: boolean | undefined;
/**
* Go to the first post in the discussion.
@@ -40,13 +44,16 @@ export default class PostStreamScrubber extends Component<import("../../common/C
* Get the percentage of the height of the scrubber that should be allocated
* to each post.
*
* @return {Object}
* @return {{ index: number, visible: number }}
* @property {Number} index The percent per post for posts on either side of
* the visible part of the scrubber.
* @property {Number} visible The percent per post for the visible part of the
* scrubber.
*/
percentPerPost(): Object;
percentPerPost(): {
index: number;
visible: number;
};
}
import Component from "../../common/Component";
import ScrollListener from "../../common/utils/ScrollListener";

View File

@@ -20,39 +20,35 @@ export default class PostsUserPage extends UserPage {
*
* @type {Post[]}
*/
posts: any[] | undefined;
posts: Post[] | undefined;
/**
* The number of activity items to load per request.
*
* @type {Integer}
* @type {number}
*/
loadLimit: any;
loadLimit: number | undefined;
/**
* Clear and reload the user's activity feed.
*
* @public
*/
public refresh(): void;
refresh(): void;
/**
* Load a new page of the user's activity feed.
*
* @param {Integer} [offset] The position to start getting results from.
* @return {Promise}
* @param {number} [offset] The position to start getting results from.
* @return {Promise<import('../../common/models/Post').default[]>}
* @protected
*/
protected loadResults(offset?: any): Promise<any>;
protected loadResults(offset?: number | undefined): Promise<import('../../common/models/Post').default[]>;
/**
* Load the next page of results.
*
* @public
*/
public loadMore(): void;
loadMore(): void;
/**
* Parse results and append them to the activity feed.
*
* @param {Post[]} results
* @return {Post[]}
* @param {import('../../common/models/Post').default[]} results
* @return {import('../../common/models/Post').default[]}
*/
parseResults(results: any[]): any[];
parseResults(results: import('../../common/models/Post').default[]): import('../../common/models/Post').default[];
}
import UserPage from "./UserPage";

View File

@@ -16,8 +16,8 @@ export default class ReplyComposer extends ComposerBody {
/**
* Get the data to submit to the server when the reply is saved.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
data(): Object;
data(): Record<string, unknown>;
}
import ComposerBody from "./ComposerBody";

View File

@@ -42,12 +42,22 @@ export interface SearchAttrs extends ComponentAttrs {
*
* - state: SearchState instance.
*/
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T> {
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T, SearchState> {
/**
* The minimum query length before sources are searched.
*/
protected static MIN_SEARCH_LEN: number;
/**
* The instance of `SearchState` for this component.
*/
protected searchState: SearchState;
/**
* The instance of `SearchState` for this component.
*
* @deprecated Replace with`this.searchState` instead.
*/
protected get state(): SearchState;
protected set state(state: SearchState);
/**
* Whether or not the search input has focus.
*/
@@ -55,7 +65,7 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone
/**
* An array of SearchSources.
*/
protected sources: SearchSource[];
protected sources?: SearchSource[];
/**
* The number of sources that are still loading results.
*/

View File

@@ -6,9 +6,9 @@ export default class SessionDropdown extends Dropdown {
/**
* Build an item list for the contents of the dropdown menu.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items(): ItemList<any>;
items(): ItemList<import('mithril').Children>;
}
import Dropdown from "../../common/components/Dropdown";
import ItemList from "../../common/utils/ItemList";

View File

@@ -6,27 +6,27 @@ export default class SettingsPage extends UserPage {
/**
* Build an item list for the user's settings controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
settingsItems(): ItemList<any>;
settingsItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the user's account settings.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
accountItems(): ItemList<any>;
accountItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the user's notification settings.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
notificationsItems(): ItemList<any>;
notificationsItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the user's privacy settings.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
privacyItems(): ItemList<any>;
privacyItems(): ItemList<import('mithril').Children>;
discloseOnlineLoading: boolean | undefined;
}
import UserPage from "./UserPage";

View File

@@ -42,8 +42,6 @@ export default class SignUpModal<CustomAttrs extends ISignupModalAttrs = ISignup
/**
* Open the log in modal, prefilling it with an email/username/password if
* the user has entered one.
*
* @public
*/
logIn(): void;
onready(): void;

View File

@@ -15,9 +15,9 @@ export default class UserCard extends Component<import("../../common/Component")
/**
* Build an item list of tidbits of info to show on this user's profile.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
infoItems(): ItemList<any>;
infoItems(): ItemList<import('mithril').Children>;
}
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -16,36 +16,36 @@ export default class UserPage extends Page<import("../../common/components/Page"
/**
* Get the content to display in the user page.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
*/
content(): any;
content(): import('mithril').Children;
/**
* Initialize the component with a user, and trigger the loading of their
* activity feed.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
* @protected
*/
protected show(user: any): void;
protected show(user: import('../../common/models/User').default): void;
/**
* Given a username, load the user's profile from the store, or make a request
* if we don't have it yet. Then initialize the profile page with that user.
*
* @param {String} username
* @param {string} username
*/
loadUser(username: string): void;
/**
* Build an item list for the content of the sidebar.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
sidebarItems(): ItemList<any>;
sidebarItems(): ItemList<import('mithril').Children>;
/**
* Build an item list for the navigation in the sidebar.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
navItems(): ItemList<any>;
navItems(): ItemList<import('mithril').Children>;
}
import Page from "../../common/components/Page";
import ItemList from "../../common/utils/ItemList";

View File

@@ -1,13 +1,26 @@
import Component from '../../common/Component';
import type Mithril from 'mithril';
export interface IWelcomeHeroAttrs {
}
/**
* The `WelcomeHero` component displays a hero that welcomes the user to the
* forum.
*/
export default class WelcomeHero extends Component<import("../../common/Component").ComponentAttrs, undefined> {
constructor();
hidden: string | boolean | null | undefined;
export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
/**
* @deprecated Extend the `isHidden` method instead.
*/
hidden: boolean;
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>): void;
view(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>): JSX.Element | null;
/**
* Hide the welcome hero.
*/
hide(): void;
/**
* Determines whether the welcome hero should be hidden.
*
* @returns if the welcome hero is hidden.
*/
isHidden(): boolean;
}
import Component from "../../common/Component";

View File

@@ -15,9 +15,9 @@ declare class ComposerState {
* The composer's intended height, which can be modified by the user
* (by dragging the composer handle).
*
* @type {Integer}
* @type {number}
*/
height: any;
height: number;
/**
* The dynamic component being shown inside the composer.
*
@@ -33,16 +33,15 @@ declare class ComposerState {
/**
* Load a content component into the composer.
*
* @param {ComposerBody} componentClass
* @public
* @param {typeof import('../components/ComposerBody').default} componentClass
*/
public load(componentClass: any, attrs: any): void;
load(componentClass: typeof import('../components/ComposerBody').default, attrs: any): void;
/**
* Clear the composer's content component.
*/
clear(): void;
onExit: {
callback: Function;
callback: () => boolean;
message: string;
} | null | undefined;
fields: {
@@ -50,47 +49,34 @@ declare class ComposerState {
} | undefined;
/**
* Show the composer.
*
* @public
*/
public show(): void;
show(): void;
/**
* Close the composer.
*
* @public
*/
public hide(): void;
hide(): void;
/**
* Confirm with the user so they don't lose their content, then close the
* composer.
*
* @public
*/
public close(): void;
close(): void;
/**
* Minimize the composer. Has no effect if the composer is hidden.
*
* @public
*/
public minimize(): void;
minimize(): void;
/**
* Take the composer into fullscreen mode. Has no effect if the composer is
* hidden.
*
* @public
*/
public fullScreen(): void;
fullScreen(): void;
/**
* Exit fullscreen mode.
*
* @public
*/
public exitFullScreen(): void;
exitFullScreen(): void;
/**
* Determine whether the body matches the given component class and data.
*
* @param {object} type The component class to check against. Subclasses are
* accepted as well.
* @param {object} type The component class to check against. Subclasses are accepted as well.
* @param {object} data
* @return {boolean}
*/
@@ -110,23 +96,22 @@ declare class ComposerState {
* This will be true if the Composer is in full-screen mode on desktop,
* or if we are on a mobile device, where we always consider the composer as full-screen..
*
* @return {Boolean}
* @public
* @return {boolean}
*/
public isFullScreen(): boolean;
isFullScreen(): boolean;
/**
* Check whether or not the user is currently composing a reply to a
* discussion.
*
* @param {Discussion} discussion
* @return {Boolean}
* @param {import('../../common/models/Discussion').default} discussion
* @return {boolean}
*/
composingReplyTo(discussion: any): boolean;
composingReplyTo(discussion: import('../../common/models/Discussion').default): boolean;
/**
* Confirm with the user that they want to close the composer and lose their
* content.
*
* @return {Boolean} Whether or not the exit was cancelled.
* @return {boolean} Whether or not the exit was cancelled.
*/
preventExit(): boolean;
/**
@@ -136,27 +121,27 @@ declare class ComposerState {
* confirmation is necessary. If the callback returns true at the time of
* closing, the provided text will be shown in a standard confirmation dialog.
*
* @param {Function} callback
* @param {String} message
* @param {() => boolean} callback
* @param {string} message
*/
preventClosingWhen(callback: Function, message: string): void;
preventClosingWhen(callback: () => boolean, message: string): void;
/**
* Minimum height of the Composer.
* @returns {Integer}
* @returns {number}
*/
minimumHeight(): any;
minimumHeight(): number;
/**
* Maxmimum height of the Composer.
* @returns {Integer}
* @returns {number}
*/
maximumHeight(): any;
maximumHeight(): number;
/**
* Computed the composer's current height, based on the intended height, and
* the composer's current state. This will be applied to the composer's
* the composer's current state. This will be applied to the composer
* content's DOM element.
* @returns {Integer|String}
* @returns {number | string}
*/
computedHeight(): any | string;
computedHeight(): number | string;
}
declare namespace ComposerState {
namespace Position {

View File

@@ -6,7 +6,7 @@ declare class PostStreamState {
*
* @type {Discussion}
*/
discussion: any;
discussion: Discussion;
/**
* Whether or not the infinite-scrolling auto-load functionality is
* disabled.
@@ -46,33 +46,30 @@ declare class PostStreamState {
/**
* Update the stream so that it loads and includes the latest posts in the
* discussion, if the end is being viewed.
*
* @public
*/
public update(): Promise<any>;
update(): Promise<void>;
visibleEnd: any;
/**
* Load and scroll up to the first post in the discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
goToFirst(): Promise<any>;
goToFirst(): Promise<void>;
/**
* Load and scroll down to the last post in the discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
goToLast(): Promise<any>;
goToLast(): Promise<void>;
/**
* Load and scroll to a post with a certain number.
*
* @param {number|String} number The post number to go to. If 'reply', go to
* the last post and scroll the reply preview into view.
* @param {Boolean} noAnimation
* @return {Promise}
* @param {number | string} number The post number to go to. If 'reply', go to the last post and scroll the reply preview into view.
* @param {boolean} [noAnimation]
* @return {Promise<void>}
*/
goToNumber(number: number | string, noAnimation?: boolean): Promise<any>;
loadPromise: Promise<any> | undefined;
goToNumber(number: number | string, noAnimation?: boolean | undefined): Promise<void>;
loadPromise: Promise<void> | undefined;
needsScroll: boolean | undefined;
targetPost: {
number: string | number;
@@ -86,28 +83,28 @@ declare class PostStreamState {
* Load and scroll to a certain index within the discussion.
*
* @param {number} index
* @param {Boolean} noAnimation
* @return {Promise}
* @param {boolean} [noAnimation]
* @return {Promise<void>}
*/
goToIndex(index: number, noAnimation?: boolean): Promise<any>;
goToIndex(index: number, noAnimation?: boolean | undefined): Promise<void>;
/**
* Clear the stream and load posts near a certain number. Returns a promise.
* If the post with the given number is already loaded, the promise will be
* resolved immediately.
*
* @param {number} number
* @return {Promise}
* @return {Promise<void>}
*/
loadNearNumber(number: number): Promise<any>;
loadNearNumber(number: number): Promise<void>;
/**
* Clear the stream and load posts near a certain index. A page of posts
* surrounding the given index will be loaded. Returns a promise. If the given
* index is already loaded, the promise will be resolved immediately.
*
* @param {number} index
* @return {Promise}
* @return {Promise<void>}
*/
loadNearIndex(index: number): Promise<any>;
loadNearIndex(index: number): Promise<void>;
/**
* Load the next page of posts.
*/
@@ -122,7 +119,7 @@ declare class PostStreamState {
*
* @param {number} start
* @param {number} end
* @param {Boolean} backwards
* @param {boolean} backwards
*/
loadPage(start: number, end: number, backwards?: boolean): void;
/**
@@ -131,15 +128,15 @@ declare class PostStreamState {
*
* @param {number} start
* @param {number} end
* @return {Promise}
* @return {Promise<void>}
*/
loadRange(start: number, end: number): Promise<any>;
loadRange(start: number, end: number): Promise<void>;
/**
* Set up the stream with the given array of posts.
*
* @param {Post[]} posts
* @param {import('../../common/models/Post').default[]} posts
*/
show(posts: any[]): void;
show(posts: import('../../common/models/Post').default[]): void;
/**
* Reset the stream so that a specific range of posts is displayed. If a range
* is not specified, the first page of posts will be displayed.
@@ -153,7 +150,7 @@ declare class PostStreamState {
*
* @return {Post[]}
*/
posts(): any[];
posts(): Post[];
/**
* Get the total number of posts in the discussion.
*
@@ -164,7 +161,7 @@ declare class PostStreamState {
* Check whether or not the scrubber should be disabled, i.e. if all of the
* posts are visible in the viewport.
*
* @return {Boolean}
* @return {boolean}
*/
disabled(): boolean;
/**

View File

@@ -2,159 +2,151 @@ declare namespace _default {
/**
* Get a list of controls for a discussion.
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @public
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
*/
function controls(discussion: any, context: any): ItemList<any>;
function controls(discussion: import("../../common/models/Discussion").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get a list of controls for a discussion.
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @public
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
*/
function controls(discussion: any, context: any): ItemList<any>;
function controls(discussion: import("../../common/models/Discussion").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a discussion pertaining to the current user (e.g. reply,
* follow).
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function userControls(discussion: any, context: any): ItemList<any>;
function userControls(discussion: import("../../common/models/Discussion").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a discussion pertaining to the current user (e.g. reply,
* follow).
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function userControls(discussion: any, context: any): ItemList<any>;
function userControls(discussion: import("../../common/models/Discussion").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a discussion pertaining to moderation (e.g. rename, lock).
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function moderationControls(discussion: any): ItemList<any>;
function moderationControls(discussion: import("../../common/models/Discussion").default): ItemList<import("mithril").Children>;
/**
* Get controls for a discussion pertaining to moderation (e.g. rename, lock).
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function moderationControls(discussion: any): ItemList<any>;
function moderationControls(discussion: import("../../common/models/Discussion").default): ItemList<import("mithril").Children>;
/**
* Get controls for a discussion which are destructive (e.g. delete).
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function destructiveControls(discussion: any): ItemList<any>;
function destructiveControls(discussion: import("../../common/models/Discussion").default): ItemList<import("mithril").Children>;
/**
* Get controls for a discussion which are destructive (e.g. delete).
*
* @param {Discussion} discussion
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Discussion').default} discussion
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function destructiveControls(discussion: any): ItemList<any>;
function destructiveControls(discussion: import("../../common/models/Discussion").default): ItemList<import("mithril").Children>;
/**
* Open the reply composer for the discussion. A promise will be returned,
* which resolves when the composer opens successfully. If the user is not
* logged in, they will be prompted. If they don't have permission to
* reply, the promise will be rejected.
*
* @param {Boolean} goToLast Whether or not to scroll down to the last post if
* the discussion is being viewed.
* @param {Boolean} forceRefresh Whether or not to force a reload of the
* composer component, even if it is already open for this discussion.
* @return {Promise}
* @param {boolean} goToLast Whether or not to scroll down to the last post if the discussion is being viewed.
* @param {boolean} forceRefresh Whether or not to force a reload of the composer component, even if it is already open for this discussion.
*
* @return {Promise<void>}
*/
function replyAction(goToLast: boolean, forceRefresh: boolean): Promise<any>;
function replyAction(goToLast: boolean, forceRefresh: boolean): Promise<void>;
/**
* Open the reply composer for the discussion. A promise will be returned,
* which resolves when the composer opens successfully. If the user is not
* logged in, they will be prompted. If they don't have permission to
* reply, the promise will be rejected.
*
* @param {Boolean} goToLast Whether or not to scroll down to the last post if
* the discussion is being viewed.
* @param {Boolean} forceRefresh Whether or not to force a reload of the
* composer component, even if it is already open for this discussion.
* @return {Promise}
* @param {boolean} goToLast Whether or not to scroll down to the last post if the discussion is being viewed.
* @param {boolean} forceRefresh Whether or not to force a reload of the composer component, even if it is already open for this discussion.
*
* @return {Promise<void>}
*/
function replyAction(goToLast: boolean, forceRefresh: boolean): Promise<any>;
function replyAction(goToLast: boolean, forceRefresh: boolean): Promise<void>;
/**
* Hide a discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
function hideAction(): Promise<any>;
function hideAction(): Promise<void>;
/**
* Hide a discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
function hideAction(): Promise<any>;
function hideAction(): Promise<void>;
/**
* Restore a discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
function restoreAction(): Promise<any>;
function restoreAction(): Promise<void>;
/**
* Restore a discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
function restoreAction(): Promise<any>;
function restoreAction(): Promise<void>;
/**
* Delete the discussion after confirming with the user.
*
* @return {Promise}
* @return {Promise<void>}
*/
function deleteAction(): Promise<any>;
function deleteAction(): Promise<void>;
/**
* Delete the discussion after confirming with the user.
*
* @return {Promise}
* @return {Promise<void>}
*/
function deleteAction(): Promise<any>;
function deleteAction(): Promise<void>;
/**
* Rename the discussion.
*
* @return {Promise}
*/
function renameAction(): Promise<any>;
function renameAction(): any;
/**
* Rename the discussion.
*
* @return {Promise}
*/
function renameAction(): Promise<any>;
function renameAction(): any;
}
export default _default;
import ItemList from "../../common/utils/ItemList";

View File

@@ -44,42 +44,30 @@ export default class Pane {
protected showing: boolean;
/**
* Enable the pane.
*
* @public
*/
public enable(): void;
enable(): void;
/**
* Disable the pane.
*
* @public
*/
public disable(): void;
disable(): void;
/**
* Show the pane.
*
* @public
*/
public show(): void;
show(): void;
/**
* Hide the pane.
*
* @public
*/
public hide(): void;
hide(): void;
/**
* Begin a timeout to hide the pane, which can be cancelled by showing the
* pane.
*
* @public
*/
public onmouseleave(): void;
onmouseleave(): void;
hideTimeout: NodeJS.Timeout | undefined;
/**
* Toggle whether or not the pane is pinned.
*
* @public
*/
public togglePinned(): void;
togglePinned(): void;
/**
* Apply the appropriate CSS classes to the page element.
*

View File

@@ -2,131 +2,129 @@ declare namespace _default {
/**
* Get a list of controls for a post.
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @public
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
*/
function controls(post: any, context: any): ItemList<any>;
function controls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get a list of controls for a post.
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @public
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
*/
function controls(post: any, context: any): ItemList<any>;
function controls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a post pertaining to the current user (e.g. report).
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
* @protected
*/
function userControls(post: any, context: any): ItemList<any>;
function userControls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a post pertaining to the current user (e.g. report).
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
* @protected
*/
function userControls(post: any, context: any): ItemList<any>;
function userControls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a post pertaining to moderation (e.g. edit).
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
* @protected
*/
function moderationControls(post: any, context: any): ItemList<any>;
function moderationControls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a post pertaining to moderation (e.g. edit).
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
* @protected
*/
function moderationControls(post: any, context: any): ItemList<any>;
function moderationControls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a post that are destructive (e.g. delete).
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
* @protected
*/
function destructiveControls(post: any, context: any): ItemList<any>;
function destructiveControls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a post that are destructive (e.g. delete).
*
* @param {Post} post
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/Post').default} post
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}')}
* @protected
*/
function destructiveControls(post: any, context: any): ItemList<any>;
function destructiveControls(post: import("../../common/models/Post").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Open the composer to edit a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function editAction(): Promise<any>;
function editAction(): Promise<void>;
/**
* Open the composer to edit a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function editAction(): Promise<any>;
function editAction(): Promise<void>;
/**
* Hide a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function hideAction(): Promise<any>;
function hideAction(): Promise<void>;
/**
* Hide a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function hideAction(): Promise<any>;
function hideAction(): Promise<void>;
/**
* Restore a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function restoreAction(): Promise<any>;
function restoreAction(): Promise<void>;
/**
* Restore a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function restoreAction(): Promise<any>;
function restoreAction(): Promise<void>;
/**
* Delete a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function deleteAction(context: any): Promise<any>;
function deleteAction(context: any): Promise<void>;
/**
* Delete a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
function deleteAction(context: any): Promise<any>;
function deleteAction(context: any): Promise<void>;
}
export default _default;
import ItemList from "../../common/utils/ItemList";

View File

@@ -2,121 +2,119 @@ declare namespace _default {
/**
* Get a list of controls for a user.
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @public
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
*/
function controls(user: any, context: any): ItemList<any>;
function controls(user: import("../../common/models/User").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get a list of controls for a user.
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @public
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
*/
function controls(user: any, context: any): ItemList<any>;
function controls(user: import("../../common/models/User").default, context: import("../../common/Component").default<any, any>): ItemList<import("mithril").Children>;
/**
* Get controls for a user pertaining to the current user (e.g. poke, follow).
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function userControls(): ItemList<any>;
function userControls(): ItemList<import("mithril").Children>;
/**
* Get controls for a user pertaining to the current user (e.g. poke, follow).
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function userControls(): ItemList<any>;
function userControls(): ItemList<import("mithril").Children>;
/**
* Get controls for a user pertaining to moderation (e.g. suspend, edit).
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function moderationControls(user: any): ItemList<any>;
function moderationControls(user: import("../../common/models/User").default): ItemList<import("mithril").Children>;
/**
* Get controls for a user pertaining to moderation (e.g. suspend, edit).
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function moderationControls(user: any): ItemList<any>;
function moderationControls(user: import("../../common/models/User").default): ItemList<import("mithril").Children>;
/**
* Get controls for a user which are destructive (e.g. delete).
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function destructiveControls(user: any): ItemList<any>;
function destructiveControls(user: import("../../common/models/User").default): ItemList<import("mithril").Children>;
/**
* Get controls for a user which are destructive (e.g. delete).
*
* @param {User} user
* @param {*} context The parent component under which the controls menu will
* be displayed.
* @return {ItemList}
* @param {import('../../common/models/User').default} user
* @param {import('../../common/Component').default<any, any>} context The parent component under which the controls menu will be displayed.
*
* @return {ItemList<import('mithril').Children>}
* @protected
*/
function destructiveControls(user: any): ItemList<any>;
function destructiveControls(user: import("../../common/models/User").default): ItemList<import("mithril").Children>;
/**
* Delete the user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
*/
function deleteAction(user: any): void;
function deleteAction(user: import("../../common/models/User").default): void;
/**
* Delete the user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
*/
function deleteAction(user: any): void;
function deleteAction(user: import("../../common/models/User").default): void;
/**
* Show deletion alert of user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
* @param {string} type
*/
function showDeletionAlert(user: any, type: string): void;
function showDeletionAlert(user: import("../../common/models/User").default, type: string): void;
/**
* Show deletion alert of user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
* @param {string} type
*/
function showDeletionAlert(user: any, type: string): void;
function showDeletionAlert(user: import("../../common/models/User").default, type: string): void;
/**
* Edit the user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
*/
function editAction(user: any): void;
function editAction(user: import("../../common/models/User").default): void;
/**
* Edit the user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
*/
function editAction(user: any): void;
function editAction(user: import("../../common/models/User").default): void;
}
export default _default;
import ItemList from "../../common/utils/ItemList";

View File

@@ -1,6 +1,6 @@
/**
* Shows an alert if the user has not yet confirmed their email address.
*
* @param {ForumApplication} app
* @param {import('../ForumApplication').default} app
*/
export default function alertEmailConfirmation(app: any): void;
export default function alertEmailConfirmation(app: import('../ForumApplication').default): void;

View File

@@ -4,11 +4,14 @@
* controls.
*
* It relies on the element having children with particular CSS classes.
* TODO: document
*
* @param {DOMElement} element
* @return {Object}
* @property {function} reset Revert the slider to its original position. This
* should be called, for example, when a controls dropdown is closed.
* The function returns a record with a `reset` proeprty. This is a function
* which reverts the slider to its original position. This should be called,
* for example, when a controls dropdown is closed.
*
* @param {HTMLElement | SVGElement | Element} element
* @return {{ reset : () => void }}
*/
export default function slidable(element: any): Object;
export default function slidable(element: HTMLElement | SVGElement | Element): {
reset: () => void;
};

2
js/dist/admin.js generated vendored

File diff suppressed because one or more lines are too long

2
js/dist/admin.js.map generated vendored

File diff suppressed because one or more lines are too long

2
js/dist/forum.js generated vendored

File diff suppressed because one or more lines are too long

2
js/dist/forum.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@
"clsx": "^1.1.1",
"color-thief-browser": "^2.0.2",
"dayjs": "^1.10.7",
"dialog-polyfill": "^0.5.6",
"focus-trap": "^6.7.1",
"jquery": "^3.6.0",
"jquery.hotkeys": "^0.1.0",
@@ -20,7 +21,7 @@
},
"devDependencies": {
"@flarum/prettier-config": "^1.0.0",
"@types/jquery": "^3.5.8",
"@types/jquery": "^3.5.10",
"@types/mithril": "^2.0.8",
"@types/punycode": "^2.1.0",
"@types/textarea-caret": "^3.0.1",
@@ -29,10 +30,10 @@
"expose-loader": "^3.1.0",
"flarum-tsconfig": "^1.0.2",
"flarum-webpack-config": "^2.0.0",
"prettier": "^2.4.1",
"typescript": "^4.4.4",
"prettier": "^2.5.1",
"typescript": "^4.5.4",
"typescript-coverage-report": "^0.6.1",
"webpack": "^5.61.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-merge": "^5.8.0"
},
@@ -47,5 +48,5 @@
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
"check-typings-coverage": "typescript-coverage-report"
},
"packageManager": "yarn@3.1.0"
"packageManager": "yarn@3.1.1"
}

46
js/src/@types/modals/index.d.ts vendored Normal file
View File

@@ -0,0 +1,46 @@
/**
* Only supported natively in Chrome. In testing in Safari Technology Preview.
*
* Please register modals with the dialog polyfill before use:
*
* ```js
* dialogPolyfill.registerDialog(dialogElementReference);
* ```
*
* ### Events
*
* Two events are fired by dialogs:
* - `cancel` - Fired when the user instructs the browser that they wish to dismiss the current open dialog.
* - `close` - Fired when the dialog is closed.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement
*/
interface HTMLDialogElement {
/**
* Shows the `<dialog>` element as a top-layered element in the document.
*/
show(): void;
/**
* Displays the dialog as a modal, over the top of any other dialogs that
* might be present. Interaction outside the dialog is blocked.
*/
showModal(): void;
/**
* If the `<dialog>` element is currently being shown, dismiss it.
*
* @param returnValue An optional return value for the dialog to hold. *This is currently unused by Flarum.*
*/
close(returnValue?: string): void;
/**
* A return value for the dialog to hold.
*
* *This is currently unused by Flarum.*
*/
returnValue: string;
/**
* Whether the dialog is currently open.
*/
open: boolean;
}

View File

@@ -55,7 +55,7 @@ export default class AdminNav extends Component {
/**
* Build an item list of main links to show in the admin navigation.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items() {
const items = new ItemList();

View File

@@ -120,8 +120,7 @@ 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}
* @public
* @return {ItemList<{ path: string, label: import('mithril').Children }>}
*/
homePageItems() {
const items = new ItemList();

View File

@@ -8,7 +8,7 @@ export default class DashboardWidget extends Component {
/**
* Get the class name to apply to the widget.
*
* @return {String}
* @return {string}
*/
className() {
return '';
@@ -17,9 +17,9 @@ export default class DashboardWidget extends Component {
/**
* Get the content of the widget.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
*/
content() {
return [];
return null;
}
}

View File

@@ -21,7 +21,7 @@ export default class HeaderPrimary extends Component {
/**
* Build an item list for the controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items() {
return new ItemList();

View File

@@ -16,7 +16,7 @@ export default class HeaderSecondary extends Component {
/**
* Build an item list for the controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items() {
const items = new ItemList();
@@ -28,7 +28,7 @@ export default class HeaderSecondary extends Component {
</LinkButton>
);
items.add('session', SessionDropdown.component());
items.add('session', <SessionDropdown />);
return items;
}

View File

@@ -78,7 +78,7 @@ export default class UploadImageButton extends Button {
/**
* After a successful upload/removal, reload the page.
*
* @param {Object} response
* @param {object} response
* @protected
*/
success(response) {
@@ -88,7 +88,7 @@ export default class UploadImageButton extends Button {
/**
* If upload/removal fails, stop loading.
*
* @param {Object} response
* @param {object} response
* @protected
*/
failure(response) {

View File

@@ -9,6 +9,7 @@ import Translator from './Translator';
import Store, { ApiPayload, ApiResponse, ApiResponsePlural, ApiResponseSingle, payloadIsPlural } from './Store';
import Session from './Session';
import extract from './utils/extract';
import extractText from './utils/extractText';
import Drawer from './utils/Drawer';
import mapRoutes from './utils/mapRoutes';
import RequestError, { InternalFlarumRequestOptions } from './utils/RequestError';
@@ -105,14 +106,21 @@ export interface RouteResolver<
*
* Returns the component class, and **not** a Vnode or JSX
* expression.
*
* @see https://mithril.js.org/route.html#routeresolveronmatch
*/
onmatch(this: this, args: RouteArgs, requestedPath: string, route: string): { new (): Comp };
/**
* A function which renders the provided component.
*
* If not specified, the route will default to rendering the
* component on its own, inside of a fragment.
*
* Returns a Mithril Vnode or other children.
*
* @see https://mithril.js.org/route.html#routeresolverrender
*/
render(this: this, vnode: Mithril.Vnode<Attrs, Comp>): Mithril.Children;
render?(this: this, vnode: Mithril.Vnode<Attrs, Comp>): Mithril.Children;
}
/**
@@ -365,9 +373,21 @@ export default class Application {
updateTitle(): void {
const count = this.titleCount ? `(${this.titleCount}) ` : '';
const pageTitleWithSeparator = this.title && m.route.get() !== this.forum.attribute('basePath') + '/' ? this.title + ' - ' : '';
const title = this.forum.attribute('title');
document.title = count + pageTitleWithSeparator + title;
const onHomepage = m.route.get() === this.forum.attribute('basePath') + '/';
const params = {
pageTitle: this.title,
forumName: this.forum.attribute('title'),
// Until we add page numbers to the frontend, this is constant at 1
// so that the page number portion doesn't show up in the URL.
pageNumber: 1,
};
const title =
onHomepage || !this.title
? extractText(app.translator.trans('core.lib.meta_titles.without_page_title', params))
: extractText(app.translator.trans('core.lib.meta_titles.with_page_title', params));
document.title = count + title;
}
protected transformRequestOptions<ResponseType>(flarumOptions: FlarumRequestOptions<ResponseType>): InternalFlarumRequestOptions<ResponseType> {
@@ -451,9 +471,6 @@ export default class Application {
* Make an AJAX request, handling any low-level errors that may occur.
*
* @see https://mithril.js.org/request.html
*
* @param options
* @return {Promise}
*/
request<ResponseType>(originalOptions: FlarumRequestOptions<ResponseType>): Promise<ResponseType> {
const options = this.transformRequestOptions(originalOptions);

View File

@@ -30,8 +30,8 @@ export default abstract class Fragment {
* containing all of the `li` elements inside the DOM element of this
* fragment.
*
* @param {String} [selector] a jQuery-compatible selector string
* @returns {jQuery} the jQuery object for the DOM node
* @param [selector] a jQuery-compatible selector string
* @returns the jQuery object for the DOM node
* @final
*/
public $(selector?: string): JQuery {

View File

@@ -1,6 +1,6 @@
import app from '../common/app';
import { FlarumRequestOptions } from './Application';
import fireDebugWarning from './helpers/fireDebugWarning';
import { fireDeprecationWarning } from './helpers/fireDebugWarning';
import Model, { ModelData, SavedModelData } from './Model';
export interface MetaInformation {
@@ -94,7 +94,7 @@ export default class Store {
* within the 'data' key of the payload.
*/
pushPayload<M extends Model>(payload: ApiPayloadSingle): ApiResponseSingle<M>;
pushPayload<Ms extends Model[]>(payload: ApiPayloadPlural): ApiResponseSingle<Ms[number]>;
pushPayload<Ms extends Model[]>(payload: ApiPayloadPlural): ApiResponsePlural<Ms[number]>;
pushPayload<M extends Model | Model[]>(payload: ApiPayload): ApiResponse<FlatArray<M, 1>> {
if (payload.included) payload.included.map(this.pushObject.bind(this));
@@ -123,11 +123,7 @@ export default class Store {
if (!this.models[data.type]) {
if (!allowUnregistered) {
setTimeout(() =>
fireDebugWarning(
`[Flarum 2.0 Deprecation] Cannot push object of type \`${data.type}\`, as that type has not yet been registered in the store. This will throw an error in Flarum 2.0 and later.
For more information, see https://github.com/flarum/core/pull/3206.`
)
fireDeprecationWarning(`Pushing object of type \`${data.type}\` not allowed, as type not yet registered in the store.`, '3206')
);
}

View File

@@ -3,6 +3,8 @@ import Button from './Button';
import listItems from '../helpers/listItems';
import extract from '../utils/extract';
import type Mithril from 'mithril';
import classList from '../utils/classList';
import app from '../app';
export interface AlertAttrs extends ComponentAttrs {
/** The type of alert this is. Will be used to give the alert a class name of `Alert--{type}`. */
@@ -24,7 +26,7 @@ export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<
const attrs = Object.assign({}, this.attrs);
const type = extract(attrs, 'type');
attrs.className = 'Alert Alert--' + type + ' ' + (attrs.className || '');
attrs.className = classList('Alert', `Alert--${type}`, attrs.className);
const content = extract(attrs, 'content') || vnode.children;
const controls = (extract(attrs, 'controls') || []) as Mithril.Vnode[];
@@ -37,13 +39,20 @@ export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<
const dismissControl: Mithril.Vnode[] = [];
if (dismissible || dismissible === undefined) {
dismissControl.push(<Button icon="fas fa-times" className="Button Button--link Button--icon Alert-dismiss" onclick={ondismiss} />);
dismissControl.push(
<Button
aria-label={app.translator.trans('core.lib.alert.dismiss_a11y_label')}
icon="fas fa-times"
class="Button Button--link Button--icon Alert-dismiss"
onclick={ondismiss}
/>
);
}
return (
<div {...attrs}>
<span className="Alert-body">{content}</span>
<ul className="Alert-controls">{listItems(controls.concat(dismissControl))}</ul>
<span class="Alert-body">{content}</span>
<ul class="Alert-controls">{listItems(controls.concat(dismissControl))}</ul>
</div>
);
}

View File

@@ -1,5 +1,4 @@
import Component from '../Component';
import Alert from './Alert';
/**
* The `AlertManager` component provides an area in which `Alert` components can
@@ -14,14 +13,18 @@ export default class AlertManager extends Component {
view() {
return (
<div className="AlertManager">
{Object.entries(this.state.getActiveAlerts()).map(([key, alert]) => (
<div className="AlertManager-alert">
<alert.componentClass {...alert.attrs} ondismiss={this.state.dismiss.bind(this.state, key)}>
{alert.children}
</alert.componentClass>
</div>
))}
<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

@@ -107,7 +107,7 @@ export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> ext
const { 'aria-label': ariaLabel } = this.attrs;
if (!ariaLabel && !extractText(vnode.children) && !this.element?.getAttribute?.('aria-label')) {
if (this.view === Button.prototype.view && !ariaLabel && !extractText(vnode.children) && !this.element?.getAttribute?.('aria-label')) {
fireDebugWarning(
'[Flarum Accessibility Warning] Button has no content and no accessible label. This means that screen-readers will not be able to interpret its meaning and just read "Button". Consider providing accessible text via the `aria-label` attribute. https://web.dev/button-name',
this.element

View File

@@ -44,7 +44,7 @@ export default class Checkbox extends Component {
/**
* Get the template for the checkbox's display (tick/cross icon).
*
* @return {*}
* @return {import('mithril').Children}
* @protected
*/
getDisplay() {
@@ -54,7 +54,7 @@ export default class Checkbox extends Component {
/**
* Run a callback when the state of the checkbox is changed.
*
* @param {Boolean} checked
* @param {boolean} checked
* @protected
*/
onchange(checked) {

View File

@@ -103,7 +103,7 @@ export default class Dropdown extends Component {
/**
* Get the template for the button.
*
* @return {*}
* @return {import('mithril').Children}
* @protected
*/
getButton(children) {
@@ -123,7 +123,7 @@ export default class Dropdown extends Component {
/**
* Get the template for the button's content.
*
* @return {*}
* @return {import('mithril').Children}
* @protected
*/
getButtonContent(children) {

View File

@@ -35,8 +35,8 @@ export default class LinkButton extends Button {
/**
* Determine whether a component with the given attrs is 'active'.
*
* @param {Object} attrs
* @return {Boolean}
* @param {object} attrs
* @return {boolean}
*/
static isActive(attrs) {
return typeof attrs.active !== 'undefined' ? attrs.active : m.route.get() === attrs.href;

View File

@@ -8,6 +8,7 @@ import type ModalManagerState from '../states/ModalManagerState';
import type RequestError from '../utils/RequestError';
import type ModalManager from './ModalManager';
import fireDebugWarning from '../helpers/fireDebugWarning';
import classList from '../utils/classList';
export interface IInternalModalAttrs {
state: ModalManagerState;
@@ -15,16 +16,63 @@ export interface IInternalModalAttrs {
animateHide: ModalManager['animateHide'];
}
export interface IDismissibleOptions {
/**
* @deprecated Check specific individual attributes instead. Will be removed in Flarum 2.0.
*/
isDismissible: boolean;
viaCloseButton: boolean;
viaEscKey: boolean;
viaBackdropClick: boolean;
}
/**
* The `Modal` component displays a modal dialog, wrapped in a form. Subclasses
* should implement the `className`, `title`, and `content` methods.
*/
export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IInternalModalAttrs> extends Component<ModalAttrs> {
// TODO: [Flarum 2.0] remove `isDismissible` static attribute
/**
* Determine whether or not the modal should be dismissible via an 'x' button.
*
* @deprecated Use the individual `isDismissibleVia...` attributes instead and remove references to this.
*/
static readonly isDismissible: boolean = true;
/**
* Can the model be dismissed with a close button (X)?
*
* If `false`, no close button is shown.
*/
protected static readonly isDismissibleViaCloseButton: boolean = true;
/**
* Can the modal be dismissed by pressing the Esc key on a keyboard?
*/
protected static readonly isDismissibleViaEscKey: boolean = true;
/**
* Can the modal be dismissed via a click on the backdrop.
*/
protected static readonly isDismissibleViaBackdropClick: boolean = true;
static get dismissibleOptions(): IDismissibleOptions {
// If someone sets this to `false`, provide the same behaviour as previous versions of Flarum.
if (!this.isDismissible) {
return {
isDismissible: false,
viaCloseButton: false,
viaEscKey: false,
viaBackdropClick: false,
};
}
return {
isDismissible: true,
viaCloseButton: this.isDismissibleViaCloseButton,
viaEscKey: this.isDismissibleViaEscKey,
viaBackdropClick: this.isDismissibleViaBackdropClick,
};
}
protected loading: boolean = false;
/**
@@ -87,16 +135,16 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
}
return (
<div className={'Modal modal-dialog ' + this.className()}>
<dialog className={classList('Modal modal-dialog fade', this.className())}>
<div className="Modal-content">
{(this.constructor as typeof Modal).isDismissible && (
{this.dismissibleOptions.viaCloseButton && (
<div className="Modal-close App-backControl">
{Button.component({
icon: 'fas fa-times',
onclick: () => this.hide(),
className: 'Button Button--icon Button--link',
'aria-label': app.translator.trans('core.lib.modal.close'),
})}
<Button
icon="fas fa-times"
onclick={() => this.hide()}
className="Button Button--icon Button--link"
aria-label={app.translator.trans('core.lib.modal.close')}
/>
</div>
)}
@@ -110,7 +158,7 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
{this.content()}
</form>
</div>
</div>
</dialog>
);
}
@@ -175,4 +223,8 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
this.onready();
}
}
private get dismissibleOptions(): IDismissibleOptions {
return (this.constructor as typeof Modal).dismissibleOptions;
}
}

View File

@@ -22,18 +22,25 @@ export default class ModalManager extends Component<IModalManagerAttrs> {
*/
protected modalShown: boolean = false;
protected modalClosing: boolean = false;
protected clickStartedOnBackdrop: boolean = false;
view(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): Mithril.Children {
const modal = this.attrs.state.modal;
const Tag = modal?.componentClass;
return (
<div className="ModalManager modal fade">
{!!modal &&
modal.componentClass.component({
...modal.attrs,
animateShow: this.animateShow.bind(this),
animateHide: this.animateHide.bind(this),
state: this.attrs.state,
})}
<div className="ModalManager modal">
{!!Tag && (
<Tag
key={modal?.key}
{...modal.attrs}
animateShow={this.animateShow.bind(this)}
animateHide={this.animateHide.bind(this)}
state={this.attrs.state}
/>
)}
</div>
);
}
@@ -41,11 +48,6 @@ export default class ModalManager extends Component<IModalManagerAttrs> {
oncreate(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): void {
super.oncreate(vnode);
// Ensure the modal state is notified about a closed modal, even when the
// DOM-based Bootstrap JavaScript code triggered the closing of the modal,
// e.g. via ESC key or a click on the modal backdrop.
this.$().on('hidden.bs.modal', this.attrs.state.close.bind(this.attrs.state));
this.focusTrap = createFocusTrap(this.element as HTMLElement);
}
@@ -62,35 +64,93 @@ export default class ModalManager extends Component<IModalManagerAttrs> {
});
}
private get dialogElement(): HTMLDialogElement {
return this.element.querySelector('dialog') as HTMLDialogElement;
}
animateShow(readyCallback: () => void): void {
if (!this.attrs.state.modal) return;
const dismissible = !!this.attrs.state.modal.componentClass.isDismissible;
const dismissibleState = this.attrs.state.modal.componentClass.dismissibleOptions;
this.modalShown = true;
// If we are opening this modal while another modal is already open,
// the shown event will not run, because the modal is already open.
// So, we need to manually trigger the readyCallback.
if (this.$().hasClass('in')) {
readyCallback();
return;
// Register with polyfill
dialogPolyfill.registerDialog(this.dialogElement);
if (!dismissibleState.viaEscKey) this.dialogElement.addEventListener('cancel', this.preventEscPressHandler);
if (dismissibleState.viaBackdropClick) {
this.dialogElement.addEventListener('mousedown', (e) => this.handleBackdropMouseDown.call(this, e));
this.dialogElement.addEventListener('click', (e) => this.handleBackdropClick.call(this, e));
}
this.$()
.one('shown.bs.modal', readyCallback)
// @ts-expect-error: No typings available for Bootstrap modals.
.modal({
backdrop: dismissible || 'static',
keyboard: dismissible,
})
.modal('show');
this.dialogElement.addEventListener('transitionend', () => readyCallback(), { once: true });
// Ensure the modal state is ALWAYS notified about a closed modal
this.dialogElement.addEventListener('close', this.attrs.state.close.bind(this.attrs.state));
// Use close animation instead
this.dialogElement.addEventListener('cancel', this.animateCloseHandler.bind(this));
this.dialogElement.showModal();
// Fade in
requestAnimationFrame(() => {
this.dialogElement.classList.add('in');
});
}
animateHide(): void {
// @ts-expect-error: No typings available for Bootstrap modals.
this.$().modal('hide');
if (this.modalClosing || !this.modalShown) return;
this.modalClosing = true;
this.modalShown = false;
this.dialogElement.addEventListener(
'transitionend',
() => {
this.dialogElement.close();
this.dialogElement.removeEventListener('cancel', this.preventEscPressHandler);
this.modalShown = false;
this.modalClosing = false;
m.redraw();
},
{ once: true }
);
this.dialogElement.classList.remove('in');
this.dialogElement.classList.add('out');
}
protected animateCloseHandler(this: this, e: Event) {
e.preventDefault();
this.animateHide();
}
protected preventEscPressHandler(this: this, e: Event) {
e.preventDefault();
}
protected handleBackdropMouseDown(this: this, e: MouseEvent) {
// If it's a mousedown on the dialog element, the backdrop has been clicked.
// If it was a mousedown in the modal, the element would be `div.Modal-content` or some other element.
if (e.target !== this.dialogElement) return;
this.clickStartedOnBackdrop = true;
window.addEventListener(
'mouseup',
() => {
if (e.target !== this.dialogElement) this.clickStartedOnBackdrop = false;
},
{ once: true }
);
}
protected handleBackdropClick(this: this, e: MouseEvent) {
// If it's a click on the dialog element, the backdrop has been clicked.
// If it was a click in the modal, the element would be `div.Modal-content` or some other element.
if (e.target !== this.dialogElement || !this.clickStartedOnBackdrop) return;
this.clickStartedOnBackdrop = false;
this.animateHide();
}
}

View File

@@ -36,7 +36,7 @@ export default class Navigation extends Component {
/**
* Get the back button.
*
* @return {Object}
* @return {import('mithril').Children}
* @protected
*/
getBackButton() {
@@ -59,7 +59,7 @@ export default class Navigation extends Component {
/**
* Get the pane pinned toggle button.
*
* @return {Object|String}
* @return {import('mithril').Children}
* @protected
*/
getPaneButton() {
@@ -77,7 +77,7 @@ export default class Navigation extends Component {
/**
* Get the drawer toggle button.
*
* @return {Object|String}
* @return {import('mithril').Children}
* @protected
*/
getDrawerButton() {

View File

@@ -40,7 +40,8 @@ export default class SplitDropdown extends Dropdown {
* Get the first child. If the first child is an array, the first item in that
* array will be returned.
*
* @return {*}
* @param {unknown[] | unknown} children
* @return {unknown}
* @protected
*/
getFirstChild(children) {

View File

@@ -90,7 +90,7 @@ export default class TextEditor extends Component {
/**
* Build an item list for the text editor controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
controlItems() {
const items = new ItemList();
@@ -123,7 +123,7 @@ export default class TextEditor extends Component {
/**
* Build an item list for the toolbar controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
toolbarItems() {
return new ItemList();
@@ -132,7 +132,7 @@ export default class TextEditor extends Component {
/**
* Handle input into the textarea.
*
* @param {String} value
* @param {string} value
*/
oninput(value) {
this.value = value;

View File

@@ -23,7 +23,7 @@
* @param methods The name or names of the method(s) to extend
* @param callback A callback which mutates the method's output
*/
export function extend<T extends object, K extends KeyOfType<T, Function>>(
export function extend<T extends Record<string, any>, K extends KeyOfType<T, Function>>(
object: T,
methods: K | K[],
callback: (this: T, val: ReturnType<T[K]>, ...args: Parameters<T[K]>) => void
@@ -72,7 +72,7 @@ export function extend<T extends object, K extends KeyOfType<T, Function>>(
* @param methods The name or names of the method(s) to override
* @param newMethod The method to replace it with
*/
export function override<T extends object, K extends KeyOfType<T, Function>>(
export function override<T extends Record<any, any>, K extends KeyOfType<T, Function>>(
object: T,
methods: K | K[],
newMethod: (this: T, orig: T[K], ...args: Parameters<T[K]>) => void

View File

@@ -16,3 +16,21 @@ export default function fireDebugWarning(...args: Parameters<typeof console.warn
console.warn(...args);
}
/**
* Fire a Flarum deprecation warning which is shown in the JS console.
*
* These warnings are only shown when the forum is in debug mode, and the function exists to
* reduce bundle size caused by multiple warnings across our JavaScript.
*
* @param message The message to display. (Short, but sweet, please!)
* @param githubId The PR or Issue ID with more info in relation to this change.
* @param [removedFrom] The version in which this feature will be completely removed. (default: 2.0)
* @param [repo] The repo which the issue or PR is located in. (default: flarum/core)
*
* @see {@link fireDebugWarning}
*/
export function fireDeprecationWarning(message: string, githubId: string, removedFrom: string = '2.0', repo: string = 'flarum/core'): void {
// GitHub auto-redirects between `/pull` and `/issues` for us, so using `/pull` saves 2 bytes!
fireDebugWarning(`[Flarum ${removedFrom} Deprecation] ${message}\n\nSee: https://github.com/${repo}/pull/${githubId}`);
}

View File

@@ -8,8 +8,8 @@ import app from '../../common/app';
* punctuateSeries(['Toby', 'Franz', 'Dominion']) // Toby, Franz, and Dominion
* ```
*
* @param {Array} items
* @return {VirtualElement}
* @param {import('mithril').Children[]} items
* @return {import('mithril').Children}')}
*/
export default function punctuateSeries(items) {
if (items.length === 2) {

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