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>
This commit is contained in:
David Wheatley 2021-12-27 19:58:18 +01:00 committed by GitHub
parent 4df72e5ac6
commit d268894e61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 292 additions and 355 deletions

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

@ -451,9 +451,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

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

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

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

View File

@ -23,7 +23,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

@ -73,7 +73,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 {
return !!this.modal;

View File

@ -9,9 +9,8 @@ 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, data = {}) {

View File

@ -63,7 +63,6 @@ export default class Drawer {
* Check whether or not the drawer is currently open.
*
* @return {boolean}
* @public
*/
isOpen() {
return this.appElement.classList.contains('drawerOpen');
@ -71,8 +70,6 @@ export default class Drawer {
/**
* Hide the drawer.
*
* @public
*/
hide() {
/**
@ -100,8 +97,6 @@ export default class Drawer {
/**
* Show the drawer.
*
* @public
*/
show() {
this.appElement.classList.add('drawerOpen');

View File

@ -303,7 +303,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

@ -1,11 +1,3 @@
const later =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
((callback) => window.setTimeout(callback, 1000 / 60));
/**
* The `ScrollListener` class sets up a listener that handles window scroll
* events.
@ -14,7 +6,6 @@ export default class ScrollListener {
/**
* @param {(top: number) => void} callback The callback to run when the scroll position
* changes.
* @public
*/
constructor(callback) {
this.callback = callback;
@ -34,7 +25,7 @@ export default class ScrollListener {
// Schedule the callback to be executed soon (TM), and stop throttling once
// the callback is done.
later(() => {
requestAnimationFrame(() => {
this.update();
this.ticking = false;
});
@ -44,8 +35,6 @@ export default class ScrollListener {
/**
* Run the callback, whether there was a scroll event or not.
*
* @public
*/
update() {
this.callback(window.pageYOffset);
@ -53,8 +42,6 @@ export default class ScrollListener {
/**
* Start listening to and handling the window's scroll position.
*
* @public
*/
start() {
if (!this.active) {
@ -64,8 +51,6 @@ export default class ScrollListener {
/**
* Stop listening to and handling the window's scroll position.
*
* @public
*/
stop() {
window.removeEventListener('scroll', this.active);

View File

@ -8,8 +8,8 @@
* 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, callback) {
const $window = $(window);

View File

@ -4,10 +4,9 @@ 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 {
const keys = args.slice(0, -1) as string[];

View File

@ -13,17 +13,21 @@ export default {
/**
* Arrays of registered event handlers, grouped by the event name.
*
* @type {Object}
* @type {Record<string, unknown>}
* @protected
*
* @deprecated
*/
handlers: null,
/**
* 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
*/
getHandlers(event) {
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
@ -38,9 +42,10 @@ export default {
/**
* 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
*/
trigger(event, ...args) {
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
@ -51,8 +56,10 @@ export default {
/**
* 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
*/
on(event, handler) {
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
@ -64,8 +71,10 @@ export default {
* 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
*/
one(event, handler) {
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
@ -82,8 +91,10 @@ export default {
/**
* 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
*/
off(event, handler) {
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);

View File

@ -5,9 +5,9 @@
* @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, ...mixins) {
class Mixed extends Parent {}

View File

@ -69,7 +69,7 @@ export default class AvatarEditor extends Component {
/**
* Get the items in the edit avatar dropdown menu.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
controlItems() {
const items = new ItemList();
@ -94,7 +94,7 @@ export default class AvatarEditor extends Component {
/**
* Enable dragover style
*
* @param {Event} e
* @param {DragEvent} e
*/
enableDragover(e) {
e.preventDefault();
@ -105,7 +105,7 @@ export default class AvatarEditor extends Component {
/**
* Disable dragover style
*
* @param {Event} e
* @param {DragEvent} e
*/
disableDragover(e) {
e.preventDefault();
@ -116,7 +116,7 @@ export default class AvatarEditor extends Component {
/**
* Upload avatar when file is dropped into dropzone.
*
* @param {Event} e
* @param {DragEvent} e
*/
dropUpload(e) {
e.preventDefault();
@ -131,7 +131,7 @@ export default class AvatarEditor extends Component {
* 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) {
if (!this.attrs.user.avatarUrl()) {
@ -206,7 +206,7 @@ export default class AvatarEditor extends Component {
* 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
*/
success(response) {
@ -220,7 +220,7 @@ export default class AvatarEditor extends Component {
/**
* If avatar upload/removal fails, stop loading.
*
* @param {Object} response
* @param {object} response
* @protected
*/
failure(response) {

View File

@ -120,7 +120,7 @@ export default class CommentPost extends Post {
/**
* Build an item list for the post's header.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
headerItems() {
const items = new ItemList();

View File

@ -127,7 +127,7 @@ export default class Composer extends Component {
/**
* Resize the composer according to mouse movement.
*
* @param {Event} e
* @param {MouseEvent} e
*/
onmousemove(e) {
if (!this.handle) return;
@ -317,7 +317,7 @@ export default class Composer extends Component {
/**
* Build an item list for the composer's controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
controlItems() {
const items = new ItemList();
@ -379,7 +379,7 @@ export default class Composer extends Component {
/**
* Default height of the Composer in case none is saved.
* @returns {Integer}
* @returns {number}
*/
defaultHeight() {
return this.$().height();
@ -387,7 +387,7 @@ export default class Composer extends Component {
/**
* Save a new Composer height and update the DOM.
* @param {Integer} height
* @param {number} height
*/
changeHeight(height) {
this.state.height = height;

View File

@ -76,7 +76,7 @@ export default class ComposerBody extends Component {
/**
* Check if there is any unsaved data.
*
* @return {String}
* @return {boolean}
*/
hasChanges() {
const content = this.composer.fields.content();
@ -87,7 +87,7 @@ export default class ComposerBody extends Component {
/**
* Build an item list for the composer's header.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
headerItems() {
return new ItemList();

View File

@ -63,7 +63,7 @@ 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) {
if (e.which === 13) {
@ -82,7 +82,7 @@ export default class DiscussionComposer extends ComposerBody {
/**
* Get the data to submit to the server when the discussion is saved.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
data() {
return {

View File

@ -23,7 +23,7 @@ export default class DiscussionHero extends Component {
/**
* Build an item list for the contents of the discussion hero.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
items() {
const items = new ItemList();

View File

@ -143,7 +143,7 @@ export default class DiscussionListItem extends Component {
/**
* Determine whether or not the discussion is currently being viewed.
*
* @return {Boolean}
* @return {boolean}
*/
active() {
return app.current.matches(DiscussionPage, { discussion: this.attrs.discussion });
@ -154,7 +154,7 @@ export default class DiscussionListItem extends Component {
* should be displayed instead of information about the most recent reply to
* the discussion.
*
* @return {Boolean}
* @return {boolean}
*/
showFirstPost() {
return ['newest', 'oldest'].indexOf(this.attrs.params.sort) !== -1;
@ -164,7 +164,7 @@ export default class DiscussionListItem extends Component {
* Determine whether or not the number of replies should be shown instead of
* the number of unread posts.
*
* @return {Boolean}
* @return {boolean}
*/
showRepliesCount() {
return this.attrs.params.sort === 'replies';
@ -186,7 +186,7 @@ export default class DiscussionListItem extends Component {
* 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() {
const items = new ItemList();

View File

@ -70,7 +70,7 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
// we'll just close it.
app.pane?.disable();
if (app.composer.composingReplyTo(this.discussion) && !app.composer?.fields?.content()) {
if (this.discussion && app.composer.composingReplyTo(this.discussion) && !app.composer?.fields?.content()) {
app.composer.hide();
} else {
app.composer.minimize();
@ -88,11 +88,9 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* List of components shown while the discussion is loading.
*
* @returns {ItemList}
*/
loadingItems() {
const items = new ItemList();
loadingItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
items.add('spinner', <LoadingIndicator />, 100);
@ -101,10 +99,8 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* Function that renders the `sidebarItems` ItemList.
*
* @returns {import('mithril').Children}
*/
sidebar() {
sidebar(): Mithril.Children {
return (
<nav className="DiscussionPage-nav">
<ul>{listItems(this.sidebarItems().toArray())}</ul>
@ -114,20 +110,16 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* Renders the discussion's hero.
*
* @returns {import('mithril').Children}
*/
hero() {
hero(): Mithril.Children {
return <DiscussionHero discussion={this.discussion} />;
}
/**
* List of items rendered as the main page content.
*
* @returns {ItemList}
*/
pageContent() {
const items = new ItemList();
pageContent(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
items.add('hero', this.hero(), 100);
items.add('main', <div className="container">{this.mainContent().toArray()}</div>, 10);
@ -137,11 +129,9 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* List of items rendered inside the main page content container.
*
* @returns {ItemList}
*/
mainContent() {
const items = new ItemList();
mainContent(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
items.add('sidebar', this.sidebar(), 100);
@ -163,7 +153,7 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* Load the discussion from the API or use the preloaded one.
*/
load() {
load(): void {
const preloadedDiscussion = app.preloadedApiDocument<Discussion>();
if (preloadedDiscussion) {
// We must wrap this in a setTimeout because if we are mounting this
@ -183,10 +173,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() {
requestParams(): Record<string, unknown> {
return {
bySlug: true,
page: { near: this.near },
@ -196,7 +184,7 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
/**
* Initialize the component to display the given discussion.
*/
show(discussion: ApiResponseSingle<Discussion>) {
show(discussion: ApiResponseSingle<Discussion>): void {
app.history.push('discussion', discussion.title());
app.setTitle(discussion.title());
app.setTitleCount(0);
@ -242,21 +230,23 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
* Build an item list for the contents of the sidebar.
*/
sidebarItems() {
const items = new ItemList<Mithril.Vnode>();
const items = new ItemList<Mithril.Children>();
items.add(
'controls',
SplitDropdown.component(
{
icon: 'fas fa-ellipsis-v',
className: 'App-primaryControl',
buttonClassName: 'Button--primary',
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
},
DiscussionControls.controls(this.discussion, this).toArray()
),
100
);
if (this.discussion) {
items.add(
'controls',
SplitDropdown.component(
{
icon: 'fas fa-ellipsis-v',
className: 'App-primaryControl',
buttonClassName: 'Button--primary',
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
},
DiscussionControls.controls(this.discussion, this).toArray()
),
100
);
}
items.add(
'scrubber',

View File

@ -62,7 +62,7 @@ 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() {
return {

View File

@ -45,7 +45,7 @@ export default class EventPost extends Post {
/**
* Get the name of the event icon.
*
* @return {String}
* @return {string}
*/
icon() {
return '';
@ -54,8 +54,8 @@ export default class EventPost extends Post {
/**
* 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) {
return app.translator.trans(this.descriptionKey(), data);
@ -64,7 +64,7 @@ export default class EventPost extends Post {
/**
* Get the translation key for the description of the event.
*
* @return {String}
* @return {string}
*/
descriptionKey() {
return '';
@ -73,7 +73,7 @@ export default class EventPost extends Post {
/**
* Get the translation data for the description of the event.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
descriptionData() {
return {};

View File

@ -14,7 +14,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

@ -133,7 +133,7 @@ export default class IndexPage extends Page {
/**
* Get the component to display as the hero.
*
* @return {MithrilComponent}
* @return {import('mithril').Children}
*/
hero() {
return WelcomeHero.component();
@ -144,7 +144,7 @@ export default class IndexPage extends Page {
* "New Discussion" button, and then a DropdownSelect component containing a
* list of navigation items.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
sidebarItems() {
const items = new ItemList();
@ -187,7 +187,7 @@ export default class IndexPage extends Page {
* 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() {
const items = new ItemList();
@ -213,7 +213,7 @@ export default class IndexPage extends Page {
* 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() {
const items = new ItemList();
@ -255,7 +255,7 @@ export default class IndexPage extends Page {
* 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() {
const items = new ItemList();
@ -294,7 +294,7 @@ export default class IndexPage extends Page {
/**
* Open the composer for a new discussion or prompt the user to login.
*
* @return {Promise}
* @return {Promise<void>}
*/
newDiscussionAction() {
return new Promise((resolve, reject) => {
@ -313,8 +313,6 @@ export default class IndexPage extends Page {
/**
* Mark all discussions as read.
*
* @return void
*/
markAllAsRead() {
const confirmation = confirm(app.translator.trans('core.forum.index.mark_all_as_read_confirmation'));

View File

@ -12,8 +12,7 @@ export default class LogInButtons extends Component {
/**
* Build a list of LogInButton components.
*
* @return {ItemList}
* @public
* @return {ItemList<import('mithril').Children>}
*/
items() {
return new ItemList();

View File

@ -57,7 +57,7 @@ export default class Notification extends Component {
/**
* Get the name of the icon that should be displayed in the notification.
*
* @return {String}
* @return {string}
* @abstract
*/
icon() {}
@ -65,7 +65,7 @@ export default class Notification extends Component {
/**
* Get the URL that the notification should link to.
*
* @return {String}
* @return {string}
* @abstract
*/
href() {}
@ -73,7 +73,7 @@ export default class Notification extends Component {
/**
* Get the content of the notification.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
* @abstract
*/
content() {}
@ -81,7 +81,7 @@ export default class Notification extends Component {
/**
* Get the excerpt of the notification.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
* @abstract
*/
excerpt() {}

View File

@ -19,21 +19,21 @@ export default class NotificationGrid extends Component {
/**
* Information about the available notification methods.
*
* @type {Array}
* @type {({ name: string, icon: string, label: import('mithril').Children })[]}
*/
this.methods = this.notificationMethods().toArray();
/**
* A map of which notification checkboxes are loading.
*
* @type {Object}
* @type {Record<string, boolean>}
*/
this.loading = {};
/**
* Information about the available notification types.
*
* @type {Array}
* @type {({ name: string, icon: string, label: import('mithril').Children })[]}
*/
this.types = this.notificationTypes().toArray();
}
@ -104,7 +104,7 @@ export default class NotificationGrid extends Component {
* Toggle the state of the given preferences, based on the value of the first
* one.
*
* @param {Array} keys
* @param {string[]} keys
*/
toggle(keys) {
const user = this.attrs.user;
@ -128,7 +128,7 @@ export default class NotificationGrid extends Component {
/**
* Toggle all notification types for the given method.
*
* @param {String} method
* @param {string} method
*/
toggleMethod(method) {
const keys = this.types.map((type) => this.preferenceKey(type.name, method)).filter((key) => key in this.attrs.user.preferences());
@ -139,7 +139,7 @@ export default class NotificationGrid extends Component {
/**
* Toggle all notification methods for the given type.
*
* @param {String} type
* @param {string} type
*/
toggleType(type) {
const keys = this.methods.map((method) => this.preferenceKey(type, method.name)).filter((key) => key in this.attrs.user.preferences());
@ -151,9 +151,9 @@ export default class NotificationGrid extends Component {
* 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, method) {
return 'notify_' + type + '_' + method;
@ -168,7 +168,7 @@ export default class NotificationGrid extends Component {
* - `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() {
const items = new ItemList();
@ -197,7 +197,7 @@ export default class NotificationGrid extends Component {
* - `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() {
const items = new ItemList();

View File

@ -102,7 +102,7 @@ export default class Post extends Component {
/**
* Get attributes for the post element.
*
* @return {Object}
* @return {Record<string, unknown>}
*/
elementAttrs() {
return {};
@ -111,16 +111,16 @@ export default class Post extends Component {
/**
* Get the post's content.
*
* @return {Array}
* @return {import('mithril').Children}
*/
content() {
return [];
return null;
}
/**
* Get the post's classes.
*
* @param existing string
* @param {string} existing
* @returns {string[]}
*/
classes(existing) {
@ -137,7 +137,7 @@ export default class Post extends Component {
classes.push('Post--by-actor');
}
if (user && user.id() === discussion.attribute('startUserId')) {
if (user?.id() === discussion.attribute('startUserId')) {
classes.push('Post--by-start-user');
}
@ -147,7 +147,7 @@ export default class Post extends Component {
/**
* Build an item list for the post's actions.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
actionItems() {
return new ItemList();
@ -156,7 +156,7 @@ export default class Post extends Component {
/**
* Build an item list for the post's footer.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
footerItems() {
return new ItemList();

View File

@ -51,8 +51,8 @@ export default class PostMeta extends Component {
/**
* Get the permalink for the given post.
*
* @param {Post} post
* @returns {String}
* @param {import('../../common/models/Post').default} post
* @returns {string}
*/
getPermalink(post) {
return app.forum.attribute('baseUrl') + app.route.post(post);

View File

@ -148,7 +148,7 @@ export default class PostStream extends Component {
/**
*
* @param {Integer} top
* @param {number} top
*/
onscroll(top = window.pageYOffset) {
if (this.stream.paused || this.stream.pagesLoading) return;
@ -167,7 +167,7 @@ export default class PostStream extends Component {
* 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 = window.pageYOffset) {
const marginTop = this.getMarginTop();
@ -298,7 +298,7 @@ export default class PostStream extends 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() {
const headerId = app.screen() === 'phone' ? '#app-navigation' : '#header';
@ -309,9 +309,9 @@ export default class PostStream extends Component {
/**
* 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, animate) {
const $item = this.$(`.PostStream-item[data-number=${number}]`);
@ -322,10 +322,10 @@ export default class PostStream extends Component {
/**
* 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, animate, reply) {
const $item = reply ? $('.PostStream-item:last-child') : this.$(`.PostStream-item[data-index=${index}]`);
@ -340,12 +340,12 @@ export default class PostStream extends Component {
/**
* 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, animate, force, reply) {
const $container = $('html, body').stop(true);
@ -418,7 +418,7 @@ export default class PostStream extends Component {
/**
* 'Flash' the given post, drawing the user's attention to it.
*
* @param {jQuery} $item
* @param {JQuery} $item
*/
flashItem($item) {
// This might execute before the fadeIn class has been removed in PostStreamItem's

View File

@ -158,7 +158,7 @@ export default class PostStreamScrubber extends Component {
* 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 = {}) {
const index = this.stream.index;
@ -301,7 +301,7 @@ export default class PostStreamScrubber extends Component {
* 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

View File

@ -38,7 +38,7 @@ export default class PostsUserPage extends UserPage {
/**
* The number of activity items to load per request.
*
* @type {Integer}
* @type {number}
*/
this.loadLimit = 20;
@ -100,8 +100,6 @@ export default class PostsUserPage extends UserPage {
/**
* Clear and reload the user's activity feed.
*
* @public
*/
refresh() {
this.loading = true;
@ -115,8 +113,8 @@ export default class PostsUserPage extends UserPage {
/**
* 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
*/
loadResults(offset) {
@ -132,8 +130,6 @@ export default class PostsUserPage extends UserPage {
/**
* Load the next page of results.
*
* @public
*/
loadMore() {
this.loading = true;
@ -143,13 +139,13 @@ export default class PostsUserPage extends UserPage {
/**
* 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) {
this.loading = false;
[].push.apply(this.posts, results);
this.posts.push(results);
this.moreResults = results.length >= this.loadLimit;
m.redraw();

View File

@ -59,7 +59,7 @@ 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() {
return {

View File

@ -35,7 +35,7 @@ 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() {
const items = new ItemList();

View File

@ -33,7 +33,7 @@ export default class SettingsPage extends UserPage {
/**
* Build an item list for the user's settings controls.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
settingsItems() {
const items = new ItemList();
@ -53,7 +53,7 @@ export default class SettingsPage extends UserPage {
/**
* Build an item list for the user's account settings.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
accountItems() {
const items = new ItemList();
@ -78,7 +78,7 @@ export default class SettingsPage extends UserPage {
/**
* Build an item list for the user's notification settings.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
notificationsItems() {
const items = new ItemList();
@ -91,7 +91,7 @@ export default class SettingsPage extends UserPage {
/**
* Build an item list for the user's privacy settings.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
privacyItems() {
const items = new ItemList();

View File

@ -145,8 +145,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() {
const attrs = {

View File

@ -73,7 +73,7 @@ export default class UserCard extends Component {
/**
* Build an item list of tidbits of info to show on this user's profile.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
infoItems() {
const items = new ItemList();

View File

@ -30,6 +30,11 @@ export default class UserPage extends Page {
this.bodyClass = 'App--user';
}
/**
* Base view template for the user page.
*
* @return {import('mithril').Children}
*/
view() {
return (
<div className="UserPage">
@ -60,7 +65,7 @@ export default class UserPage extends Page {
/**
* Get the content to display in the user page.
*
* @return {VirtualElement}
* @return {import('mithril').Children}
*/
content() {}
@ -68,7 +73,7 @@ export default class UserPage extends Page {
* 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
*/
show(user) {
@ -85,7 +90,7 @@ export default class UserPage extends Page {
* 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) {
const lowercaseUsername = username.toLowerCase();
@ -110,7 +115,7 @@ export default class UserPage extends Page {
/**
* Build an item list for the content of the sidebar.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
sidebarItems() {
const items = new ItemList();
@ -128,7 +133,7 @@ export default class UserPage extends Page {
/**
* Build an item list for the navigation in the sidebar.
*
* @return {ItemList}
* @return {ItemList<import('mithril').Children>}
*/
navItems() {
const items = new ItemList();

View File

@ -17,7 +17,7 @@ class ComposerState {
* The composer's intended height, which can be modified by the user
* (by dragging the composer handle).
*
* @type {Integer}
* @type {number}
*/
this.height = null;
@ -41,8 +41,7 @@ class ComposerState {
/**
* Load a content component into the composer.
*
* @param {ComposerBody} componentClass
* @public
* @param {typeof import('../components/ComposerBody').default} componentClass
*/
load(componentClass, attrs) {
const body = { componentClass, attrs };
@ -82,8 +81,6 @@ class ComposerState {
/**
* Show the composer.
*
* @public
*/
show() {
if (this.position === ComposerState.Position.NORMAL || this.position === ComposerState.Position.FULLSCREEN) return;
@ -94,8 +91,6 @@ class ComposerState {
/**
* Close the composer.
*
* @public
*/
hide() {
this.clear();
@ -105,8 +100,6 @@ class ComposerState {
/**
* Confirm with the user so they don't lose their content, then close the
* composer.
*
* @public
*/
close() {
if (this.preventExit()) return;
@ -116,8 +109,6 @@ class ComposerState {
/**
* Minimize the composer. Has no effect if the composer is hidden.
*
* @public
*/
minimize() {
if (!this.isVisible()) return;
@ -129,8 +120,6 @@ class ComposerState {
/**
* Take the composer into fullscreen mode. Has no effect if the composer is
* hidden.
*
* @public
*/
fullScreen() {
if (!this.isVisible()) return;
@ -141,8 +130,6 @@ class ComposerState {
/**
* Exit fullscreen mode.
*
* @public
*/
exitFullScreen() {
if (this.position !== ComposerState.Position.FULLSCREEN) return;
@ -154,8 +141,7 @@ class ComposerState {
/**
* 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}
*/
@ -186,8 +172,7 @@ 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}
*/
isFullScreen() {
return this.position === ComposerState.Position.FULLSCREEN || app.screen() === 'phone';
@ -197,8 +182,8 @@ class ComposerState {
* 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) {
return this.isVisible() && this.bodyMatches(ReplyComposer, { discussion });
@ -208,7 +193,7 @@ class ComposerState {
* 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() {
if (!this.isVisible()) return;
@ -226,8 +211,8 @@ 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, message) {
this.onExit = { callback, message };
@ -235,7 +220,7 @@ class ComposerState {
/**
* Minimum height of the Composer.
* @returns {Integer}
* @returns {number}
*/
minimumHeight() {
return 200;
@ -243,7 +228,7 @@ class ComposerState {
/**
* Maxmimum height of the Composer.
* @returns {Integer}
* @returns {number}
*/
maximumHeight() {
return $(window).height() - $('#header').outerHeight();
@ -251,9 +236,9 @@ class ComposerState {
/**
* 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() {
// If the composer is minimized, then we don't want to set a height; we'll

View File

@ -60,8 +60,6 @@ class PostStreamState {
/**
* Update the stream so that it loads and includes the latest posts in the
* discussion, if the end is being viewed.
*
* @public
*/
update() {
if (!this.viewingEnd()) return Promise.resolve();
@ -74,7 +72,7 @@ class PostStreamState {
/**
* Load and scroll up to the first post in the discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
goToFirst() {
return this.goToIndex(0);
@ -83,7 +81,7 @@ class PostStreamState {
/**
* Load and scroll down to the last post in the discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
goToLast() {
return this.goToIndex(this.count() - 1, true);
@ -92,10 +90,9 @@ class PostStreamState {
/**
* 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, noAnimation = false) {
// If we want to go to the reply preview, then we will go to the end of the
@ -127,8 +124,8 @@ 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, noAnimation = false) {
this.paused = true;
@ -151,7 +148,7 @@ class PostStreamState {
* resolved immediately.
*
* @param {number} number
* @return {Promise}
* @return {Promise<void>}
*/
loadNearNumber(number) {
if (this.posts().some((post) => post && Number(post.number()) === Number(number))) {
@ -174,7 +171,7 @@ class PostStreamState {
* index is already loaded, the promise will be resolved immediately.
*
* @param {number} index
* @return {Promise}
* @return {Promise<void>}
*/
loadNearIndex(index) {
if (index >= this.visibleStart && index < this.visibleEnd) {
@ -240,7 +237,7 @@ class PostStreamState {
*
* @param {number} start
* @param {number} end
* @param {Boolean} backwards
* @param {boolean} backwards
*/
loadPage(start, end, backwards = false) {
this.pagesLoading++;
@ -271,7 +268,7 @@ class PostStreamState {
*
* @param {number} start
* @param {number} end
* @return {Promise}
* @return {Promise<void>}
*/
loadRange(start, end) {
const loadIds = [];
@ -302,7 +299,7 @@ class PostStreamState {
/**
* Set up the stream with the given array of posts.
*
* @param {Post[]} posts
* @param {import('../../common/models/Post').default[]} posts
*/
show(posts) {
this.visibleStart = posts.length ? this.discussion.postIds().indexOf(posts[0].id()) : 0;
@ -350,7 +347,7 @@ 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() {
return this.visible >= this.count();

View File

@ -16,11 +16,10 @@ export 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>}
*/
controls(discussion, context) {
const items = new ItemList();
@ -40,10 +39,10 @@ export default {
* 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
*/
userControls(discussion, context) {
@ -88,10 +87,10 @@ export default {
/**
* 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
*/
moderationControls(discussion) {
@ -116,10 +115,10 @@ export default {
/**
* 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
*/
destructiveControls(discussion) {
@ -175,11 +174,10 @@ export default {
* 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>}
*/
replyAction(goToLast, forceRefresh) {
return new Promise((resolve, reject) => {
@ -212,7 +210,7 @@ export default {
/**
* Hide a discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
hideAction() {
this.pushAttributes({ hiddenAt: new Date(), hiddenUser: app.session.user });
@ -223,7 +221,7 @@ export default {
/**
* Restore a discussion.
*
* @return {Promise}
* @return {Promise<void>}
*/
restoreAction() {
this.pushAttributes({ hiddenAt: null, hiddenUser: null });
@ -234,7 +232,7 @@ export default {
/**
* Delete the discussion after confirming with the user.
*
* @return {Promise}
* @return {Promise<void>}
*/
deleteAction() {
if (confirm(extractText(app.translator.trans('core.forum.discussion_controls.delete_confirmation')))) {
@ -250,8 +248,6 @@ export default {
/**
* Rename the discussion.
*
* @return {Promise}
*/
renameAction() {
return app.modal.show(RenameDiscussionModal, {

View File

@ -52,8 +52,6 @@ export default class Pane {
/**
* Enable the pane.
*
* @public
*/
enable() {
this.active = true;
@ -62,8 +60,6 @@ export default class Pane {
/**
* Disable the pane.
*
* @public
*/
disable() {
this.active = false;
@ -73,8 +69,6 @@ export default class Pane {
/**
* Show the pane.
*
* @public
*/
show() {
clearTimeout(this.hideTimeout);
@ -84,8 +78,6 @@ export default class Pane {
/**
* Hide the pane.
*
* @public
*/
hide() {
this.showing = false;
@ -95,8 +87,6 @@ export default class Pane {
/**
* Begin a timeout to hide the pane, which can be cancelled by showing the
* pane.
*
* @public
*/
onmouseleave() {
this.hideTimeout = setTimeout(this.hide.bind(this), 250);
@ -104,8 +94,6 @@ export default class Pane {
/**
* Toggle whether or not the pane is pinned.
*
* @public
*/
togglePinned() {
this.pinned = !this.pinned;

View File

@ -13,11 +13,10 @@ export 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>}')}
*/
controls(post, context) {
const items = new ItemList();
@ -36,10 +35,10 @@ export default {
/**
* 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
*/
userControls(post, context) {
@ -49,10 +48,10 @@ export default {
/**
* 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
*/
moderationControls(post, context) {
@ -79,10 +78,10 @@ export default {
/**
* 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
*/
destructiveControls(post, context) {
@ -134,7 +133,7 @@ export default {
/**
* Open the composer to edit a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
editAction() {
return new Promise((resolve) => {
@ -148,7 +147,7 @@ export default {
/**
* Hide a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
hideAction() {
if (!confirm(extractText(app.translator.trans('core.forum.post_controls.hide_confirmation')))) return;
@ -160,7 +159,7 @@ export default {
/**
* Restore a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
restoreAction() {
this.pushAttributes({ hiddenAt: null, hiddenUser: null });
@ -171,7 +170,7 @@ export default {
/**
* Delete a post.
*
* @return {Promise}
* @return {Promise<void>}
*/
deleteAction(context) {
if (!confirm(extractText(app.translator.trans('core.forum.post_controls.delete_confirmation')))) return;

View File

@ -13,11 +13,10 @@ export 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>}
*/
controls(user, context) {
const items = new ItemList();
@ -36,10 +35,10 @@ export default {
/**
* 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
*/
userControls() {
@ -49,10 +48,10 @@ export default {
/**
* 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
*/
moderationControls(user) {
@ -73,10 +72,10 @@ export default {
/**
* 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
*/
destructiveControls(user) {
@ -97,7 +96,7 @@ export default {
/**
* Delete the user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
*/
deleteAction(user) {
if (!confirm(app.translator.trans('core.forum.user_controls.delete_confirmation'))) {
@ -120,7 +119,7 @@ export default {
/**
* Show deletion alert of user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
* @param {string} type
*/
showDeletionAlert(user, type) {
@ -141,7 +140,7 @@ export default {
/**
* Edit the user.
*
* @param {User} user
* @param {import('../../common/models/User').default} user
*/
editAction(user) {
app.modal.show(EditUserModal, { user });

View File

@ -6,7 +6,7 @@ import Component from '../../common/Component';
/**
* 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) {
const user = app.session.user;

View File

@ -4,12 +4,13 @@
* 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) {
const $element = $(element);
@ -27,15 +28,15 @@ export default function slidable(element) {
/**
* Animate the slider to a new position.
*
* @param {Integer} newPos
* @param {Object} [options]
* @param {number} newPos
* @param {Partial<JQueryAnimationOptions>} [options]
*/
const animatePos = (newPos, options = {}) => {
// Since we can't animate the transform property with jQuery, we'll use a
// bit of a workaround. We set up the animation with a step function that
// will set the transform property, but then we animate an unused property
// (background-position-x) with jQuery.
options.duration = options.duration || 'fast';
options.duration ||= 'fast';
options.step = function (x) {
$(this).css('transform', 'translate(' + x + 'px, 0)');
};