1
0
mirror of https://github.com/flarum/core.git synced 2025-08-16 05:14:20 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Skvortsov
efa9d38d73 Throw exceptions in API Client responses
Currently, the API client middleware includes an error handler instance in its middleware stack, so any exceptions thrown have to be manually checked for in API client callers. This is generally forgotten or omited, and leads to issues when call sites try to read data from the response but fail with a confusing error.

In this PR, we no longer handle those errors, so they will be propogated in their original form to the original request's error handler. This is more appropriate behavior, and will make debugging errors significantly easier. This is not a breaking change, since broken requests would have failed anyway due to other, more confusing errors. Additionally, all error checking code that I've found just throws a new error if an API client request fails, so that case won't be broken either.
2021-08-23 12:44:22 -04:00
423 changed files with 17734 additions and 15618 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: flarum
open_collective: flarum

24
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,24 @@
<!--
IMPORTANT: We applaud pull requests, they excite us every single time. As we have an obligation to maintain a healthy code standard and quality, we take sufficient time for reviews. Please do create a separate pull request per change/issue/feature; we will ask you to split bundled pull requests.
-->
**Fixes #0000**
**Changes proposed in this pull request:**
<!-- fill this out, mention the pages and/or components which have been impacted -->
**Reviewers should focus on:**
<!-- fill this out, ask for feedback on specific changes you are unsure about -->
**Screenshot**
<!-- include an image of the most relevant user-facing change, if any -->
**Confirmed**
- [ ] Frontend changes: tested on a local Flarum installation.
- [ ] Backend changes: tests are green (run `composer test`).
**Required changes:**
- [ ] Related documentation PR: (Remove if irrelevant)
- [ ] Related core extension PRs: (Remove if irrelevant)

13
.github/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,13 @@
# Security Policy
## Versions
Due to the nature of our project - being open source - we have decided to patch only the latest major release (currently v1.x) for security vulnerabilities.
## How to disclose
Please use [huntr.dev](https://huntr.dev/) for security issues that affect our project. If you believe you have found a vulnerability, please disclose it via [this form](https://huntr.dev/bounties/disclose/?target=https://github.com/flarum/core).
This will enable us to **review** the vulnerability, **fix** it promptly, and **reward** you for your efforts.
If you have any questions about the process, feel free to reach out to security@huntr.dev or security@flarum.org.

View File

@@ -18,67 +18,21 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: "yarn" cache: "npm"
cache-dependency-path: js/yarn.lock cache-dependency-path: js/package-lock.json
- name: Install JS dependencies - name: Install JS dependencies
run: yarn install --immutable run: npm ci
working-directory: ./js working-directory: ./js
- name: Check JS formatting - name: Check JS formatting
run: yarn run format-check run: npm run format-check
working-directory: ./js
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: "yarn"
cache-dependency-path: js/yarn.lock
- name: Install JS dependencies
run: yarn --frozen-lockfile
working-directory: ./js
- name: Typecheck
run: yarn run check-typings
working-directory: ./js
type-coverage:
name: Type Coverage
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: "yarn"
cache-dependency-path: js/yarn.lock
- name: Install JS dependencies
run: yarn --frozen-lockfile
working-directory: ./js
- name: Check type coverage
run: yarn run check-typings-coverage
working-directory: ./js working-directory: ./js
build-prod: build-prod:
name: Build and commit name: Build and commit
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [prettier, typecheck, type-coverage] needs: [prettier]
# Only commit JS on push to master branch # Only commit JS on push to master branch
# Remember to change in `build-test` job too # Remember to change in `build-test` job too
@@ -92,8 +46,8 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: "yarn" cache: "npm"
cache-dependency-path: js/yarn.lock cache-dependency-path: js/package-lock.json
# Our action will install npm, cd into `./js`, run `npm run build` and # Our action will install npm, cd into `./js`, run `npm run build` and
# `npm run build-typings`, then commit and upload any changes # `npm run build-typings`, then commit and upload any changes
@@ -102,13 +56,13 @@ jobs:
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build build_script: build
package_manager: yarn package_manager: npm
typings_script: build-typings typings_script: build-typings
build-test: build-test:
name: Test build name: Test build
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [prettier, typecheck, type-coverage] needs: [prettier]
# Inverse check of `build-prod` # Inverse check of `build-prod`
# Remember to change in `build-prod` job too # Remember to change in `build-prod` job too
@@ -122,8 +76,8 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: "yarn" cache: "npm"
cache-dependency-path: js/yarn.lock cache-dependency-path: js/package-lock.json
# Our action will install npm, cd into `./js`, run `npm run build` and # Our action will install npm, cd into `./js`, run `npm run build` and
# `npm run build-typings`, then commit and upload any changes # `npm run build-typings`, then commit and upload any changes
@@ -132,6 +86,6 @@ jobs:
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build build_script: build
package_manager: yarn package_manager: npm
typings_script: build-typings typings_script: build-typings
do_not_commit: true do_not_commit: true

View File

@@ -8,7 +8,7 @@ jobs:
strategy: strategy:
matrix: matrix:
php: [7.3, 7.4, '8.0', '8.1'] php: [7.3, 7.4, '8.0']
service: ['mysql:5.7', mariadb] service: ['mysql:5.7', mariadb]
prefix: ['', flarum_] prefix: ['', flarum_]

2
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/vendor /vendor
composer.lock composer.lock
composer.phar composer.phar
node_modules
.DS_Store .DS_Store
Thumbs.db Thumbs.db
tests/.phpunit.result.cache tests/.phpunit.result.cache
@@ -8,4 +9,3 @@ tests/.phpunit.result.cache
.vagrant .vagrant
.idea/* .idea/*
.vscode .vscode
js/coverage-ts

View File

@@ -1,60 +1,5 @@
# Changelog # Changelog
## [1.1.0](https://github.com/flarum/core/compare/v1.0.4...v1.1.0)
### Added
- Info command now displays MySQL version, queue driver, mail driver (https://github.com/flarum/core/pull/2991)
- Use organization Prettier config (https://github.com/flarum/core/pull/2967)
- Support for global typings in extensions (https://github.com/flarum/core/pull/2992)
- Typings for class component state attribute (https://github.com/flarum/core/pull/2995)
- Custom colorising with CSS custom properties (https://github.com/flarum/core/pull/3001)
- Theme Extender to allow overriding LESS files (https://github.com/flarum/core/pull/3008)
- Update lastSeenAt when authenticating via API (https://github.com/flarum/core/pull/3058)
- NoJs Admin View (https://github.com/flarum/core/pull/3059)
- Preload FontAwesome, JS and CSS, and add `preload` extender (https://github.com/flarum/core/pull/3057)
### Changed
- Move Day.js plugin types import to global typings (https://github.com/flarum/core/pull/2954)
- Avoid resolving excluded middleware on each middleware items
- Allow extra attrs provided to `<Select>` to be passed through to the DOM element (https://github.com/flarum/core/pull/2959)
- Limit height of code blocks (https://github.com/flarum/core/pull/3012)
- Update normalize.css from v3.0.2 to v8.0.1 (https://github.com/flarum/core/pull/3015)
- Permission Grid: stick the headers to handle a lot of tags (https://github.com/flarum/core/pull/2887)
- Use `ItemList` for `DiscussionPage` content (https://github.com/flarum/core/pull/3004)
- Move email confirmation to POST request (https://github.com/flarum/core/pull/3038)
- Minor CSS code cleanup (https://github.com/flarum/core/pull/3026)
- Replace username with display name in more places (https://github.com/flarum/core/pull/3040)
- Rewrite Button to Typescript (https://github.com/flarum/core/pull/2984)
- Rewrite AdminPage abstract component into Typescript (https://github.com/flarum/core/pull/2996)
- Allow adding page parameters to PaginatedListState (https://github.com/flarum/core/pull/2935)
- Pass filter params to getApiDocument (https://github.com/flarum/core/pull/3037)
- Use author filter instead of gambit to get a user's discussions (https://github.com/flarum/core/pull/3068)
- [A11Y] Accessibility improvements for the Search component (https://github.com/flarum/core/pull/3017)
- Add determinsm to extension order resolution (https://github.com/flarum/core/pull/3076)
- Add cache control headers to the admin area (https://github.com/flarum/core/pull/3097)
### Fixed
- HLJS 11 new styles resulting in double padding (https://github.com/flarum/core/pull/2909)
- Internal API client attempting to load an uninstantiated session
- Empty post footer taking visual space (https://github.com/flarum/core/pull/2926)
- Unrecognized component class custom attribute typings (https://github.com/flarum/core/pull/2962)
- User edit groups permission not visually depending on view hidden groups permission (https://github.com/flarum/core/pull/2880)
- Event post excerpt preview triggers error (https://github.com/flarum/core/pull/2964)
- Missing settings defaults for display name driver and User slug driver (https://github.com/flarum/core/pull/2971)
- [A11Y] Icons not hidden from screenreaders (https://github.com/flarum/core/pull/3027)
- [A11Y] Checkboxes not focusable (https://github.com/flarum/core/pull/3014)
- Uploading ICO favicons resulting in server errors (https://github.com/flarum/core/pull/2949)
- Missing proper validation for large avatar upload payload (https://github.com/flarum/core/pull/3042)
- [A11Y] Missing focus rings in control elements (https://github.com/flarum/core/pull/3016)
- Unsanitised integer query parameters (https://github.com/flarum/core/pull/3064)
###### Code Contributors
@lhsazevedo, @Ornanovitch, @pierres, @the-turk, @iPurpl3x
###### Issue Reporters
@uamv, @dannyuk1982, @BurnNoticeSpy, @haarp, @peopleinside, @matteocontrini
## [1.0.4](https://github.com/flarum/core/compare/v1.0.3...v1.0.4) ## [1.0.4](https://github.com/flarum/core/compare/v1.0.3...v1.0.4)
### Fixed ### Fixed

View File

@@ -9,6 +9,7 @@
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a> <a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
</p> </p>
## About Flarum ## 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: **[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:
@@ -19,15 +20,13 @@
* **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarums architecture is amazingly flexible, with a powerful Extension API. * **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 ## 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). 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/). 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).
## Contributing ## Contributing
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://docs.flarum.org/contributing)** to learn how you can help. Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://flarum.org/docs/contributing.html)** to learn how you can help.
## Security Vulnerabilities ## Security Vulnerabilities

9
js/.gitignore vendored
View File

@@ -1,9 +0,0 @@
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
node_modules

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,41 +1,3 @@
declare type Writable<T> = { -readonly [P in keyof T]: T[P] };
declare type DeepWritable<T> = { -readonly [P in keyof T]: DeepWritable<T[P]> };
declare type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> };
/**
* UTILITY TYPES
*/
/**
* Type that returns an array of all keys of a provided object that are of
* of the provided type, or a subtype of the type.
*/
declare type KeysOfType<Type extends object, Match> = {
[Key in keyof Type]-?: Type[Key] extends Match ? Key : never;
};
/**
* Type that matches one of the keys of an object that is of the provided
* type, or a subtype of it.
*/
declare type KeyOfType<Type extends object, Match> = KeysOfType<Type, Match>[keyof Type];
type Component<A> = import('mithril').Component<A>;
declare type ComponentClass<Attrs = Record<string, unknown>, C extends Component<Attrs> = Component<Attrs>> = {
new (...args: any[]): Component<Attrs>;
prototype: C;
};
/**
* Unfortunately, TypeScript only supports strings and classes for JSX tags.
* Therefore, our type definition should only allow for those two types.
*
* @see https://github.com/microsoft/TypeScript/issues/14789#issuecomment-412247771
*/
declare type VnodeElementTag<Attrs = Record<string, unknown>, C extends Component<Attrs> = Component<Attrs>> = string | ComponentClass<Attrs, C>;
/** /**
* @deprecated Please import `app` from a namespace instead of using it as a global variable. * @deprecated Please import `app` from a namespace instead of using it as a global variable.
* *
@@ -59,17 +21,6 @@ declare const app: never;
declare const m: import('mithril').Static; declare const m: import('mithril').Static;
declare const dayjs: typeof import('dayjs'); declare const dayjs: typeof import('dayjs');
/**
* From https://github.com/lokesh/color-thief/issues/188
*/
declare module 'color-thief-browser' {
type Color = [number, number, number];
export default class ColorThief {
getColor: (img: HTMLImageElement | null) => Color;
getPalette: (img: HTMLImageElement | null) => Color[];
}
}
type ESModule = { __esModule: true; [key: string]: unknown }; type ESModule = { __esModule: true; [key: string]: unknown };
/** /**
@@ -129,10 +80,3 @@ interface JSX {
attrs: Record<string, unknown>; attrs: Record<string, unknown>;
}; };
} }
interface Event {
/**
* Whether this event should trigger a Mithril redraw.
*/
redraw: boolean;
}

View File

@@ -1,30 +1,3 @@
import Application from '../common/Application';
import ExtensionData from './utils/ExtensionData';
export declare type Extension = {
id: string;
version: string;
description?: string;
icon?: {
name: string;
};
links: {
authors?: {
name?: string;
link?: string;
}[];
discuss?: string;
documentation?: string;
support?: string;
website?: string;
donate?: string;
source?: string;
};
extra: {
'flarum-extension': {
title: string;
};
};
};
export default class AdminApplication extends Application { export default class AdminApplication extends Application {
extensionData: ExtensionData; extensionData: ExtensionData;
extensionCategories: { extensionCategories: {
@@ -35,27 +8,10 @@ export default class AdminApplication extends Application {
history: { history: {
canGoBack: () => boolean; canGoBack: () => boolean;
getPrevious: () => void; getPrevious: () => void;
backUrl: () => string; backUrl: () => any;
back: () => void; back: () => void;
}; };
/** getRequiredPermissions(permission: any): string[];
* Settings are serialized to the admin dashboard as strings.
* Additional encoding/decoding is possible, but must take
* place on the client side.
*
* @inheritdoc
*/
data: Application['data'] & {
extensions: Record<string, Extension>;
settings: Record<string, string>;
modelStatistics: Record<string, {
total: number;
}>;
};
constructor();
/**
* @inheritdoc
*/
mount(): void;
getRequiredPermissions(permission: string): string[];
} }
import Application from "../common/Application";
import ExtensionData from "./utils/ExtensionData";

View File

@@ -1,5 +1,5 @@
declare var _default: { declare var _default: {
extend: any; extend: typeof import("../common/extend");
Session: typeof import("../common/Session").default; Session: typeof import("../common/Session").default;
Store: typeof import("../common/Store").default; Store: typeof import("../common/Store").default;
'utils/BasicEditorDriver': typeof import("../common/utils/BasicEditorDriver").default; 'utils/BasicEditorDriver': typeof import("../common/utils/BasicEditorDriver").default;
@@ -32,15 +32,17 @@ declare var _default: {
'utils/subclassOf': typeof import("../common/utils/subclassOf").default; 'utils/subclassOf': typeof import("../common/utils/subclassOf").default;
'utils/setRouteWithForcedRefresh': typeof import("../common/utils/setRouteWithForcedRefresh").default; 'utils/setRouteWithForcedRefresh': typeof import("../common/utils/setRouteWithForcedRefresh").default;
'utils/patchMithril': typeof import("../common/utils/patchMithril").default; 'utils/patchMithril': typeof import("../common/utils/patchMithril").default;
'utils/proxifyCompat': typeof import("../common/utils/proxifyCompat").default; 'utils/proxifyCompat': (compat: {
[key: string]: any;
}, namespace: string) => {
[key: string]: any;
};
'utils/classList': (...classes: import("clsx").ClassValue[]) => string; 'utils/classList': (...classes: import("clsx").ClassValue[]) => string;
'utils/extractText': typeof import("../common/utils/extractText").default; 'utils/extractText': typeof import("../common/utils/extractText").default;
'utils/formatNumber': typeof import("../common/utils/formatNumber").default; 'utils/formatNumber': typeof import("../common/utils/formatNumber").default;
'utils/mapRoutes': typeof import("../common/utils/mapRoutes").default; 'utils/mapRoutes': typeof import("../common/utils/mapRoutes").default;
'utils/withAttr': (key: string, cb: Function) => (this: Element) => void; 'utils/withAttr': (key: string, cb: Function) => (this: Element) => void;
'utils/throttleDebounce': typeof import("../common/utils/throttleDebounce"); 'utils/throttleDebounce': typeof import("../common/utils/throttleDebounce");
'utils/isObject': typeof import("../common/utils/isObject").default;
'utils/focusTrap': typeof import("../common/utils/focusTrap");
'models/Notification': typeof import("../common/models/Notification").default; 'models/Notification': typeof import("../common/models/Notification").default;
'models/User': typeof import("../common/models/User").default; 'models/User': typeof import("../common/models/User").default;
'models/Post': typeof import("../common/models/Post").default; 'models/Post': typeof import("../common/models/Post").default;
@@ -67,7 +69,6 @@ declare var _default: {
'components/Link': typeof import("../common/components/Link").default; 'components/Link': typeof import("../common/components/Link").default;
'components/LinkButton': typeof import("../common/components/LinkButton").default; 'components/LinkButton': typeof import("../common/components/LinkButton").default;
'components/Checkbox': typeof import("../common/components/Checkbox").default; 'components/Checkbox': typeof import("../common/components/Checkbox").default;
'components/ColorPreviewInput': typeof import("../common/components/ColorPreviewInput").default;
'components/SelectDropdown': typeof import("../common/components/SelectDropdown").default; 'components/SelectDropdown': typeof import("../common/components/SelectDropdown").default;
'components/ModalManager': typeof import("../common/components/ModalManager").default; 'components/ModalManager': typeof import("../common/components/ModalManager").default;
'components/Button': typeof import("../common/components/Button").default; 'components/Button': typeof import("../common/components/Button").default;

View File

@@ -7,8 +7,8 @@ export default class AdminNav extends Component<import("../../common/Component")
* *
* @return {ItemList} * @return {ItemList}
*/ */
items(): ItemList<any>; items(): ItemList;
extensionItems(): ItemList<any>; extensionItems(): ItemList;
} }
import Component from "../../common/Component"; import Component from "../../common/Component";
import Stream from "../../common/utils/Stream"; import Stream from "../../common/utils/Stream";

View File

@@ -1,167 +0,0 @@
import type Mithril from 'mithril';
import Page, { IPageAttrs } from '../../common/components/Page';
import Stream from '../../common/utils/Stream';
export interface AdminHeaderOptions {
title: Mithril.Children;
description: Mithril.Children;
icon: string;
/**
* Will be used as the class for the AdminPage.
*
* Will also be appended with `-header` and set as the class for the `AdminHeader` component.
*/
className: string;
}
/**
* A type that matches any valid value for the `type` attribute on an HTML `<input>` element.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type
*
* Note: this will be exported from a different location in the future.
*
* @see https://github.com/flarum/core/issues/3039
*/
export declare type HTMLInputTypes = 'button' | 'checkbox' | 'color' | 'date' | 'datetime-local' | 'email' | 'file' | 'hidden' | 'image' | 'month' | 'number' | 'password' | 'radio' | 'range' | 'reset' | 'search' | 'submit' | 'tel' | 'text' | 'time' | 'url' | 'week';
interface CommonSettingsItemOptions extends Mithril.Attributes {
setting: string;
label: Mithril.Children;
help?: Mithril.Children;
className?: string;
}
/**
* Valid options for the setting component builder to generate an HTML input element.
*/
export interface HTMLInputSettingsComponentOptions extends CommonSettingsItemOptions {
/**
* Any valid HTML input `type` value.
*/
type: HTMLInputTypes;
}
declare const BooleanSettingTypes: readonly ["bool", "checkbox", "switch", "boolean"];
declare const SelectSettingTypes: readonly ["select", "dropdown", "selectdropdown"];
declare const TextareaSettingTypes: readonly ["textarea"];
declare const ColorPreviewSettingType = "color-preview";
/**
* Valid options for the setting component builder to generate a Switch.
*/
export interface SwitchSettingComponentOptions extends CommonSettingsItemOptions {
type: typeof BooleanSettingTypes[number];
}
/**
* Valid options for the setting component builder to generate a Select dropdown.
*/
export interface SelectSettingComponentOptions extends CommonSettingsItemOptions {
type: typeof SelectSettingTypes[number];
/**
* Map of values to their labels
*/
options: {
[value: string]: Mithril.Children;
};
default: string;
}
/**
* Valid options for the setting component builder to generate a Textarea.
*/
export interface TextareaSettingComponentOptions extends CommonSettingsItemOptions {
type: typeof TextareaSettingTypes[number];
}
/**
* Valid options for the setting component builder to generate a ColorPreviewInput.
*/
export interface ColorPreviewSettingComponentOptions extends CommonSettingsItemOptions {
type: typeof ColorPreviewSettingType;
}
/**
* All valid options for the setting component builder.
*/
export declare type SettingsComponentOptions = HTMLInputSettingsComponentOptions | SwitchSettingComponentOptions | SelectSettingComponentOptions | TextareaSettingComponentOptions | ColorPreviewSettingComponentOptions;
/**
* Valid attrs that can be returned by the `headerInfo` function
*/
export declare type AdminHeaderAttrs = AdminHeaderOptions & Partial<Omit<Mithril.Attributes, 'class'>>;
export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends Page<CustomAttrs> {
settings: Record<string, Stream<string>>;
loading: boolean;
view(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children;
/**
* Returns the content of the AdminPage.
*/
abstract content(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children;
/**
* Returns the submit button for this AdminPage.
*
* Calls `this.saveSettings` when the button is clicked.
*/
submitButton(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children;
/**
* Returns the Header component for this AdminPage.
*/
header(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children;
/**
* Returns the options passed to the AdminHeader component.
*/
headerInfo(): AdminHeaderAttrs;
/**
* `buildSettingComponent` takes a settings object and turns it into a component.
* Depending on the type of input, you can set the type to 'bool', 'select', or
* any standard <input> type. Any values inside the 'extra' object will be added
* to the component as an attribute.
*
* Alternatively, you can pass a callback that will be executed in ExtensionPage's
* context to include custom JSX elements.
*
* @example
*
* {
* setting: 'acme.checkbox',
* label: app.translator.trans('acme.admin.setting_label'),
* type: 'bool',
* help: app.translator.trans('acme.admin.setting_help'),
* className: 'Setting-item'
* }
*
* @example
*
* {
* setting: 'acme.select',
* label: app.translator.trans('acme.admin.setting_label'),
* type: 'select',
* options: {
* 'option1': 'Option 1 label',
* 'option2': 'Option 2 label',
* },
* default: 'option1',
* }
*
* @example
*
* () => {
* return <p>My cool component</p>;
* }
*/
buildSettingComponent(entry: ((this: this) => Mithril.Children) | SettingsComponentOptions): Mithril.Children;
/**
* Called when `saveSettings` completes successfully.
*/
onsaved(): void;
/**
* Returns a function that fetches the setting from the `app` global.
*/
setting(key: string, fallback?: string): Stream<string>;
/**
* Returns a map of settings keys to values which includes only those which have been modified but not yet saved.
*/
dirty(): Record<string, string>;
/**
* Returns the number of settings that have been modified.
*/
isChanged(): number;
/**
* Saves the modified settings to the database.
*/
saveSettings(e: SubmitEvent & {
redraw: boolean;
}): Promise<void>;
}
export {};

View File

@@ -1,6 +1,4 @@
export default class AppearancePage extends AdminPage<import("../../common/components/Page").IPageAttrs> { export default class AppearancePage extends AdminPage<import("../../common/components/Page").IPageAttrs> {
constructor(); constructor();
colorItems(): ItemList<any>;
} }
import AdminPage from "./AdminPage"; import AdminPage from "./AdminPage";
import ItemList from "../../common/utils/ItemList";

View File

@@ -10,7 +10,7 @@ export default class BasicsPage extends AdminPage<import("../../common/component
* @return {ItemList} * @return {ItemList}
* @public * @public
*/ */
public homePageItems(): ItemList<any>; public homePageItems(): ItemList;
} }
import AdminPage from "./AdminPage"; import AdminPage from "./AdminPage";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -1,6 +1,6 @@
export default class DashboardPage extends AdminPage<import("../../common/components/Page").IPageAttrs> { export default class DashboardPage extends AdminPage<import("../../common/components/Page").IPageAttrs> {
constructor(); constructor();
availableWidgets(): ItemList<any>; availableWidgets(): ItemList;
} }
import AdminPage from "./AdminPage"; import AdminPage from "./AdminPage";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -2,15 +2,14 @@
* The `EditGroupModal` component shows a modal dialog which allows the user * The `EditGroupModal` component shows a modal dialog which allows the user
* to create or edit a group. * to create or edit a group.
*/ */
export default class EditGroupModal extends Modal<import("../../common/components/Modal").IInternalModalAttrs> { export default class EditGroupModal extends Modal {
constructor();
group: any; group: any;
nameSingular: Stream<any> | undefined; nameSingular: Stream<any> | undefined;
namePlural: Stream<any> | undefined; namePlural: Stream<any> | undefined;
icon: Stream<any> | undefined; icon: Stream<any> | undefined;
color: Stream<any> | undefined; color: Stream<any> | undefined;
isHidden: Stream<any> | undefined; isHidden: Stream<any> | undefined;
fields(): ItemList<any>; fields(): ItemList;
submitData(): { submitData(): {
nameSingular: any; nameSingular: any;
namePlural: any; namePlural: any;

View File

@@ -1,5 +1,5 @@
export default class ExtensionLinkButton extends LinkButton { export default class ExtensionLinkButton extends LinkButton {
statusItems(name: any): ItemList<any>; statusItems(name: any): ItemList;
} }
import LinkButton from "../../common/components/LinkButton"; import LinkButton from "../../common/components/LinkButton";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -1,15 +1,7 @@
import ItemList from '../../common/utils/ItemList'; export default class ExtensionPage extends AdminPage<import("../../common/components/Page").IPageAttrs> {
import AdminPage from './AdminPage'; constructor();
import RequestError from '../../common/utils/RequestError'; extension: any;
import { Extension } from '../AdminApplication'; changingState: boolean | undefined;
import { IPageAttrs } from '../../common/components/Page';
import type Mithril from 'mithril';
export interface ExtensionPageAttrs extends IPageAttrs {
id: string;
}
export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionPageAttrs> extends AdminPage<Attrs> {
extension: Extension;
changingState: boolean;
infoFields: { infoFields: {
discuss: string; discuss: string;
documentation: string; documentation: string;
@@ -17,16 +9,14 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
website: string; website: string;
donate: string; donate: string;
source: string; source: string;
}; } | undefined;
oninit(vnode: Mithril.Vnode<Attrs, this>): void;
className(): string; className(): string;
view(vnode: Mithril.VnodeDOM<Attrs, this>): JSX.Element | null; sections(): ItemList;
header(): JSX.Element[]; topItems(): ItemList;
sections(vnode: Mithril.VnodeDOM<Attrs, this>): ItemList<unknown>; infoItems(): ItemList;
content(vnode: Mithril.VnodeDOM<Attrs, this>): JSX.Element;
topItems(): ItemList<Mithril.Children>;
infoItems(): ItemList<Mithril.Children>;
toggle(): void; toggle(): void;
isEnabled(): any; isEnabled(): any;
onerror(e: RequestError): void; onerror(e: any): void;
} }
import AdminPage from "./AdminPage";
import ItemList from "../../common/utils/ItemList";

View File

@@ -1,19 +1,4 @@
import PermissionGrid, { PermissionGridEntry } from './PermissionGrid'; export default class ExtensionPermissionGrid extends PermissionGrid {
import ItemList from '../../common/utils/ItemList'; extensionId: any;
import Mithril from 'mithril';
export interface IExtensionPermissionGridAttrs {
extensionId: string;
}
export default class ExtensionPermissionGrid<CustomAttrs extends IExtensionPermissionGridAttrs = IExtensionPermissionGridAttrs> extends PermissionGrid<CustomAttrs> {
protected extensionId: string;
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
permissionItems(): ItemList<{
label: Mithril.Children;
children: PermissionGridEntry[];
}>;
viewItems(): ItemList<import("./PermissionGrid").PermissionConfig>;
startItems(): ItemList<import("./PermissionGrid").PermissionConfig>;
replyItems(): ItemList<import("./PermissionGrid").PermissionConfig>;
moderateItems(): ItemList<import("./PermissionGrid").PermissionConfig>;
scopeControlItems(): ItemList<unknown>;
} }
import PermissionGrid from "./PermissionGrid";

View File

@@ -10,7 +10,7 @@ export default class HeaderPrimary extends Component<import("../../common/Compon
* *
* @return {ItemList} * @return {ItemList}
*/ */
items(): ItemList<any>; items(): ItemList;
} }
import Component from "../../common/Component"; import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -8,7 +8,7 @@ export default class HeaderSecondary extends Component<import("../../common/Comp
* *
* @return {ItemList} * @return {ItemList}
*/ */
items(): ItemList<any>; items(): ItemList;
} }
import Component from "../../common/Component"; import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -1,14 +1,3 @@
/// <reference path="../../../src/common/translator-icu-rich.d.ts" /> export default class LoadingModal extends Modal {
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
export interface ILoadingModalAttrs extends IInternalModalAttrs {
}
export default class LoadingModal<ModalAttrs extends ILoadingModalAttrs = ILoadingModalAttrs> extends Modal<ModalAttrs> {
/**
* @inheritdoc
*/
static readonly isDismissible: boolean;
className(): string;
title(): import("@askvortsov/rich-icu-message-formatter").NestedStringArray;
content(): string;
onsubmit(e: Event): void;
} }
import Modal from "../../common/components/Modal";

View File

@@ -1,36 +1,12 @@
import Component, { ComponentAttrs } from '../../common/Component'; export default class PermissionGrid extends Component<import("../../common/Component").ComponentAttrs, undefined> {
import ItemList from '../../common/utils/ItemList'; constructor();
import type Mithril from 'mithril'; permissionItems(): ItemList;
export interface PermissionConfig { viewItems(): ItemList;
permission: string; startItems(): ItemList;
icon: string; replyItems(): ItemList;
label: Mithril.Children; moderateItems(): ItemList;
allowGuest?: boolean; scopeItems(): ItemList;
} scopeControlItems(): ItemList;
export interface PermissionSetting {
setting: () => Mithril.Children;
icon: string;
label: Mithril.Children;
}
export declare type PermissionGridEntry = PermissionConfig | PermissionSetting;
export declare type PermissionType = 'view' | 'start' | 'reply' | 'moderate';
export interface ScopeItem {
label: Mithril.Children;
render: (permission: PermissionGridEntry) => Mithril.Children;
onremove?: () => void;
}
export interface IPermissionGridAttrs extends ComponentAttrs {
}
export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = IPermissionGridAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.Vnode<CustomAttrs, this>): JSX.Element;
permissionItems(): ItemList<{
label: Mithril.Children;
children: PermissionGridEntry[];
}>;
viewItems(): ItemList<PermissionGridEntry>;
startItems(): ItemList<PermissionGridEntry>;
replyItems(): ItemList<PermissionGridEntry>;
moderateItems(): ItemList<PermissionGridEntry>;
scopeItems(): ItemList<ScopeItem>;
scopeControlItems(): ItemList<unknown>;
} }
import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList";

View File

@@ -1,18 +0,0 @@
/// <reference path="../../../src/common/translator-icu-rich.d.ts" />
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
import ExtensionReadme from '../models/ExtensionReadme';
import type Mithril from 'mithril';
import type { Extension } from '../AdminApplication';
export interface IReadmeModalAttrs extends IInternalModalAttrs {
extension: Extension;
}
export default class ReadmeModal<CustomAttrs extends IReadmeModalAttrs = IReadmeModalAttrs> extends Modal<CustomAttrs> {
protected name: string;
protected extName: string;
protected readme: ExtensionReadme;
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
className(): string;
title(): import("@askvortsov/rich-icu-message-formatter").NestedStringArray;
content(): JSX.Element;
loadReadme(): Promise<void>;
}

View File

@@ -8,7 +8,7 @@ export default class SessionDropdown extends Dropdown {
* *
* @return {ItemList} * @return {ItemList}
*/ */
items(): ItemList<any>; items(): ItemList;
} }
import Dropdown from "../../common/components/Dropdown"; import Dropdown from "../../common/components/Dropdown";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -1,5 +1,4 @@
export default class SettingsModal extends Modal<import("../../common/components/Modal").IInternalModalAttrs> { export default class SettingsModal extends Modal {
constructor();
settings: {} | undefined; settings: {} | undefined;
form(): string; form(): string;
submitButton(): JSX.Element; submitButton(): JSX.Element;

View File

@@ -1,6 +1,5 @@
export default class StatusWidget extends DashboardWidget { export default class StatusWidget extends DashboardWidget {
items(): ItemList<any>; items(): ItemList;
toolsItems(): ItemList<any>;
handleClearCache(e: any): void; handleClearCache(e: any): void;
} }
import DashboardWidget from "./DashboardWidget"; import DashboardWidget from "./DashboardWidget";

View File

@@ -1,18 +1,6 @@
/// <reference path="../../../src/common/translator-icu-rich.d.ts" /> /// <reference types="mithril" />
import type Mithril from 'mithril';
import type User from '../../common/models/User';
import ItemList from '../../common/utils/ItemList'; import ItemList from '../../common/utils/ItemList';
import AdminPage from './AdminPage'; import AdminPage from './AdminPage';
declare type ColumnData = {
/**
* Column title
*/
name: Mithril.Children;
/**
* Component(s) to show for this column.
*/
content: (user: User) => Mithril.Children;
};
/** /**
* Admin page which displays a paginated list of all users on the forum. * Admin page which displays a paginated list of all users on the forum.
*/ */
@@ -62,12 +50,12 @@ export default class UserListPage extends AdminPage {
* *
* See `UserListPage.tsx` for examples. * See `UserListPage.tsx` for examples.
*/ */
columns(): ItemList<ColumnData>; columns(): ItemList;
headerInfo(): { headerInfo(): {
className: string; className: string;
icon: string; icon: string;
title: import("@askvortsov/rich-icu-message-formatter").NestedStringArray; title: any;
description: import("@askvortsov/rich-icu-message-formatter").NestedStringArray; description: any;
}; };
/** /**
* Asynchronously fetch the next set of users to be rendered. * Asynchronously fetch the next set of users to be rendered.
@@ -82,4 +70,3 @@ export default class UserListPage extends AdminPage {
nextPage(): void; nextPage(): void;
previousPage(): void; previousPage(): void;
} }
export {};

View File

@@ -1,3 +1,5 @@
import app from './app'; import app from './app';
export { app }; export { app };
export declare const compat: Record<string, unknown>; export declare const compat: {
[key: string]: any;
};

View File

@@ -1,4 +0,0 @@
export default class ExtensionReadme extends Model {
content: () => any;
}
import Model from "../../common/Model";

View File

@@ -1,10 +1,9 @@
import DefaultResolver from '../../common/resolvers/DefaultResolver'; import DefaultResolver from '../../common/resolvers/DefaultResolver';
import ExtensionPage, { ExtensionPageAttrs } from '../components/ExtensionPage';
/** /**
* A custom route resolver for ExtensionPage that generates handles routes * A custom route resolver for ExtensionPage that generates handles routes
* to default extension pages or a page provided by an extension. * to default extension pages or a page provided by an extension.
*/ */
export default class ExtensionPageResolver<Attrs extends ExtensionPageAttrs = ExtensionPageAttrs, RouteArgs extends Record<string, unknown> = {}> extends DefaultResolver<Attrs, ExtensionPage<Attrs>, RouteArgs> { export default class ExtensionPageResolver extends DefaultResolver {
static extension: string | null; static extension: string | null;
onmatch(args: Attrs & RouteArgs, requestedPath: string, route: string): new () => ExtensionPage<Attrs>; onmatch(args: any, requestedPath: any, route: any): any;
} }

View File

@@ -1,5 +1,6 @@
import AdminApplication from './AdminApplication';
/** /**
* The `routes` initializer defines the forum app's routes. * The `routes` initializer defines the forum app's routes.
*
* @param {App} app
*/ */
export default function (app: AdminApplication): void; export default function _default(app: any): void;

View File

@@ -1,46 +1,17 @@
import type Mithril from 'mithril';
import ItemList from '../../common/utils/ItemList';
import { SettingsComponentOptions } from '../components/AdminPage';
import ExtensionPage, { ExtensionPageAttrs } from '../components/ExtensionPage';
import { PermissionConfig, PermissionType } from '../components/PermissionGrid';
declare type SettingConfigInput = SettingsComponentOptions | (() => Mithril.Children);
declare type SettingConfigInternal = SettingsComponentOptions | ((() => Mithril.Children) & {
setting: string;
});
export declare type CustomExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionPageAttrs> = new () => ExtensionPage<Attrs>;
declare type ExtensionConfig = {
settings?: ItemList<SettingConfigInternal>;
permissions?: {
view?: ItemList<PermissionConfig>;
start?: ItemList<PermissionConfig>;
reply?: ItemList<PermissionConfig>;
moderate?: ItemList<PermissionConfig>;
};
page?: CustomExtensionPage;
};
declare type InnerDataNoActiveExtension = {
currentExtension: null;
data: {
[key: string]: ExtensionConfig | undefined;
};
};
declare type InnerDataActiveExtension = {
currentExtension: string;
data: {
[key: string]: ExtensionConfig;
};
};
export default class ExtensionData { export default class ExtensionData {
protected state: InnerDataActiveExtension | InnerDataNoActiveExtension; data: {};
currentExtension: any;
/** /**
* This function simply takes the extension id * This function simply takes the extension id
* *
* @example * @example
* app.extensionData.for('flarum-tags') * app.extensionData.load('flarum-tags')
* *
* flarum/flags -> flarum-flags | acme/extension -> acme-extension * flarum/flags -> flarum-flags | acme/extension -> acme-extension
*
* @param extension
*/ */
for(extension: string): this; for(extension: any): ExtensionData;
/** /**
* This function registers your settings with Flarum * This function registers your settings with Flarum
* *
@@ -53,8 +24,13 @@ export default class ExtensionData {
* type: 'text', // This will be inputted into the input tag for the setting (text/number/etc) * type: 'text', // This will be inputted into the input tag for the setting (text/number/etc)
* label: app.translator.trans('flarum-flags.admin.settings.guidelines_url_label') * label: app.translator.trans('flarum-flags.admin.settings.guidelines_url_label')
* }, 15) // priority is optional (ItemList) * }, 15) // priority is optional (ItemList)
*
*
* @param content
* @param priority
* @returns {ExtensionData}
*/ */
registerSetting(content: SettingConfigInput, priority?: number): this; registerSetting(content: any, priority?: number): ExtensionData;
/** /**
* This function registers your permission with Flarum * This function registers your permission with Flarum
* *
@@ -65,32 +41,58 @@ export default class ExtensionData {
* label: app.translator.trans('flarum-flags.admin.permissions.view_flags_label'), * label: app.translator.trans('flarum-flags.admin.permissions.view_flags_label'),
* permission: 'discussion.viewFlags' * permission: 'discussion.viewFlags'
* }, 'moderate', 65) * }, 'moderate', 65)
*
* @param content
* @param permissionType
* @param priority
* @returns {ExtensionData}
*/ */
registerPermission(content: PermissionConfig, permissionType: PermissionType, priority?: number): this; registerPermission(content: any, permissionType?: any, priority?: number): ExtensionData;
/** /**
* Replace the default extension page with a custom component. * Replace the default extension page with a custom component.
* This component would typically extend ExtensionPage * This component would typically extend ExtensionPage
*
* @param component
* @returns {ExtensionData}
*/ */
registerPage(component: CustomExtensionPage): this; registerPage(component: any): ExtensionData;
/** /**
* Get an extension's registered settings * Get an extension's registered settings
*
* @param extensionId
* @returns {boolean|*}
*/ */
getSettings(extensionId: string): SettingConfigInternal[] | undefined; getSettings(extensionId: any): boolean | any;
/** /**
*
* Get an ItemList of all extensions' registered permissions * Get an ItemList of all extensions' registered permissions
*
* @param extension
* @param type
* @returns {ItemList}
*/ */
getAllExtensionPermissions(type: PermissionType): ItemList<PermissionConfig>; getAllExtensionPermissions(type: any): ItemList;
/** /**
* Get a singular extension's registered permissions * Get a singular extension's registered permissions
*
* @param extension
* @param type
* @returns {boolean|*}
*/ */
getExtensionPermissions(extension: string, type: PermissionType): ItemList<PermissionConfig>; getExtensionPermissions(extension: any, type: any): boolean | any;
/** /**
* Checks whether a given extension has registered permissions. * Checks whether a given extension has registered permissions.
*
* @param extension
* @returns {boolean}
*/ */
extensionHasPermissions(extension: string): boolean; extensionHasPermissions(extension: any): boolean;
/** /**
* Returns an extension's custom page component if it exists. * Returns an extension's custom page component if it exists.
*
* @param extension
* @returns {boolean|*}
*/ */
getPage<Attrs extends ExtensionPageAttrs = ExtensionPageAttrs>(extension: string): CustomExtensionPage<Attrs> | undefined; getPage(extension: any): boolean | any;
} }
export {}; import ItemList from "../../common/utils/ItemList";

View File

@@ -1,89 +1,3 @@
import ItemList from './utils/ItemList';
import Translator from './Translator';
import Store, { ApiPayload, ApiResponsePlural, ApiResponseSingle } from './Store';
import Session from './Session';
import Drawer from './utils/Drawer';
import RequestError, { InternalFlarumRequestOptions } from './utils/RequestError';
import Forum from './models/Forum';
import PageState from './states/PageState';
import ModalManagerState from './states/ModalManagerState';
import AlertManagerState from './states/AlertManagerState';
import type DefaultResolver from './resolvers/DefaultResolver';
import type Mithril from 'mithril';
import type Component from './Component';
import type { ComponentAttrs } from './Component';
import Model, { SavedModelData } from './Model';
export declare type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd';
export declare type FlarumGenericRoute = RouteItem<any, any, any>;
export interface FlarumRequestOptions<ResponseType> extends Omit<Mithril.RequestOptions<ResponseType>, 'extract'> {
errorHandler?: (error: RequestError) => void;
url: string;
/**
* Manipulate the response text before it is parsed into JSON.
*
* @deprecated Please use `modifyText` instead.
*/
extract?: (responseText: string) => string;
/**
* Manipulate the response text before it is parsed into JSON.
*
* This overrides any `extract` method provided.
*/
modifyText?: (responseText: string) => string;
}
/**
* A valid route definition.
*/
export declare type RouteItem<Attrs extends ComponentAttrs, Comp extends Component<Attrs & {
routeName: string;
}>, RouteArgs extends Record<string, unknown> = {}> = {
/**
* The path for your route.
*
* This might be a specific URL path (e.g.,`/myPage`), or it might
* contain a variable used by a resolver (e.g., `/myPage/:id`).
*
* @see https://docs.flarum.org/extend/frontend-pages.html#route-resolvers-advanced
*/
path: `/${string}`;
} & ({
/**
* The component to render when this route matches.
*/
component: new () => Comp;
/**
* A custom resolver class.
*
* This should be the class itself, and **not** an instance of the
* class.
*/
resolverClass?: new (component: new () => Comp, routeName: string) => DefaultResolver<Attrs, Comp, RouteArgs>;
} | {
/**
* An instance of a route resolver.
*/
resolver: RouteResolver<Attrs, Comp, RouteArgs>;
});
export interface RouteResolver<Attrs extends ComponentAttrs, Comp extends Component<Attrs & {
routeName: string;
}>, RouteArgs extends Record<string, unknown> = {}> {
/**
* A method which selects which component to render based on
* conditional logic.
*
* Returns the component class, and **not** a Vnode or JSX
* expression.
*/
onmatch(this: this, args: RouteArgs, requestedPath: string, route: string): {
new (): Comp;
};
/**
* A function which renders the provided component.
*
* Returns a Mithril Vnode or other children.
*/
render(this: this, vnode: Mithril.Vnode<Attrs, Comp>): Mithril.Children;
}
/** /**
* The `App` class provides a container for an application, as well as various * The `App` class provides a container for an application, as well as various
* utilities for the rest of the app to use. * utilities for the rest of the app to use.
@@ -91,8 +5,11 @@ export interface RouteResolver<Attrs extends ComponentAttrs, Comp extends Compon
export default class Application { export default class Application {
/** /**
* The forum model for this application. * The forum model for this application.
*
* @type {Forum}
* @public
*/ */
forum: Forum; public forum: Forum;
/** /**
* A map of routes, keyed by a unique route name. Each route is an object * A map of routes, keyed by a unique route name. Each route is an object
* containing the following properties: * containing the following properties:
@@ -101,42 +18,71 @@ export default class Application {
* - `component` The Mithril component to render when this route is active. * - `component` The Mithril component to render when this route is active.
* *
* @example * @example
* app.routes.discussion = { path: '/d/:id', component: DiscussionPage }; * app.routes.discussion = {path: '/d/:id', component: DiscussionPage.component()};
*
* @type {Object}
* @public
*/ */
routes: Record<string, FlarumGenericRoute>; public routes: Object;
/** /**
* An ordered list of initializers to bootstrap the application. * An ordered list of initializers to bootstrap the application.
*
* @type {ItemList}
* @public
*/ */
initializers: ItemList<(app: this) => void>; public initializers: ItemList;
/** /**
* The app's session. * The app's session.
* *
* Stores info about the current user. * @type {Session}
* @public
*/ */
session: Session; public session: Session;
/** /**
* The app's translator. * The app's translator.
*
* @type {Translator}
* @public
*/ */
translator: Translator; public translator: Translator;
/** /**
* The app's data store. * The app's data store.
*
* @type {Store}
* @public
*/ */
store: Store; public store: Store;
/** /**
* A local cache that can be used to store data at the application level, so * A local cache that can be used to store data at the application level, so
* that is persists between different routes. * that is persists between different routes.
*
* @type {Object}
* @public
*/ */
cache: Record<string, unknown>; public cache: Object;
/** /**
* Whether or not the app has been booted. * Whether or not the app has been booted.
*
* @type {Boolean}
* @public
*/ */
booted: boolean; public booted: boolean;
/**
* The key for an Alert that was shown as a result of an AJAX request error.
* If present, it will be dismissed on the next successful request.
*
* @type {int}
* @private
*/
private requestErrorAlert;
/** /**
* The page the app is currently on. * The page the app is currently on.
* *
* This object holds information about the type of page we are currently * This object holds information about the type of page we are currently
* visiting, and sometimes additional arbitrary page state that may be * visiting, and sometimes additional arbitrary page state that may be
* relevant to lower-level components. * relevant to lower-level components.
*
* @type {PageState}
*/ */
current: PageState; current: PageState;
/** /**
@@ -145,89 +91,84 @@ export default class Application {
* Once the application navigates to another page, the object previously * Once the application navigates to another page, the object previously
* assigned to this.current will be moved to this.previous, while this.current * assigned to this.current will be moved to this.previous, while this.current
* is re-initialized. * is re-initialized.
*
* @type {PageState}
*/ */
previous: PageState; previous: PageState;
/**
* An object that manages modal state.
*/
modal: ModalManagerState; modal: ModalManagerState;
/** /**
* An object that manages the state of active alerts. * An object that manages the state of active alerts.
*
* @type {AlertManagerState}
*/ */
alerts: AlertManagerState; alerts: AlertManagerState;
/** data: any;
* An object that manages the state of the navigation drawer. title: string;
*/ titleCount: number;
drawer: Drawer; initialRoute: any;
data: { load(payload: any): void;
apiDocument: ApiPayload | null;
locale: string;
locales: Record<string, string>;
resources: SavedModelData[];
session: {
userId: number;
csrfToken: string;
};
[key: string]: unknown;
};
private _title;
private _titleCount;
private set title(value);
get title(): string;
private set titleCount(value);
get titleCount(): number;
/**
* The key for an Alert that was shown as a result of an AJAX request error.
* If present, it will be dismissed on the next successful request.
*/
private requestErrorAlert;
initialRoute: string;
load(payload: Application['data']): void;
boot(): void; boot(): void;
bootExtensions(extensions: Record<string, { bootExtensions(extensions: any): void;
extend?: unknown[]; mount(basePath?: string): void;
}>): void; drawer: Drawer | undefined;
protected mount(basePath?: string): void;
/** /**
* Get the API response document that has been preloaded into the application. * Get the API response document that has been preloaded into the application.
*
* @return {Object|null}
* @public
*/ */
preloadedApiDocument<M extends Model>(): ApiResponseSingle<M> | null; public preloadedApiDocument(): Object | null;
preloadedApiDocument<Ms extends Model[]>(): ApiResponsePlural<Ms[number]> | null;
/** /**
* Determine the current screen mode, based on our media queries. * Determine the current screen mode, based on our media queries.
*/
screen(): FlarumScreens;
/**
* Set the `<title>` of the page.
* *
* @param title New page title * @returns {String} - one of "phone", "tablet", "desktop" or "desktop-hd"
*/ */
setTitle(title: string): void; screen(): string;
/** /**
* Set a number to display in the `<title>` of the page. * Set the <title> of the page.
* *
* @param count Number to display in title * @param {String} title
* @public
*/ */
setTitleCount(count: number): void; public setTitle(title: string): void;
/**
* Set a number to display in the <title> of the page.
*
* @param {Integer} count
*/
setTitleCount(count: any): void;
updateTitle(): void; updateTitle(): void;
protected transformRequestOptions<ResponseType>(flarumOptions: FlarumRequestOptions<ResponseType>): InternalFlarumRequestOptions<ResponseType>;
/** /**
* Make an AJAX request, handling any low-level errors that may occur. * Make an AJAX request, handling any low-level errors that may occur.
* *
* @see https://mithril.js.org/request.html * @see https://mithril.js.org/request.html
* * @param {Object} options
* @param options
* @return {Promise} * @return {Promise}
* @public
*/ */
request<ResponseType>(originalOptions: FlarumRequestOptions<ResponseType>): Promise<ResponseType>; public request(originalOptions: any): Promise<any>;
/** /**
* By default, show an error alert, and log the error to the console. * @param {RequestError} error
* @param {string[]} [formattedError]
* @private
*/ */
protected requestErrorCatch<ResponseType>(error: RequestError, customErrorHandler: FlarumRequestOptions<ResponseType>['errorHandler']): Promise<never>;
protected requestErrorDefaultHandler(e: unknown, isDebug: boolean, formattedErrors: string[]): void;
private showDebug; private showDebug;
/** /**
* Construct a URL to the route with the given name. * Construct a URL to the route with the given name.
*
* @param {String} name
* @param {Object} params
* @return {String}
* @public
*/ */
route(name: string, params?: Record<string, unknown>): string; public route(name: string, params?: Object): string;
} }
import Forum from "./models/Forum";
import ItemList from "./utils/ItemList";
import Session from "./Session";
import Translator from "./Translator";
import Store from "./Store";
import PageState from "./states/PageState";
import ModalManagerState from "./states/ModalManagerState";
import AlertManagerState from "./states/AlertManagerState";
import Drawer from "./utils/Drawer";

View File

@@ -99,7 +99,7 @@ export default abstract class Component<Attrs extends ComponentAttrs = Component
* *
* @see https://mithril.js.org/hyperscript.html#mselector,-attributes,-children * @see https://mithril.js.org/hyperscript.html#mselector,-attributes,-children
*/ */
static component<SAttrs extends ComponentAttrs = ComponentAttrs>(attrs?: SAttrs, children?: Mithril.Children): Mithril.Vnode; static component(attrs?: {}, children?: null): Mithril.Vnode;
/** /**
* Saves a reference to the vnode attrs after running them through initAttrs, * Saves a reference to the vnode attrs after running them through initAttrs,
* and checking for common issues. * and checking for common issues.

View File

@@ -32,7 +32,7 @@ export default abstract class Fragment {
* @returns {jQuery} the jQuery object for the DOM node * @returns {jQuery} the jQuery object for the DOM node
* @final * @final
*/ */
$(selector?: string): JQuery; $(selector: any): JQuery<any>;
/** /**
* Get the renderable virtual DOM that represents the fragment's view. * Get the renderable virtual DOM that represents the fragment's view.
* *

View File

@@ -1,147 +1,149 @@
import { FlarumRequestOptions } from './Application';
import Store, { ApiPayloadSingle, ApiResponseSingle, MetaInformation } from './Store';
export interface ModelIdentifier {
type: string;
id: string;
}
export interface ModelAttributes {
[key: string]: unknown;
}
export interface ModelRelationships {
[relationship: string]: {
data: ModelIdentifier | ModelIdentifier[];
};
}
export interface UnsavedModelData {
type?: string;
attributes?: ModelAttributes;
relationships?: ModelRelationships;
}
export interface SavedModelData {
type: string;
id: string;
attributes?: ModelAttributes;
relationships?: ModelRelationships;
}
export declare type ModelData = UnsavedModelData | SavedModelData;
export interface SaveRelationships {
[relationship: string]: Model | Model[];
}
export interface SaveAttributes {
[key: string]: unknown;
relationships?: SaveRelationships;
}
/** /**
* The `Model` class represents a local data resource. It provides methods to * The `Model` class represents a local data resource. It provides methods to
* persist changes via the API. * persist changes via the API.
*
* @abstract
*/ */
export default abstract class Model { export default class Model {
/**
* The resource object from the API.
*/
data: ModelData;
/**
* The time at which the model's data was last updated. Watching the value
* of this property is a fast way to retain/cache a subtree if data hasn't
* changed.
*/
freshness: Date;
/**
* Whether or not the resource exists on the server.
*/
exists: boolean;
/**
* The data store that this resource should be persisted to.
*/
protected store: Store;
/**
* @param data A resource object from the API.
* @param store The data store that this model should be persisted to.
*/
constructor(data?: ModelData, store?: Store);
/**
* Get the model's ID.
*
* @final
*/
id(): string | undefined;
/**
* Get one of the model's attributes.
*
* @final
*/
attribute<T = unknown>(attribute: string): T;
/**
* Merge new data into this model locally.
*
* @param data A resource object to merge into this model
*/
pushData(data: ModelData | {
relationships?: SaveRelationships;
}): this;
/**
* Merge new attributes into this model locally.
*
* @param attributes The attributes to merge.
*/
pushAttributes(attributes: ModelAttributes): void;
/**
* Merge new attributes into this model, both locally and with persistence.
*
* @param attributes The attributes to save. If a 'relationships' key
* exists, it will be extracted and relationships will also be saved.
*/
save(attributes: SaveAttributes, options?: Omit<FlarumRequestOptions<ApiPayloadSingle>, 'url'> & {
meta?: MetaInformation;
}): Promise<ApiResponseSingle<this>>;
/**
* Send a request to delete the resource.
*
* @param body Data to send along with the DELETE request.
*/
delete(body?: FlarumRequestOptions<void>['body'], options?: Omit<FlarumRequestOptions<void>, 'url'>): Promise<void>;
/**
* Construct a path to the API endpoint for this resource.
*/
protected apiEndpoint(): string;
protected copyData(): ModelData;
protected rawRelationship<M extends Model>(relationship: string): undefined | ModelIdentifier;
protected rawRelationship<M extends Model[]>(relationship: string): undefined | ModelIdentifier[];
/** /**
* Generate a function which returns the value of the given attribute. * Generate a function which returns the value of the given attribute.
* *
* @param transform A function to transform the attribute value * @param {String} name
* @param {function} [transform] A function to transform the attribute value
* @return {*}
* @public
*/ */
static attribute<T>(name: string): () => T; public static attribute(name: string, transform?: Function | undefined): any;
static attribute<T, O = unknown>(name: string, transform: (attr: O) => T): () => T;
/** /**
* Generate a function which returns the value of the given has-one * Generate a function which returns the value of the given has-one
* relationship. * relationship.
* *
* @return false if no information about the * @param {String} name
* @return {Model|Boolean|undefined} false if no information about the
* relationship exists; undefined if the relationship exists but the model * relationship exists; undefined if the relationship exists but the model
* has not been loaded; or the model if it has been loaded. * has not been loaded; or the model if it has been loaded.
* @public
*/ */
static hasOne<M extends Model>(name: string): () => M | false; public static hasOne(name: string): Model | boolean | undefined;
static hasOne<M extends Model | null>(name: string): () => M | null | false;
/** /**
* Generate a function which returns the value of the given has-many * Generate a function which returns the value of the given has-many
* relationship. * relationship.
* *
* @return false if no information about the relationship * @param {String} name
* @return {Array|Boolean} false if no information about the relationship
* exists; an array if it does, containing models if they have been * exists; an array if it does, containing models if they have been
* loaded, and undefined for those that have not. * loaded, and undefined for those that have not.
* @public
*/ */
static hasMany<M extends Model>(name: string): () => (M | undefined)[] | false; public static hasMany(name: string): any[] | boolean;
/** /**
* Transform the given value into a Date object. * Transform the given value into a Date object.
*
* @param {String} value
* @return {Date|null}
* @public
*/ */
static transformDate(value: string): Date; public static transformDate(value: string): Date | null;
static transformDate(value: string | null): Date | null;
static transformDate(value: string | undefined): Date | undefined;
static transformDate(value: string | null | undefined): Date | null | undefined;
/** /**
* Get a resource identifier object for the given model. * Get a resource identifier object for the given model.
*
* @param {Model} model
* @return {Object}
* @protected
*/ */
protected static getIdentifier(model: Model): ModelIdentifier; protected static getIdentifier(model: Model): Object;
/**
* @param {Object} data A resource object from the API.
* @param {Store} store The data store that this model should be persisted to.
* @public
*/
constructor(data?: Object, store?: any);
/**
* The resource object from the API.
*
* @type {Object}
* @public
*/
public data: Object;
/**
* The time at which the model's data was last updated. Watching the value
* of this property is a fast way to retain/cache a subtree if data hasn't
* changed.
*
* @type {Date}
* @public
*/
public freshness: Date;
/**
* Whether or not the resource exists on the server.
*
* @type {Boolean}
* @public
*/
public exists: boolean;
/**
* The data store that this resource should be persisted to.
*
* @type {Store}
* @protected
*/
protected store: any;
/**
* Get the model's ID.
*
* @return {Integer}
* @public
* @final
*/
public id(): any;
/**
* Get one of the model's attributes.
*
* @param {String} attribute
* @return {*}
* @public
* @final
*/
public attribute(attribute: string): any;
/**
* Merge new data into this model locally.
*
* @param {Object} data A resource object to merge into this model
* @public
*/
public pushData(data: Object): void;
/**
* Merge new attributes into this model locally.
*
* @param {Object} attributes The attributes to merge.
* @public
*/
public pushAttributes(attributes: Object): void;
/**
* Merge new attributes into this model, both locally and with persistence.
*
* @param {Object} attributes The attributes to save. If a 'relationships' key
* exists, it will be extracted and relationships will also be saved.
* @param {Object} [options]
* @return {Promise}
* @public
*/
public save(attributes: Object, options?: Object | undefined): Promise<any>;
/**
* Send a request to delete the resource.
*
* @param {Object} body Data to send along with the DELETE request.
* @param {Object} [options]
* @return {Promise}
* @public
*/
public delete(body: Object, options?: Object | undefined): Promise<any>;
/**
* Construct a path to the API endpoint for this resource.
*
* @return {String}
* @protected
*/
protected apiEndpoint(): string;
copyData(): any;
} }

View File

@@ -1,33 +1,37 @@
import User from './models/User';
import { FlarumRequestOptions } from './Application';
export declare type LoginParams = {
/**
* The username/email
*/
identification: string;
password: string;
remember: boolean;
};
/** /**
* The `Session` class defines the current user session. It stores a reference * The `Session` class defines the current user session. It stores a reference
* to the current authenticated user, and provides methods to log in/out. * to the current authenticated user, and provides methods to log in/out.
*/ */
export default class Session { export default class Session {
constructor(user: any, csrfToken: any);
/** /**
* The current authenticated user. * The current authenticated user.
*
* @type {User|null}
* @public
*/ */
user: User | null; public user: any | null;
/** /**
* The CSRF token. * The CSRF token.
*
* @type {String|null}
* @public
*/ */
csrfToken: string; public csrfToken: string | null;
constructor(user: User | null, csrfToken: string);
/** /**
* Attempt to log in a user. * Attempt to log in a user.
*
* @param {String} identification The username/email.
* @param {String} password
* @param {Object} [options]
* @return {Promise}
* @public
*/ */
login(body: LoginParams, options?: Omit<FlarumRequestOptions<any>, 'url' | 'body' | 'method'>): Promise<any>; public login(body: any, options?: Object | undefined): Promise<any>;
/** /**
* Log the user out. * Log the user out.
*
* @public
*/ */
logout(): void; public logout(): void;
} }

View File

@@ -1,127 +1,97 @@
import { FlarumRequestOptions } from './Application';
import Model, { ModelData, SavedModelData } from './Model';
export interface MetaInformation {
[key: string]: any;
}
export interface ApiQueryParamsSingle {
fields?: string[];
include?: string;
bySlug?: boolean;
meta?: MetaInformation;
}
export interface ApiQueryParamsPlural {
fields?: string[];
include?: string;
filter?: {
q: string;
[key: string]: string;
};
page?: {
offset?: number;
number?: number;
limit?: number;
size?: number;
};
sort?: string;
meta?: MetaInformation;
}
export declare type ApiQueryParams = ApiQueryParamsPlural | ApiQueryParamsSingle;
export interface ApiPayloadSingle {
data: SavedModelData;
included?: SavedModelData[];
meta?: MetaInformation;
}
export interface ApiPayloadPlural {
data: SavedModelData[];
included?: SavedModelData[];
links?: {
first: string;
next?: string;
prev?: string;
};
meta?: MetaInformation;
}
export declare type ApiPayload = ApiPayloadSingle | ApiPayloadPlural;
export declare type ApiResponseSingle<M extends Model> = M & {
payload: ApiPayloadSingle;
};
export declare type ApiResponsePlural<M extends Model> = M[] & {
payload: ApiPayloadPlural;
};
export declare type ApiResponse<M extends Model> = ApiResponseSingle<M> | ApiResponsePlural<M>;
interface ApiQueryRequestOptions<ResponseType> extends Omit<FlarumRequestOptions<ResponseType>, 'url'> {
}
interface StoreData {
[type: string]: Partial<Record<string, Model>>;
}
export declare function payloadIsPlural(payload: ApiPayload): payload is ApiPayloadPlural;
/** /**
* The `Store` class defines a local data store, and provides methods to * The `Store` class defines a local data store, and provides methods to
* retrieve data from the API. * retrieve data from the API.
*/ */
export default class Store { export default class Store {
constructor(models: any);
/** /**
* The local data store. A tree of resource types to IDs, such that * The local data store. A tree of resource types to IDs, such that
* accessing data[type][id] will return the model for that type/ID. * accessing data[type][id] will return the model for that type/ID.
*
* @type {Object}
* @protected
*/ */
protected data: StoreData; protected data: Object;
/** /**
* The model registry. A map of resource types to the model class that * The model registry. A map of resource types to the model class that
* should be used to represent resources of that type. * should be used to represent resources of that type.
*
* @type {Object}
* @public
*/ */
models: Record<string, typeof Model>; public models: Object;
constructor(models: Record<string, typeof Model>);
/** /**
* Push resources contained within an API payload into the store. * Push resources contained within an API payload into the store.
* *
* @return The model(s) representing the resource(s) contained * @param {Object} payload
* @return {Model|Model[]} The model(s) representing the resource(s) contained
* within the 'data' key of the payload. * within the 'data' key of the payload.
* @public
*/ */
pushPayload<M extends Model>(payload: ApiPayloadSingle): ApiResponseSingle<M>; public pushPayload(payload: Object): any | any[];
pushPayload<Ms extends Model[]>(payload: ApiPayloadPlural): ApiResponseSingle<Ms[number]>;
/** /**
* Create a model to represent a resource object (or update an existing one), * Create a model to represent a resource object (or update an existing one),
* and push it into the store. * and push it into the store.
* *
* @param data The resource object * @param {Object} data The resource object
* @return The model, or null if no model class has been * @return {Model|null} The model, or null if no model class has been
* registered for this resource type. * registered for this resource type.
* @public
*/ */
pushObject<M extends Model>(data: SavedModelData): M | null; public pushObject(data: Object): any | null;
pushObject<M extends Model>(data: SavedModelData, allowUnregistered: false): M;
/** /**
* Make a request to the API to find record(s) of a specific type. * Make a request to the API to find record(s) of a specific type.
*
* @param {String} type The resource type.
* @param {Integer|Integer[]|Object} [id] The ID(s) of the model(s) to retrieve.
* Alternatively, if an object is passed, it will be handled as the
* `query` parameter.
* @param {Object} [query]
* @param {Object} [options]
* @return {Promise}
* @public
*/ */
find<M extends Model>(type: string, params: ApiQueryParamsSingle): Promise<ApiResponseSingle<M>>; public find(type: string, id?: any | any[] | Object, query?: Object | undefined, options?: Object | undefined): Promise<any>;
find<Ms extends Model[]>(type: string, params: ApiQueryParamsPlural): Promise<ApiResponsePlural<Ms[number]>>;
find<M extends Model>(type: string, id: string, params?: ApiQueryParamsSingle, options?: ApiQueryRequestOptions<ApiPayloadSingle>): Promise<ApiResponseSingle<M>>;
find<Ms extends Model[]>(type: string, ids: string[], params?: ApiQueryParamsPlural, options?: ApiQueryRequestOptions<ApiPayloadPlural>): Promise<ApiResponsePlural<Ms[number]>>;
/** /**
* Get a record from the store by ID. * Get a record from the store by ID.
*
* @param {String} type The resource type.
* @param {Integer} id The resource ID.
* @return {Model}
* @public
*/ */
getById<M extends Model>(type: string, id: string): M | undefined; public getById(type: string, id: any): any;
/** /**
* Get a record from the store by the value of a model attribute. * Get a record from the store by the value of a model attribute.
* *
* @param type The resource type. * @param {String} type The resource type.
* @param key The name of the method on the model. * @param {String} key The name of the method on the model.
* @param value The value of the model attribute. * @param {*} value The value of the model attribute.
* @return {Model}
* @public
*/ */
getBy<M extends Model, T = unknown>(type: string, key: keyof M, value: T): M | undefined; public getBy(type: string, key: string, value: any): any;
/** /**
* Get all loaded records of a specific type. * Get all loaded records of a specific type.
*
* @param {String} type
* @return {Model[]}
* @public
*/ */
all<M extends Model>(type: string): M[]; public all(type: string): any[];
/** /**
* Remove the given model from the store. * Remove the given model from the store.
*
* @param {Model} model
*/ */
remove(model: Model): void; remove(model: any): void;
/** /**
* Create a new record of the given type. * Create a new record of the given type.
* *
* @param type The resource type * @param {String} type The resource type
* @param data Any data to initialize the model with * @param {Object} [data] Any data to initialize the model with
* @return {Model}
* @public
*/ */
createRecord<M extends Model>(type: string, data?: ModelData): M; public createRecord(type: string, data?: Object | undefined): any;
} }
export {};

View File

@@ -1,6 +1,3 @@
/// <reference path="../../src/common/translator-icu-rich.d.ts" />
import { RichMessageFormatter } from '@askvortsov/rich-icu-message-formatter';
import { pluralTypeHandler, selectTypeHandler } from '@ultraq/icu-message-formatter';
declare type Translations = Record<string, string>; declare type Translations = Record<string, string>;
declare type TranslatorParameters = Record<string, unknown>; declare type TranslatorParameters = Record<string, unknown>;
export default class Translator { export default class Translator {
@@ -11,15 +8,15 @@ export default class Translator {
/** /**
* The underlying ICU MessageFormatter util. * The underlying ICU MessageFormatter util.
*/ */
protected formatter: RichMessageFormatter; protected formatter: any;
setLocale(locale: string): void; setLocale(locale: string): void;
addTranslations(translations: Translations): void; addTranslations(translations: Translations): void;
/** /**
* An extensible entrypoint for extenders to register type handlers for translations. * An extensible entrypoint for extenders to register type handlers for translations.
*/ */
protected formatterTypeHandlers(): { protected formatterTypeHandlers(): {
plural: typeof pluralTypeHandler; plural: any;
select: typeof selectTypeHandler; select: any;
}; };
/** /**
* A temporary system to preprocess parameters. * A temporary system to preprocess parameters.
@@ -29,6 +26,6 @@ export default class Translator {
* @internal * @internal
*/ */
protected preprocessParameters(parameters: TranslatorParameters): TranslatorParameters; protected preprocessParameters(parameters: TranslatorParameters): TranslatorParameters;
trans(id: string, parameters?: TranslatorParameters): import("@askvortsov/rich-icu-message-formatter").NestedStringArray; trans(id: string, parameters?: TranslatorParameters): any;
} }
export {}; export {};

View File

@@ -1,5 +1,5 @@
declare var _default: { declare var _default: {
extend: any; extend: typeof extend;
Session: typeof Session; Session: typeof Session;
Store: typeof Store; Store: typeof Store;
'utils/BasicEditorDriver': typeof BasicEditorDriver; 'utils/BasicEditorDriver': typeof BasicEditorDriver;
@@ -32,15 +32,17 @@ declare var _default: {
'utils/subclassOf': typeof subclassOf; 'utils/subclassOf': typeof subclassOf;
'utils/setRouteWithForcedRefresh': typeof setRouteWithForcedRefresh; 'utils/setRouteWithForcedRefresh': typeof setRouteWithForcedRefresh;
'utils/patchMithril': typeof patchMithril; 'utils/patchMithril': typeof patchMithril;
'utils/proxifyCompat': typeof proxifyCompat; 'utils/proxifyCompat': (compat: {
[key: string]: any;
}, namespace: string) => {
[key: string]: any;
};
'utils/classList': (...classes: import("clsx").ClassValue[]) => string; 'utils/classList': (...classes: import("clsx").ClassValue[]) => string;
'utils/extractText': typeof extractText; 'utils/extractText': typeof extractText;
'utils/formatNumber': typeof formatNumber; 'utils/formatNumber': typeof formatNumber;
'utils/mapRoutes': typeof mapRoutes; 'utils/mapRoutes': typeof mapRoutes;
'utils/withAttr': (key: string, cb: Function) => (this: Element) => void; 'utils/withAttr': (key: string, cb: Function) => (this: Element) => void;
'utils/throttleDebounce': typeof ThrottleDebounce; 'utils/throttleDebounce': typeof ThrottleDebounce;
'utils/isObject': typeof isObject;
'utils/focusTrap': typeof FocusTrap;
'models/Notification': typeof Notification; 'models/Notification': typeof Notification;
'models/User': typeof User; 'models/User': typeof User;
'models/Post': typeof Post; 'models/Post': typeof Post;
@@ -67,7 +69,6 @@ declare var _default: {
'components/Link': typeof Link; 'components/Link': typeof Link;
'components/LinkButton': typeof LinkButton; 'components/LinkButton': typeof LinkButton;
'components/Checkbox': typeof Checkbox; 'components/Checkbox': typeof Checkbox;
'components/ColorPreviewInput': typeof ColorPreviewInput;
'components/SelectDropdown': typeof SelectDropdown; 'components/SelectDropdown': typeof SelectDropdown;
'components/ModalManager': typeof ModalManager; 'components/ModalManager': typeof ModalManager;
'components/Button': typeof Button; 'components/Button': typeof Button;
@@ -92,6 +93,7 @@ declare var _default: {
'states/PaginatedListState': typeof PaginatedListState; 'states/PaginatedListState': typeof PaginatedListState;
}; };
export default _default; export default _default;
import * as extend from "./extend";
import Session from "./Session"; import Session from "./Session";
import Store from "./Store"; import Store from "./Store";
import BasicEditorDriver from "./utils/BasicEditorDriver"; import BasicEditorDriver from "./utils/BasicEditorDriver";
@@ -116,13 +118,10 @@ import Stream from "./utils/Stream";
import subclassOf from "./utils/subclassOf"; import subclassOf from "./utils/subclassOf";
import setRouteWithForcedRefresh from "./utils/setRouteWithForcedRefresh"; import setRouteWithForcedRefresh from "./utils/setRouteWithForcedRefresh";
import patchMithril from "./utils/patchMithril"; import patchMithril from "./utils/patchMithril";
import proxifyCompat from "./utils/proxifyCompat";
import extractText from "./utils/extractText"; import extractText from "./utils/extractText";
import formatNumber from "./utils/formatNumber"; import formatNumber from "./utils/formatNumber";
import mapRoutes from "./utils/mapRoutes"; import mapRoutes from "./utils/mapRoutes";
import * as ThrottleDebounce from "./utils/throttleDebounce"; import * as ThrottleDebounce from "./utils/throttleDebounce";
import isObject from "./utils/isObject";
import * as FocusTrap from "./utils/focusTrap";
import Notification from "./models/Notification"; import Notification from "./models/Notification";
import User from "./models/User"; import User from "./models/User";
import Post from "./models/Post"; import Post from "./models/Post";
@@ -149,7 +148,6 @@ import Alert from "./components/Alert";
import Link from "./components/Link"; import Link from "./components/Link";
import LinkButton from "./components/LinkButton"; import LinkButton from "./components/LinkButton";
import Checkbox from "./components/Checkbox"; import Checkbox from "./components/Checkbox";
import ColorPreviewInput from "./components/ColorPreviewInput";
import SelectDropdown from "./components/SelectDropdown"; import SelectDropdown from "./components/SelectDropdown";
import ModalManager from "./components/ModalManager"; import ModalManager from "./components/ModalManager";
import Button from "./components/Button"; import Button from "./components/Button";

View File

@@ -15,5 +15,5 @@ export interface AlertAttrs extends ComponentAttrs {
* some controls, and may be dismissible. * some controls, and may be dismissible.
*/ */
export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<T> { export default class Alert<T extends AlertAttrs = AlertAttrs> extends Component<T> {
view(vnode: Mithril.VnodeDOM<T, this>): JSX.Element; view(vnode: Mithril.Vnode): JSX.Element;
} }

View File

@@ -60,8 +60,8 @@ export interface IButtonAttrs extends ComponentAttrs {
* styles can be applied by providing `className="Button"` to the Button component. * styles can be applied by providing `className="Button"` to the Button component.
*/ */
export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> extends Component<CustomAttrs> { export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>): JSX.Element; view(vnode: Mithril.Vnode<IButtonAttrs, never>): JSX.Element;
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>): void; oncreate(vnode: Mithril.VnodeDOM<IButtonAttrs, this>): void;
/** /**
* Get the template for the button's content. * Get the template for the button's content.
*/ */

View File

@@ -1,6 +0,0 @@
import type Mithril from 'mithril';
import Component, { ComponentAttrs } from '../Component';
export default class ColorPreviewInput extends Component {
value?: string;
view(vnode: Mithril.Vnode<ComponentAttrs, this>): JSX.Element;
}

View File

@@ -1,31 +1,25 @@
/// <reference path="../../../src/common/translator-icu-rich.d.ts" /> /**
import Modal, { IInternalModalAttrs } from './Modal'; * The `EditUserModal` component displays a modal dialog with a login form.
import ItemList from '../utils/ItemList'; */
import Stream from '../utils/Stream'; export default class EditUserModal extends Modal {
import type Mithril from 'mithril'; username: Stream<any> | undefined;
import type User from '../models/User'; email: Stream<any> | undefined;
import type { SaveAttributes } from '../Model'; isEmailConfirmed: Stream<any> | undefined;
export interface IEditUserModalAttrs extends IInternalModalAttrs { setPassword: Stream<boolean> | undefined;
user: User; password: Stream<any> | undefined;
} groups: {} | undefined;
export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEditUserModalAttrs> extends Modal<CustomAttrs> { fields(): ItemList;
protected username: Stream<string>;
protected email: Stream<string>;
protected isEmailConfirmed: Stream<boolean>;
protected setPassword: Stream<boolean>;
protected password: Stream<string>;
protected groups: Record<string, Stream<boolean>>;
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
className(): string;
title(): import("@askvortsov/rich-icu-message-formatter").NestedStringArray;
content(): JSX.Element;
fields(): ItemList<unknown>;
activate(): void; activate(): void;
data(): SaveAttributes; data(): {
onsubmit(e: SubmitEvent): void; relationships: {};
nonAdminEditingAdmin(): boolean | null; };
nonAdminEditingAdmin(): any;
/** /**
* @internal * @internal
* @protected
*/ */
protected userIsAdmin(user: User | null): boolean | null; protected userIsAdmin(user: any): any;
} }
import Modal from "./Modal";
import Stream from "../utils/Stream";
import ItemList from "../utils/ItemList";

View File

@@ -1,68 +1,67 @@
import Component from '../Component';
import { AlertAttrs } from './Alert';
import type Mithril from 'mithril';
import type ModalManagerState from '../states/ModalManagerState';
import type RequestError from '../utils/RequestError';
import type ModalManager from './ModalManager';
export interface IInternalModalAttrs {
state: ModalManagerState;
animateShow: ModalManager['animateShow'];
animateHide: ModalManager['animateHide'];
}
/** /**
* The `Modal` component displays a modal dialog, wrapped in a form. Subclasses * The `Modal` component displays a modal dialog, wrapped in a form. Subclasses
* should implement the `className`, `title`, and `content` methods. * should implement the `className`, `title`, and `content` methods.
*
* @abstract
*/ */
export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IInternalModalAttrs> extends Component<ModalAttrs> { export default class Modal extends Component<import("../Component").ComponentAttrs, undefined> {
/** /**
* Determine whether or not the modal should be dismissible via an 'x' button. * Determine whether or not the modal should be dismissible via an 'x' button.
*/ */
static readonly isDismissible: boolean; static isDismissible: boolean;
protected loading: boolean; constructor();
/** /**
* Attributes for an alert component to show below the header. * Attributes for an alert component to show below the header.
*
* @type {object}
*/ */
alertAttrs: AlertAttrs | null; alertAttrs: object;
oninit(vnode: Mithril.Vnode<ModalAttrs, this>): void;
oncreate(vnode: Mithril.VnodeDOM<ModalAttrs, this>): void;
onbeforeremove(vnode: Mithril.VnodeDOM<ModalAttrs, this>): Promise<void> | void;
/**
* @todo split into FormModal and Modal in 2.0
*/
view(): JSX.Element;
/** /**
* Get the class name to apply to the modal. * Get the class name to apply to the modal.
*
* @return {String}
* @abstract
*/ */
abstract className(): string; className(): string;
/** /**
* Get the title of the modal dialog. * Get the title of the modal dialog.
*
* @return {String}
* @abstract
*/ */
abstract title(): Mithril.Children; title(): string;
/** /**
* Get the content of the modal. * Get the content of the modal.
*
* @return {VirtualElement}
* @abstract
*/ */
abstract content(): Mithril.Children; content(): any;
/** /**
* Handle the modal form's submit event. * Handle the modal form's submit event.
*/
onsubmit(e: SubmitEvent): void;
/**
* Callback executed when the modal is shown and ready to be interacted with.
* *
* @remark Focuses the first input in the modal. * @param {Event} e
*/
onsubmit(): void;
/**
* Focus on the first input when the modal is ready to be used.
*/ */
onready(): void; onready(): void;
/** /**
* Hides the modal. * Hide the modal.
*/ */
hide(): void; hide(): void;
/** /**
* Sets `loading` to false and triggers a redraw. * Stop loading.
*/ */
loaded(): void; loaded(): void;
loading: boolean | undefined;
/** /**
* Shows an alert describing an error returned from the API, and gives focus to * Show an alert describing an error returned from the API, and give focus to
* the first relevant field involved in the error. * the first relevant field.
*
* @param {RequestError} error
*/ */
onerror(error: RequestError): void; onerror(error: any): void;
} }
import Component from "../Component";

View File

@@ -1,25 +1,11 @@
import Component from '../Component';
import { FocusTrap } from '../utils/focusTrap';
import type ModalManagerState from '../states/ModalManagerState';
import type Mithril from 'mithril';
interface IModalManagerAttrs {
state: ModalManagerState;
}
/** /**
* The `ModalManager` component manages a modal dialog. Only one modal dialog * The `ModalManager` component manages a modal dialog. Only one modal dialog
* can be shown at once; loading a new component into the ModalManager will * can be shown at once; loading a new component into the ModalManager will
* overwrite the previous one. * overwrite the previous one.
*/ */
export default class ModalManager extends Component<IModalManagerAttrs> { export default class ModalManager extends Component<import("../Component").ComponentAttrs, undefined> {
protected focusTrap: FocusTrap | undefined; constructor();
/** animateShow(readyCallback: any): void;
* Whether a modal is currently shown by this modal manager.
*/
protected modalShown: boolean;
view(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): Mithril.Children;
oncreate(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): void;
onupdate(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): void;
animateShow(readyCallback: () => void): void;
animateHide(): void; animateHide(): void;
} }
export {}; import Component from "../Component";

View File

@@ -1,4 +1,3 @@
import type Mithril from 'mithril';
import Component from '../Component'; import Component from '../Component';
export interface IPageAttrs { export interface IPageAttrs {
key?: number; key?: number;
@@ -10,19 +9,7 @@ export interface IPageAttrs {
* @abstract * @abstract
*/ */
export default abstract class Page<CustomAttrs extends IPageAttrs = IPageAttrs> extends Component<CustomAttrs> { export default abstract class Page<CustomAttrs extends IPageAttrs = IPageAttrs> extends Component<CustomAttrs> {
/** oninit(vnode: any): void;
* A class name to apply to the body while the route is active. oncreate(vnode: any): void;
*/ onremove(vnode: any): void;
protected bodyClass: string;
/**
* Whether we should scroll to the top of the page when its rendered.
*/
protected scrollTopOnCreate: boolean;
/**
* Whether the browser should restore scroll state on refreshes.
*/
protected useBrowserScrollRestoration: boolean;
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>): void;
onremove(vnode: Mithril.VnodeDOM<CustomAttrs, this>): void;
} }

View File

@@ -1,12 +1,3 @@
/// <reference types="mithril" /> export default class RequestErrorModal extends Modal {
import type RequestError from '../utils/RequestError';
import Modal, { IInternalModalAttrs } from './Modal';
export interface IRequestErrorModalAttrs extends IInternalModalAttrs {
error: RequestError;
formattedError: string[];
}
export default class RequestErrorModal<CustomAttrs extends IRequestErrorModalAttrs = IRequestErrorModalAttrs> extends Modal<CustomAttrs> {
className(): string;
title(): string;
content(): JSX.Element;
} }
import Modal from "./Modal";

View File

@@ -38,13 +38,13 @@ export default class TextEditor extends Component<import("../Component").Compone
* *
* @return {ItemList} * @return {ItemList}
*/ */
controlItems(): ItemList<any>; controlItems(): ItemList;
/** /**
* Build an item list for the toolbar controls. * Build an item list for the toolbar controls.
* *
* @return {ItemList} * @return {ItemList}
*/ */
toolbarItems(): ItemList<any>; toolbarItems(): ItemList;
/** /**
* Handle input into the textarea. * Handle input into the textarea.
* *

View File

@@ -19,11 +19,11 @@
* // something that needs to be run on creation and update * // something that needs to be run on creation and update
* }); * });
* *
* @param object The object that owns the method * @param {object} object The object that owns the method
* @param methods The name or names of the method(s) to extend * @param {string|string[]} methods The name or names of the method(s) to extend
* @param callback A callback which mutates the method's output * @param {function} 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 function extend(object: object, methods: string | string[], callback: Function): void;
/** /**
* Override an object's method by replacing it with a new function, so that the * 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. * new function will be run every time the object's method is called.
@@ -47,8 +47,8 @@ export declare function extend<T extends object, K extends KeyOfType<T, Function
* // something that needs to be run on creation and update * // something that needs to be run on creation and update
* }); * });
* *
* @param object The object that owns the method * @param {object} object The object that owns the method
* @param methods The name or names of the method(s) to override * @param {string|string[]} method The name or names of the method(s) to override
* @param newMethod The method to replace it with * @param {function} 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 function override(object: object, methods: any, newMethod: Function): void;

View File

@@ -1,5 +1,5 @@
export default class Model { export default class Model {
constructor(type: any, model?: null); constructor(type: any, model?: any);
type: any; type: any;
attributes: any[]; attributes: any[];
hasOnes: any[]; hasOnes: any[];

View File

@@ -1,12 +1,9 @@
import type Mithril from 'mithril'; import type Mithril from 'mithril';
import type { ComponentAttrs } from '../Component';
import User from '../models/User'; import User from '../models/User';
export interface AvatarAttrs extends ComponentAttrs {
}
/** /**
* The `avatar` helper displays a user's avatar. * The `avatar` helper displays a user's avatar.
* *
* @param user * @param user
* @param attrs Attributes to apply to the avatar element * @param attrs Attributes to apply to the avatar element
*/ */
export default function avatar(user: User, attrs?: ComponentAttrs): Mithril.Vnode; export default function avatar(user: User, attrs?: Object): Mithril.Vnode;

View File

@@ -10,17 +10,3 @@
* can fix. * can fix.
*/ */
export default function fireDebugWarning(...args: Parameters<typeof console.warn>): void; 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

@@ -1,34 +1,6 @@
import type Mithril from 'mithril'; import type Mithril from 'mithril';
import { ComponentAttrs } from '../Component';
declare type ModdedVnodeAttrs = {
itemClassName?: string;
key?: string;
};
declare type ModdedTag = Mithril.Vnode['tag'] & {
isListItem?: boolean;
isActive?: (attrs: ComponentAttrs) => boolean;
};
declare type ModdedVnode = Mithril.Vnode<ModdedVnodeAttrs> & {
itemName?: string;
itemClassName?: string;
tag: ModdedTag;
};
declare type ModdedChild = ModdedVnode | string | number | boolean | null | undefined;
declare type ModdedChildArray = ModdedChildren[];
declare type ModdedChildren = ModdedChild | ModdedChildArray;
/** /**
* This type represents an element of a list returned by `ItemList.toArray()`, * The `listItems` helper wraps a collection of components in <li> tags,
* coupled with some static properties used on various components.
*/
export declare type ModdedChildrenWithItemName = ModdedChildren & {
itemName?: string;
};
/**
* The `listItems` helper wraps an array of components in the provided tag,
* stripping out any unnecessary `Separator` components. * stripping out any unnecessary `Separator` components.
*
* By default, this tag is an `<li>` tag, but this is customisable through the
* second function parameter, `customTag`.
*/ */
export default function listItems<Attrs extends ComponentAttrs>(rawItems: ModdedChildrenWithItemName[], customTag?: VnodeElementTag<Attrs>, attributes?: Attrs): Mithril.Vnode[]; export default function listItems(items: Mithril.Vnode | Array<Mithril.Vnode>): Array<Mithril.Vnode>;
export {};

View File

@@ -3,4 +3,4 @@ import User from '../models/User';
/** /**
* The `useronline` helper displays a green circle if the user is online * The `useronline` helper displays a green circle if the user is online
*/ */
export default function userOnline(user: User): Mithril.Vnode<{}, {}> | null; export default function userOnline(user: User): Mithril.Vnode;

View File

@@ -4,4 +4,4 @@ import User from '../models/User';
* The `username` helper displays a user's username in a <span class="username"> * The `username` helper displays a user's username in a <span class="username">
* tag. If the user doesn't exist, the username will be displayed as [deleted]. * tag. If the user doesn't exist, the username will be displayed as [deleted].
*/ */
export default function username(user: User | null | undefined | false): Mithril.Vnode; export default function username(user: User): Mithril.Vnode;

View File

@@ -1,48 +1,3 @@
import Model from '../Model';
import ItemList from '../utils/ItemList';
import Mithril from 'mithril';
import Post from './Post';
import User from './User';
export default class Discussion extends Model { export default class Discussion extends Model {
title(): string;
slug(): string;
createdAt(): Date | undefined;
user(): false | User | null;
firstPost(): false | Post | null;
lastPostedAt(): Date | null | undefined;
lastPostedUser(): false | User | null;
lastPost(): false | Post | null;
lastPostNumber(): number | null | undefined;
commentCount(): number | undefined;
replyCount(): Number;
posts(): false | (Post | undefined)[];
mostRelevantPost(): false | Post | null;
lastReadAt(): Date | null | undefined;
lastReadPostNumber(): number | null | undefined;
isUnread(): boolean;
isRead(): boolean;
hiddenAt(): Date | null | undefined;
hiddenUser(): false | User | null;
isHidden(): boolean;
canReply(): boolean | undefined;
canRename(): boolean | undefined;
canHide(): boolean | undefined;
canDelete(): boolean | undefined;
/**
* Remove a post from the discussion's posts relationship.
*/
removePost(id: string): void;
/**
* Get the estimated number of unread posts in this discussion for the current
* user.
*/
unreadCount(): number;
/**
* Get the Badge components that apply to this discussion.
*/
badges(): ItemList<Mithril.Children>;
/**
* Get a list of all of the post IDs in this discussion.
*/
postIds(): string[];
} }
import Model from "../Model";

View File

@@ -1,4 +1,3 @@
import Model from '../Model';
export default class Forum extends Model { export default class Forum extends Model {
apiEndpoint(): string;
} }
import Model from "../Model";

View File

@@ -1,11 +1,9 @@
import Model from '../Model'; export default Group;
export default class Group extends Model { declare class Group extends Model {
static ADMINISTRATOR_ID: string;
static GUEST_ID: string;
static MEMBER_ID: string;
nameSingular(): string;
namePlural(): string;
color(): string | null;
icon(): string | null;
isHidden(): boolean;
} }
declare namespace Group {
const ADMINISTRATOR_ID: string;
const GUEST_ID: string;
const MEMBER_ID: string;
}
import Model from "../Model";

View File

@@ -1,11 +1,3 @@
import Model from '../Model';
import User from './User';
export default class Notification extends Model { export default class Notification extends Model {
contentType(): string;
content(): string;
createdAt(): Date;
isRead(): boolean;
user(): false | User;
fromUser(): false | User | null;
subject(): false | Model | null;
} }
import Model from "../Model";

View File

@@ -1,23 +1,3 @@
import Model from '../Model';
import Discussion from './Discussion';
import User from './User';
export default class Post extends Model { export default class Post extends Model {
number(): number;
discussion(): Discussion;
createdAt(): Date;
user(): false | User;
contentType(): string | null;
content(): string | null | undefined;
contentHtml(): string | null | undefined;
renderFailed(): boolean | undefined;
contentPlain(): string | null | undefined;
editedAt(): Date | null | undefined;
editedUser(): false | User | null;
isEdited(): boolean;
hiddenAt(): Date | null | undefined;
hiddenUser(): false | User | null;
isHidden(): boolean;
canEdit(): boolean | undefined;
canHide(): boolean | undefined;
canDelete(): boolean | undefined;
} }
import Model from "../Model";

View File

@@ -1,46 +1,3 @@
import { Color } from 'color-thief-browser';
import Model from '../Model';
import ItemList from '../utils/ItemList';
import Mithril from 'mithril';
import Group from './Group';
export default class User extends Model { export default class User extends Model {
username(): string;
slug(): string;
displayName(): string;
email(): string | undefined;
isEmailConfirmed(): boolean | undefined;
password(): string | undefined;
avatarUrl(): string | null;
preferences(): Record<string, any> | null | undefined;
groups(): false | (Group | undefined)[];
joinTime(): Date | null | undefined;
lastSeenAt(): Date | null | undefined;
markedAllAsReadAt(): Date | null | undefined;
unreadNotificationCount(): number | undefined;
newNotificationCount(): number | undefined;
discussionCount(): number | undefined;
commentCount(): number | undefined;
canEdit(): boolean | undefined;
canEditCredentials(): boolean | undefined;
canEditGroups(): boolean | undefined;
canDelete(): boolean | undefined;
color(): string;
protected avatarColor: Color | null;
/**
* Check whether or not the user has been seen in the last 5 minutes.
*/
isOnline(): boolean;
/**
* Get the Badge components that apply to this user.
*/
badges(): ItemList<Mithril.Children>;
/**
* Calculate the dominant color of the user's avatar. The dominant color will
* be set to the `avatarColor` property once it has been calculated.
*/
protected calculateAvatarColor(): void;
/**
* Update the user's preferences.
*/
savePreferences(newPreferences: Record<string, unknown>): Promise<this>;
} }
import Model from "../Model";

View File

@@ -1,30 +1,21 @@
import type Mithril from 'mithril'; import type Mithril from 'mithril';
import type { RouteResolver } from '../Application';
import type { default as Component, ComponentAttrs } from '../Component';
/** /**
* Generates a route resolver for a given component. * Generates a route resolver for a given component.
*
* In addition to regular route resolver functionality: * In addition to regular route resolver functionality:
* - It provide the current route name as an attr * - It provide the current route name as an attr
* - It sets a key on the component so a rerender will be triggered on route change. * - It sets a key on the component so a rerender will be triggered on route change.
*/ */
export default class DefaultResolver<Attrs extends ComponentAttrs, Comp extends Component<Attrs & { export default class DefaultResolver {
component: Mithril.Component;
routeName: string; routeName: string;
}>, RouteArgs extends Record<string, unknown> = {}> implements RouteResolver<Attrs, Comp, RouteArgs> { constructor(component: any, routeName: any);
component: new () => Comp;
routeName: string;
constructor(component: new () => Comp, routeName: string);
/** /**
* When a route change results in a changed key, a full page * When a route change results in a changed key, a full page
* rerender occurs. This method can be overriden in subclasses * rerender occurs. This method can be overriden in subclasses
* to prevent rerenders on some route changes. * to prevent rerenders on some route changes.
*/ */
makeKey(): string; makeKey(): string;
makeAttrs(vnode: Mithril.Vnode<Attrs, Comp>): Attrs & { makeAttrs(vnode: any): any;
routeName: string; onmatch(args: any, requestedPath: any, route: any): Mithril.Component<{}, {}>;
}; render(vnode: any): any[];
onmatch(args: RouteArgs, requestedPath: string, route: string): {
new (): Comp;
};
render(vnode: Mithril.Vnode<Attrs, Comp>): Mithril.Children;
} }

View File

@@ -24,11 +24,11 @@ export default class AlertManagerState {
*/ */
show(children: Mithril.Children): AlertIdentifier; show(children: Mithril.Children): AlertIdentifier;
show(attrs: AlertAttrs, children: Mithril.Children): AlertIdentifier; show(attrs: AlertAttrs, children: Mithril.Children): AlertIdentifier;
show(componentClass: typeof Alert, attrs: AlertAttrs, children: Mithril.Children): AlertIdentifier; show(componentClass: Alert, attrs: AlertAttrs, children: Mithril.Children): AlertIdentifier;
/** /**
* Dismiss an alert. * Dismiss an alert.
*/ */
dismiss(key: AlertIdentifier | null): void; dismiss(key: AlertIdentifier): void;
/** /**
* Clear all alerts. * Clear all alerts.
*/ */

View File

@@ -1,52 +1,19 @@
import type Component from '../Component';
import Modal from '../components/Modal';
/**
* Ideally, `show` would take a higher-kinded generic, ala:
* `show<Attrs, C>(componentClass: C<Attrs>, attrs: Attrs): void`
* Unfortunately, TypeScript does not support this:
* https://github.com/Microsoft/TypeScript/issues/1213
* Therefore, we have to use this ugly, messy workaround.
*/
declare type UnsafeModalClass = ComponentClass<any, Modal> & {
isDismissible: boolean;
component: typeof Component.component;
};
/**
* Class used to manage modal state.
*
* Accessible on the `app` object via `app.modal` property.
*/
export default class ModalManagerState { export default class ModalManagerState {
modal: {
componentClass: any;
attrs: any;
} | null;
/** /**
* @internal * Show a modal dialog.
*
* @public
*/ */
modal: null | { public show(componentClass: any, attrs: any): void;
componentClass: UnsafeModalClass;
attrs?: Record<string, unknown>;
};
private closeTimeout?;
/** /**
* Shows a modal dialog. * Close the modal dialog.
* *
* If a modal is already open, the existing one will close and the new modal will replace it. * @public
*
* @example <caption>Show a modal</caption>
* app.modal.show(MyCoolModal, { attr: 'value' });
*
* @example <caption>Show a modal from a lifecycle method (`oncreate`, `view`, etc.)</caption>
* // This "hack" is needed due to quirks with nested redraws in Mithril.
* setTimeout(() => app.modal.show(MyCoolModal, { attr: 'value' }), 0);
*/ */
show(componentClass: UnsafeModalClass, attrs?: Record<string, unknown>): void; public close(): void;
/** closeTimeout: number | undefined;
* Closes the currently open dialog, if one is open.
*/
close(): void;
/**
* Checks if a modal is currently open.
*
* @returns `true` if a modal dialog is currently open, otherwise `false`.
*/
isModalOpen(): boolean;
} }
export {};

View File

@@ -1,5 +1,4 @@
import Model from '../Model'; import Model from '../Model';
import { ApiQueryParamsPlural, ApiResponsePlural } from '../Store';
export interface Page<TModel> { export interface Page<TModel> {
number: number; number: number;
items: TModel[]; items: TModel[];
@@ -11,30 +10,24 @@ export interface PaginationLocation {
startIndex?: number; startIndex?: number;
endIndex?: number; endIndex?: number;
} }
export interface PaginatedListParams { export default abstract class PaginatedListState<T extends Model> {
[key: string]: any;
}
export interface PaginatedListRequestParams extends Omit<ApiQueryParamsPlural, 'include'> {
include?: string | string[];
}
export default abstract class PaginatedListState<T extends Model, P extends PaginatedListParams = PaginatedListParams> {
protected location: PaginationLocation; protected location: PaginationLocation;
protected pageSize: number; protected pageSize: number;
protected pages: Page<T>[]; protected pages: Page<T>[];
protected params: P; protected params: any;
protected initialLoading: boolean; protected initialLoading: boolean;
protected loadingPrev: boolean; protected loadingPrev: boolean;
protected loadingNext: boolean; protected loadingNext: boolean;
protected constructor(params?: P, page?: number, pageSize?: number); protected constructor(params?: any, page?: number, pageSize?: number);
abstract get type(): string; abstract get type(): string;
clear(): void; clear(): void;
loadPrev(): Promise<void>; loadPrev(): Promise<void>;
loadNext(): Promise<void>; loadNext(): Promise<void>;
protected parseResults(pg: number, results: ApiResponsePlural<T>): void; protected parseResults(pg: number, results: T[]): void;
/** /**
* Load a new page of results. * Load a new page of results.
*/ */
protected loadPage(page?: number): Promise<ApiResponsePlural<T>>; protected loadPage(page?: number): Promise<T[]>;
/** /**
* Get the parameters that should be passed in the API request. * Get the parameters that should be passed in the API request.
* Do not include page offset unless subclass overrides loadPage. * Do not include page offset unless subclass overrides loadPage.
@@ -42,7 +35,7 @@ export default abstract class PaginatedListState<T extends Model, P extends Pagi
* @abstract * @abstract
* @see loadPage * @see loadPage
*/ */
protected requestParams(): PaginatedListRequestParams; protected requestParams(): any;
/** /**
* Update the `this.params` object, calling `refresh` if they have changed. * Update the `this.params` object, calling `refresh` if they have changed.
* Use `requestParams` for converting `this.params` into API parameters * Use `requestParams` for converting `this.params` into API parameters
@@ -51,8 +44,8 @@ export default abstract class PaginatedListState<T extends Model, P extends Pagi
* @param page * @param page
* @see requestParams * @see requestParams
*/ */
refreshParams(newParams: P, page: number): Promise<void>; refreshParams(newParams: any, page: number): any;
refresh(page?: number): Promise<void>; refresh(page?: number): any;
getPages(): Page<T>[]; getPages(): Page<T>[];
getLocation(): PaginationLocation; getLocation(): PaginationLocation;
isLoading(): boolean; isLoading(): boolean;
@@ -77,9 +70,9 @@ export default abstract class PaginatedListState<T extends Model, P extends Pagi
/** /**
* Stored state parameters. * Stored state parameters.
*/ */
getParams(): P; getParams(): any;
protected getNextPageNumber(): number; protected getNextPageNumber(): number;
protected getPrevPageNumber(): number; protected getPrevPageNumber(): number;
protected paramsChanged(newParams: P): boolean; protected paramsChanged(newParams: any): boolean;
protected getAllItems(): T[]; protected getAllItems(): T[];
} }

View File

@@ -3,8 +3,8 @@ import ItemList from './ItemList';
export default class BasicEditorDriver implements EditorDriverInterface { export default class BasicEditorDriver implements EditorDriverInterface {
el: HTMLTextAreaElement; el: HTMLTextAreaElement;
constructor(dom: HTMLElement, params: EditorDriverParams); constructor(dom: HTMLElement, params: EditorDriverParams);
protected build(dom: HTMLElement, params: EditorDriverParams): void; build(dom: HTMLElement, params: EditorDriverParams): void;
protected keyHandlers(params: EditorDriverParams): ItemList<(e: KeyboardEvent) => void>; keyHandlers(params: EditorDriverParams): ItemList;
moveCursorTo(position: number): void; moveCursorTo(position: number): void;
getSelectionRange(): Array<number>; getSelectionRange(): Array<number>;
getLastNChars(n: number): string; getLastNChars(n: number): string;

View File

@@ -4,36 +4,10 @@
* footer. * footer.
*/ */
export default class Drawer { export default class Drawer {
/**
* @type {import('./focusTrap').FocusTrap}
*/
focusTrap: import('./focusTrap').FocusTrap;
/**
* @type {HTMLDivElement}
*/
appElement: HTMLDivElement;
/**
* @internal
* @type {MediaQueryList}
*/
drawerAvailableMediaQuery: MediaQueryList;
/**
* Handler for the `resize` event on `window`.
*
* This is used to close the drawer when the viewport is widened past the `phone` size.
* At this point, the drawer turns into the standard header that we see on desktop, but
* the drawer is still registered as 'open' internally.
*
* This causes issues with the focus trap, resulting in focus becoming trapped within
* the header on desktop viewports.
*
* @internal
*/
resizeHandler: (e: any) => void;
/** /**
* Check whether or not the drawer is currently open. * Check whether or not the drawer is currently open.
* *
* @return {boolean} * @return {Boolean}
* @public * @public
*/ */
public isOpen(): boolean; public isOpen(): boolean;

View File

@@ -1,31 +1,20 @@
export interface IItemObject<T> { declare class Item {
content: T; content: any;
itemName: string;
priority: number; priority: number;
} key?: number;
declare class Item<T> { constructor(content: any, priority?: number);
content: T;
priority: number;
constructor(content: T, priority: number);
} }
/** /**
* The `ItemList` class collects items and then arranges them into an array * The `ItemList` class collects items and then arranges them into an array
* by priority. * by priority.
*/ */
export default class ItemList<T> { export default class ItemList {
/** /**
* The items in the list. * The items in the list
*/ */
protected _items: Record<string, Item<T>>; items: {
/** [key: string]: Item;
* A **read-only copy** of items in the list. };
*
* We don't allow adding new items to the ItemList via setting new properties,
* nor do we allow modifying existing items directly.
*
* @deprecated Use {@link ItemList.toObject} instead.
*/
get items(): DeepReadonly<Record<string, Item<T>>>;
/** /**
* Check whether the list is empty. * Check whether the list is empty.
*/ */
@@ -37,165 +26,33 @@ export default class ItemList<T> {
/** /**
* Get the content of an item. * Get the content of an item.
*/ */
get(key: string): T; get(key: string): any;
/**
* Get the priority of an item.
*/
getPriority(key: string): number;
/** /**
* Add an item to the list. * Add an item to the list.
* *
* @param key A unique key for the item. * @param key A unique key for the item.
* @param content The item's content. * @param content The item's content.
* @param priority The priority of the item. Items with a higher priority * @param [priority] The priority of the item. Items with a higher
* will be positioned before items with a lower priority. * priority will be positioned before items with a lower priority.
*/ */
add(key: string, content: T, priority?: number): this; add(key: string, content: any, priority?: number): this;
/** /**
* Replace an item and/or priority in the list, only if it is already present. * Replace an item in the list, only if it is already present.
*
* If `content` or `priority` are `null`, these values will not be replaced.
*
* If the provided `key` is not present, nothing will happen.
*
* @deprecated Please use the {@link ItemList.setContent} and {@link ItemList.setPriority}
* methods to replace items and their priorities. This method will be removed in Flarum 2.0.
*
* @param key The key of the item in the list
* @param content The item's new content
* @param priority The item's new priority
*
* @example <caption>Replace priority and not content.</caption>
* items.replace('myItem', null, 10);
*
* @example <caption>Replace content and not priority.</caption>
* items.replace('myItem', <p>My new value.</p>);
*
* @example <caption>Replace content and priority.</caption>
* items.replace('myItem', <p>My new value.</p>, 10);
*/ */
replace(key: string, content?: T | null, priority?: number | null): this; replace(key: string, content?: any, priority?: number): this;
/**
* Replaces an item's content, if the provided item key exists.
*
* If the provided `key` is not present, nothing will happen.
*
* @param key The key of the item in the list
* @param content The item's new content
*
* @example <caption>Replace item content.</caption>
* items.setContent('myItem', <p>My new value.</p>);
*
* @example <caption>Replace item content and priority.</caption>
* items
* .setContent('myItem', <p>My new value.</p>)
* .setPriority('myItem', 10);
*
* @throws If the provided `key` is not present in the ItemList.
*/
setContent(key: string, content: T): this;
/**
* Replaces an item's priority, if the provided item key exists.
*
* If the provided `key` is not present, nothing will happen.
*
* @param key The key of the item in the list
* @param priority The item's new priority
*
* @example <caption>Replace item priority.</caption>
* items.setPriority('myItem', 10);
*
* @example <caption>Replace item priority and content.</caption>
* items
* .setPriority('myItem', 10)
* .setContent('myItem', <p>My new value.</p>);
*
* @throws If the provided `key` is not present in the ItemList.
*/
setPriority(key: string, priority: number): this;
/** /**
* Remove an item from the list. * Remove an item from the list.
*/ */
remove(key: string): this; remove(key: string): this;
/** /**
* Merge another list's items into this one. * Merge another list's items into this one.
*
* The list passed to this function will overwrite items which already exist
* with the same key.
*/ */
merge(otherList: ItemList<T>): ItemList<T>; merge(items: this): this;
/** /**
* Convert the list into an array of item content arranged by priority. * Convert the list into an array of item content arranged by priority. Each
* * item's content will be assigned an `itemName` property equal to the item's
* This **does not** preserve the original types of primitives and proxies * unique key.
* all content values to make `itemName` accessible on them.
*
* **NOTE:** If your ItemList holds primitive types (such as numbers, booleans
* or strings), these will be converted to their object counterparts if you do
* not provide `true` to this function.
*
* **NOTE:** Modifying any objects in the final array may also update the
* content of the original ItemList.
*
* @param keepPrimitives Converts item content to objects and sets the
* `itemName` property on them.
*
* @see https://github.com/flarum/core/issues/3030
*/ */
toArray(keepPrimitives?: false): (T & { toArray(): any[];
itemName: string;
})[];
/**
* Convert the list into an array of item content arranged by priority.
*
* Content values that are already objects will be proxied and have
* `itemName` accessible on them. Primitive values will not have the
* `itemName` property accessible.
*
* **NOTE:** Modifying any objects in the final array may also update the
* content of the original ItemList.
*
* @param keepPrimitives Converts item content to objects and sets the
* `itemName` property on them.
*/
toArray(keepPrimitives: true): (T extends object ? T & Readonly<{
itemName: string;
}> : T)[];
/**
* A read-only map of all keys to their respective items in no particular order.
*
* We don't allow adding new items to the ItemList via setting new properties,
* nor do we allow modifying existing items directly. You should use the
* {@link ItemList.add}, {@link ItemList.setContent} and
* {@link ItemList.setPriority} methods instead.
*
* To match the old behaviour of the `ItemList.items` property, call
* `Object.values(ItemList.toObject())`.
*
* @example
* const items = new ItemList();
* items.add('b', 'My cool value', 20);
* items.add('a', 'My value', 10);
* items.toObject();
* // {
* // a: { content: 'My value', priority: 10, itemName: 'a' },
* // b: { content: 'My cool value', priority: 20, itemName: 'b' },
* // }
*/
toObject(): DeepReadonly<Record<string, IItemObject<T>>>;
/**
* Proxies an item's content, adding the `itemName` readonly property to it.
*
* @example
* createItemContentProxy({ foo: 'bar' }, 'myItem');
* // { foo: 'bar', itemName: 'myItem' }
*
* @param content The item's content (objects only)
* @param key The item's key
* @returns Proxied content
*
* @internal
*/
private createItemContentProxy;
} }
export {}; export {};

View File

@@ -1,21 +1,9 @@
import type Mithril from 'mithril'; export default class RequestError {
import type { AlertAttrs } from '../components/Alert'; status: string;
export declare type InternalFlarumRequestOptions<ResponseType> = Mithril.RequestOptions<ResponseType> & { options: object;
url: string;
};
export default class RequestError<ResponseType = string> {
status: number;
options: InternalFlarumRequestOptions<ResponseType>;
xhr: XMLHttpRequest; xhr: XMLHttpRequest;
responseText: string | null; responseText: string | null;
response: { response: object | null;
[key: string]: unknown; alert: any;
errors?: { constructor(status: string, responseText: string | null, options: object, xhr: XMLHttpRequest);
detail?: string;
code?: string;
[key: string]: unknown;
}[];
} | null;
alert: AlertAttrs | null;
constructor(status: number, responseText: string | null, options: InternalFlarumRequestOptions<ResponseType>, xhr: XMLHttpRequest);
} }

View File

@@ -4,12 +4,12 @@
*/ */
export default class ScrollListener { export default class ScrollListener {
/** /**
* @param {(top: number) => void} callback The callback to run when the scroll position * @param {Function} callback The callback to run when the scroll position
* changes. * changes.
* @public * @public
*/ */
constructor(callback: (top: number) => void); constructor(callback: Function);
callback: (top: number) => void; callback: Function;
ticking: boolean; ticking: boolean;
/** /**
* On each animation frame, as long as the listener is active, run the * On each animation frame, as long as the listener is active, run the

View File

@@ -22,23 +22,31 @@
* @see https://mithril.js.org/lifecycle-methods.html#onbeforeupdate * @see https://mithril.js.org/lifecycle-methods.html#onbeforeupdate
*/ */
export default class SubtreeRetainer { export default class SubtreeRetainer {
protected callbacks: (() => any)[];
protected data: Record<string, any>;
/** /**
* @param callbacks Functions returning data to keep track of. * @param {...callbacks} callbacks Functions returning data to keep track of.
*/ */
constructor(...callbacks: (() => any)[]); constructor(...callbacks: any[]);
callbacks: any[];
data: {};
/** /**
* Return whether any data has changed since the last check. * Return whether any data has changed since the last check.
* If so, Mithril needs to re-diff the vnode and its children. * If so, Mithril needs to re-diff the vnode and its children.
*
* @return {boolean}
* @public
*/ */
needsRebuild(): boolean; public needsRebuild(): boolean;
/** /**
* Add another callback to be checked. * Add another callback to be checked.
*
* @param {...Function} callbacks
* @public
*/ */
check(...callbacks: (() => any)[]): void; public check(...callbacks: Function[]): void;
/** /**
* Invalidate the subtree, forcing it to be redrawn. * Invalidate the subtree, forcing it to be rerendered.
*
* @public
*/ */
invalidate(): void; public invalidate(): void;
} }

View File

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

View File

@@ -1,5 +1,7 @@
import type Mithril from 'mithril';
/** /**
* Extract the text nodes from a virtual element. * Extract the text nodes from a virtual element.
*
* @param {VirtualElement} vdom
* @return {String}
*/ */
export default function extractText(vdom: Mithril.Children): string; export default function extractText(vdom: any): string;

View File

@@ -1,20 +0,0 @@
import { createFocusTrap as _createFocusTrap } from 'focus-trap';
/**
* Creates a focus trap for the given element with the given options.
*
* This function applies some default options that are different to the library.
* Your own options still override these custom defaults:
*
* ```json
* {
escapeDeactivates: false,
* }
* ```
*
* @param element The element to be the focus trap, or a selector that will be used to find the element.
*
* @see https://github.com/focus-trap/focus-trap#readme - Library documentation
*/
declare function createFocusTrap(...args: Parameters<typeof _createFocusTrap>): ReturnType<typeof _createFocusTrap>;
export * from 'focus-trap';
export { createFocusTrap };

View File

@@ -1,9 +1,9 @@
/** /**
* The `formatNumber` utility localizes a number into a string with the * The `formatNumber` utility localizes a number into a string with the
* appropriate punctuation based on the provided locale otherwise will default to the users locale. * appropriate punctuation.
* *
* @example * @example
* formatNumber(1234); * formatNumber(1234);
* // 1,234 * // 1,234
*/ */
export default function formatNumber(number: number, locale?: string): string; export default function formatNumber(number: number): string;

View File

@@ -1,6 +1,5 @@
import dayjs from 'dayjs';
/** /**
* The `humanTime` utility converts a date to a localized, human-readable time- * The `humanTime` utility converts a date to a localized, human-readable time-
* ago string. * ago string.
*/ */
export default function humanTime(time: dayjs.ConfigType): string; export default function humanTime(time: Date): string;

View File

@@ -1,24 +0,0 @@
/**
* Returns if the passed value is an object.
*
* In this context, "object" refers to **any non-primitive value**, including
* arrays, function, maps, dates, and more.
*
* @example
* isObject({}); // true
* @example
* isObject([]); // true
* @example
* isObject(function () {}); // true
* @example
* isObject(Object(1)); // true
* @example
* isObject(null); // false
* @example
* isObject(1); // false
* @example
* isObject("hello world"); // false
*
* @see https://github.com/jashkenas/underscore/blob/943977e34e2279503528a71ddcc2dd5f96483945/underscore.js#L87-L91
*/
export default function isObject(obj: unknown): obj is object;

View File

@@ -1,13 +1,11 @@
import type { FlarumGenericRoute, RouteResolver } from '../Application';
import type Component from '../Component';
/** /**
* The `mapRoutes` utility converts a map of named application routes into a * The `mapRoutes` utility converts a map of named application routes into a
* format that can be understood by Mithril, and wraps them in route resolvers * format that can be understood by Mithril, and wraps them in route resolvers
* to provide each route with the current route name. * to provide each route with the current route name.
* *
* @see https://mithril.js.org/route.html#signature * @see https://mithril.js.org/route.html#signature
* @param {Object} routes
* @param {String} [basePath]
* @return {Object}
*/ */
export default function mapRoutes(routes: Record<string, FlarumGenericRoute>, basePath?: string): Record<string, RouteResolver<Record<string, unknown>, Component<{ export default function mapRoutes(routes: Object, basePath?: string | undefined): Object;
[key: string]: unknown;
routeName: string;
}, undefined>, Record<string, unknown>>>;

View File

@@ -1 +1,6 @@
export default function proxifyCompat(compat: Record<string, unknown>, namespace: string): Record<string, unknown>; declare const _default: (compat: {
[key: string]: any;
}, namespace: string) => {
[key: string]: any;
};
export default _default;

View File

@@ -9,7 +9,6 @@ interface StyleArgs {
scanFor: string; scanFor: string;
surroundWithNewlines: boolean; surroundWithNewlines: boolean;
orderedList: boolean; orderedList: boolean;
unorderedList: boolean;
trimFirst: boolean; trimFirst: boolean;
} }
export default function styleSelectedText(textarea: HTMLTextAreaElement, styleArgs: StyleArgs): void; export default function styleSelectedText(textarea: HTMLTextAreaElement, styleArgs: StyleArgs): void;

View File

@@ -1,60 +1,51 @@
import History from './utils/History';
import Pane from './utils/Pane';
import { makeRouteHelpers } from './routes';
import Application from '../common/Application';
import NotificationListState from './states/NotificationListState';
import GlobalSearchState from './states/GlobalSearchState';
import DiscussionListState from './states/DiscussionListState';
import ComposerState from './states/ComposerState';
import type Notification from './components/Notification';
import type Post from './components/Post';
import Discussion from '../common/models/Discussion';
export default class ForumApplication extends Application { export default class ForumApplication extends Application {
/** /**
* A map of notification types to their components. * A map of notification types to their components.
*
* @type {Object}
*/ */
notificationComponents: Record<string, typeof Notification>; notificationComponents: Object;
/** /**
* A map of post types to their components. * A map of post types to their components.
*
* @type {Object}
*/ */
postComponents: Record<string, typeof Post>; postComponents: Object;
/** /**
* An object which controls the state of the page's side pane. * An object which controls the state of the page's side pane.
*
* @type {Pane}
*/ */
pane: Pane | null; pane: Pane;
/** /**
* The app's history stack, which keeps track of which routes the user visits * The app's history stack, which keeps track of which routes the user visits
* so that they can easily navigate back to the previous route. * so that they can easily navigate back to the previous route.
*
* @type {History}
*/ */
history: History; history: History;
/** /**
* An object which controls the state of the user's notifications. * An object which controls the state of the user's notifications.
*
* @type {NotificationListState}
*/ */
notifications: NotificationListState; notifications: NotificationListState;
/**
* An object which stores previously searched queries and provides convenient
* tools for retrieving and managing search values.
*/
search: GlobalSearchState; search: GlobalSearchState;
/**
* An object which controls the state of the composer.
*/
composer: ComposerState; composer: ComposerState;
/** /**
* An object which controls the state of the cached discussion list, which * An object which controls the state of the cached discussion list, which
* is used in the index page and the slideout pane. * is used in the index page and the slideout pane.
*
* @type {DiscussionListState}
*/ */
discussions: DiscussionListState; discussions: DiscussionListState;
route: typeof Application.prototype.route & ReturnType<typeof makeRouteHelpers>;
constructor();
/**
* @inheritdoc
*/
mount(): void;
/** /**
* Check whether or not the user is currently viewing a discussion. * Check whether or not the user is currently viewing a discussion.
*
* @param {Discussion} discussion
* @return {Boolean}
*/ */
viewingDiscussion(discussion: Discussion): boolean; viewingDiscussion(discussion: any): boolean;
/** /**
* Callback for when an external authenticator (social login) action has * Callback for when an external authenticator (social login) action has
* completed. * completed.
@@ -62,6 +53,18 @@ export default class ForumApplication extends Application {
* If the payload indicates that the user has been logged in, then the page * If the payload indicates that the user has been logged in, then the page
* will be reloaded. Otherwise, a SignUpModal will be opened, prefilled * will be reloaded. Otherwise, a SignUpModal will be opened, prefilled
* with the provided details. * with the provided details.
*
* @param {Object} payload A dictionary of attrs to pass into the sign up
* modal. A truthy `loggedIn` attr indicates that the user has logged
* in, and thus the page is reloaded.
* @public
*/ */
authenticationComplete(payload: Record<string, unknown>): void; public authenticationComplete(payload: Object): void;
} }
import Application from "../common/Application";
import Pane from "./utils/Pane";
import History from "./utils/History";
import NotificationListState from "./states/NotificationListState";
import GlobalSearchState from "./states/GlobalSearchState";
import ComposerState from "./states/ComposerState";
import DiscussionListState from "./states/DiscussionListState";

View File

@@ -1,5 +1,5 @@
declare var _default: { declare var _default: {
extend: any; extend: typeof import("../common/extend");
Session: typeof import("../common/Session").default; Session: typeof import("../common/Session").default;
Store: typeof import("../common/Store").default; Store: typeof import("../common/Store").default;
'utils/BasicEditorDriver': typeof BasicEditorDriver; 'utils/BasicEditorDriver': typeof BasicEditorDriver;
@@ -32,15 +32,17 @@ declare var _default: {
'utils/subclassOf': typeof import("../common/utils/subclassOf").default; 'utils/subclassOf': typeof import("../common/utils/subclassOf").default;
'utils/setRouteWithForcedRefresh': typeof import("../common/utils/setRouteWithForcedRefresh").default; 'utils/setRouteWithForcedRefresh': typeof import("../common/utils/setRouteWithForcedRefresh").default;
'utils/patchMithril': typeof import("../common/utils/patchMithril").default; 'utils/patchMithril': typeof import("../common/utils/patchMithril").default;
'utils/proxifyCompat': typeof import("../common/utils/proxifyCompat").default; 'utils/proxifyCompat': (compat: {
[key: string]: any;
}, namespace: string) => {
[key: string]: any;
};
'utils/classList': (...classes: import("clsx").ClassValue[]) => string; 'utils/classList': (...classes: import("clsx").ClassValue[]) => string;
'utils/extractText': typeof import("../common/utils/extractText").default; 'utils/extractText': typeof import("../common/utils/extractText").default;
'utils/formatNumber': typeof import("../common/utils/formatNumber").default; 'utils/formatNumber': typeof import("../common/utils/formatNumber").default;
'utils/mapRoutes': typeof import("../common/utils/mapRoutes").default; 'utils/mapRoutes': typeof import("../common/utils/mapRoutes").default;
'utils/withAttr': (key: string, cb: Function) => (this: Element) => void; 'utils/withAttr': (key: string, cb: Function) => (this: Element) => void;
'utils/throttleDebounce': typeof import("../common/utils/throttleDebounce"); 'utils/throttleDebounce': typeof import("../common/utils/throttleDebounce");
'utils/isObject': typeof import("../common/utils/isObject").default;
'utils/focusTrap': typeof import("../common/utils/focusTrap");
'models/Notification': typeof import("../common/models/Notification").default; 'models/Notification': typeof import("../common/models/Notification").default;
'models/User': typeof import("../common/models/User").default; 'models/User': typeof import("../common/models/User").default;
'models/Post': typeof import("../common/models/Post").default; 'models/Post': typeof import("../common/models/Post").default;
@@ -67,7 +69,6 @@ declare var _default: {
'components/Link': typeof import("../common/components/Link").default; 'components/Link': typeof import("../common/components/Link").default;
'components/LinkButton': typeof import("../common/components/LinkButton").default; 'components/LinkButton': typeof import("../common/components/LinkButton").default;
'components/Checkbox': typeof import("../common/components/Checkbox").default; 'components/Checkbox': typeof import("../common/components/Checkbox").default;
'components/ColorPreviewInput': typeof import("../common/components/ColorPreviewInput").default;
'components/SelectDropdown': typeof import("../common/components/SelectDropdown").default; 'components/SelectDropdown': typeof import("../common/components/SelectDropdown").default;
'components/ModalManager': typeof import("../common/components/ModalManager").default; 'components/ModalManager': typeof import("../common/components/ModalManager").default;
'components/Button': typeof import("../common/components/Button").default; 'components/Button': typeof import("../common/components/Button").default;
@@ -92,10 +93,10 @@ declare var _default: {
'states/PaginatedListState': typeof import("../common/states/PaginatedListState").default; 'states/PaginatedListState': typeof import("../common/states/PaginatedListState").default;
} & { } & {
'utils/PostControls': { 'utils/PostControls': {
controls(post: any, context: any): import("../common/utils/ItemList").default<any>; controls(post: any, context: any): import("../common/utils/ItemList").default;
userControls(post: any, context: any): import("../common/utils/ItemList").default<any>; userControls(post: any, context: any): import("../common/utils/ItemList").default;
moderationControls(post: any, context: any): import("../common/utils/ItemList").default<any>; moderationControls(post: any, context: any): import("../common/utils/ItemList").default;
destructiveControls(post: any, context: any): import("../common/utils/ItemList").default<any>; destructiveControls(post: any, context: any): import("../common/utils/ItemList").default;
editAction(): Promise<any>; editAction(): Promise<any>;
hideAction(): Promise<any>; hideAction(): Promise<any>;
restoreAction(): Promise<any>; restoreAction(): Promise<any>;
@@ -105,10 +106,10 @@ declare var _default: {
'utils/slidable': typeof slidable; 'utils/slidable': typeof slidable;
'utils/History': typeof History; 'utils/History': typeof History;
'utils/DiscussionControls': { 'utils/DiscussionControls': {
controls(discussion: any, context: any): import("../common/utils/ItemList").default<any>; controls(discussion: any, context: any): import("../common/utils/ItemList").default;
userControls(discussion: any, context: any): import("../common/utils/ItemList").default<any>; userControls(discussion: any, context: any): import("../common/utils/ItemList").default;
moderationControls(discussion: any): import("../common/utils/ItemList").default<any>; moderationControls(discussion: any): import("../common/utils/ItemList").default;
destructiveControls(discussion: any): import("../common/utils/ItemList").default<any>; destructiveControls(discussion: any): import("../common/utils/ItemList").default;
replyAction(goToLast: boolean, forceRefresh: boolean): Promise<any>; replyAction(goToLast: boolean, forceRefresh: boolean): Promise<any>;
hideAction(): Promise<any>; hideAction(): Promise<any>;
restoreAction(): Promise<any>; restoreAction(): Promise<any>;
@@ -117,10 +118,10 @@ declare var _default: {
}; };
'utils/alertEmailConfirmation': typeof alertEmailConfirmation; 'utils/alertEmailConfirmation': typeof alertEmailConfirmation;
'utils/UserControls': { 'utils/UserControls': {
controls(user: any, context: any): import("../common/utils/ItemList").default<any>; controls(user: any, context: any): import("../common/utils/ItemList").default;
userControls(): import("../common/utils/ItemList").default<any>; userControls(): import("../common/utils/ItemList").default;
moderationControls(user: any): import("../common/utils/ItemList").default<any>; moderationControls(user: any): import("../common/utils/ItemList").default;
destructiveControls(user: any): import("../common/utils/ItemList").default<any>; destructiveControls(user: any): import("../common/utils/ItemList").default;
deleteAction(user: any): void; deleteAction(user: any): void;
showDeletionAlert(user: any, type: string): void; showDeletionAlert(user: any, type: string): void;
editAction(user: any): void; editAction(user: any): void;

View File

@@ -26,7 +26,7 @@ export default class AvatarEditor extends Component<import("../../common/Compone
* *
* @return {ItemList} * @return {ItemList}
*/ */
controlItems(): ItemList<any>; controlItems(): ItemList;
/** /**
* Enable dragover style * Enable dragover style
* *

View File

@@ -2,8 +2,7 @@
* The `ChangeEmailModal` component shows a modal dialog which allows the user * The `ChangeEmailModal` component shows a modal dialog which allows the user
* to change their email address. * to change their email address.
*/ */
export default class ChangeEmailModal extends Modal<import("../../common/components/Modal").IInternalModalAttrs> { export default class ChangeEmailModal extends Modal {
constructor();
/** /**
* Whether or not the email has been changed successfully. * Whether or not the email has been changed successfully.
* *

View File

@@ -2,7 +2,6 @@
* The `ChangePasswordModal` component shows a modal dialog which allows the * The `ChangePasswordModal` component shows a modal dialog which allows the
* user to send themself a password reset email. * user to send themself a password reset email.
*/ */
export default class ChangePasswordModal extends Modal<import("../../common/components/Modal").IInternalModalAttrs> { export default class ChangePasswordModal extends Modal {
constructor();
} }
import Modal from "../../common/components/Modal"; import Modal from "../../common/components/Modal";

View File

@@ -34,7 +34,7 @@ export default class CommentPost extends Post {
* *
* @return {ItemList} * @return {ItemList}
*/ */
headerItems(): ItemList<any>; headerItems(): ItemList;
} }
import Post from "./Post"; import Post from "./Post";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -85,7 +85,7 @@ export default class Composer extends Component<import("../../common/Component")
* *
* @return {ItemList} * @return {ItemList}
*/ */
controlItems(): ItemList<any>; controlItems(): ItemList;
/** /**
* Initialize default Composer height. * Initialize default Composer height.
*/ */

View File

@@ -35,7 +35,7 @@ export default class ComposerBody extends Component<import("../../common/Compone
* *
* @return {ItemList} * @return {ItemList}
*/ */
headerItems(): ItemList<any>; headerItems(): ItemList;
/** /**
* Handle the submit event of the text editor. * Handle the submit event of the text editor.
* *

View File

@@ -13,6 +13,6 @@
export default class ComposerPostPreview extends Component<import("../../common/Component").ComponentAttrs, undefined> { export default class ComposerPostPreview extends Component<import("../../common/Component").ComponentAttrs, undefined> {
static initAttrs(attrs: any): void; static initAttrs(attrs: any): void;
constructor(); constructor();
updateInterval: NodeJS.Timer | undefined; updateInterval: number | undefined;
} }
import Component from "../../common/Component"; import Component from "../../common/Component";

View File

@@ -12,7 +12,7 @@ export default class DiscussionHero extends Component<import("../../common/Compo
* *
* @return {ItemList} * @return {ItemList}
*/ */
items(): ItemList<any>; items(): ItemList;
} }
import Component from "../../common/Component"; import Component from "../../common/Component";
import ItemList from "../../common/utils/ItemList"; import ItemList from "../../common/utils/ItemList";

View File

@@ -51,8 +51,7 @@ export default class DiscussionListItem extends Component<import("../../common/C
* *
* @return {ItemList} * @return {ItemList}
*/ */
infoItems(): ItemList<any>; infoItems(): ItemList;
replyCountItem(): JSX.Element;
} }
import Component from "../../common/Component"; import Component from "../../common/Component";
import SubtreeRetainer from "../../common/utils/SubtreeRetainer"; import SubtreeRetainer from "../../common/utils/SubtreeRetainer";

View File

@@ -1,64 +1,53 @@
import type Mithril from 'mithril';
import Page, { IPageAttrs } from '../../common/components/Page';
import ItemList from '../../common/utils/ItemList';
import PostStreamState from '../states/PostStreamState';
import Discussion from '../../common/models/Discussion';
import { ApiResponseSingle } from '../../common/Store';
export interface IDiscussionPageAttrs extends IPageAttrs {
id: string;
near?: number;
}
/** /**
* The `DiscussionPage` component displays a whole discussion page, including * The `DiscussionPage` component displays a whole discussion page, including
* the discussion list pane, the hero, the posts, and the sidebar. * the discussion list pane, the hero, the posts, and the sidebar.
*/ */
export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = IDiscussionPageAttrs> extends Page<CustomAttrs> { export default class DiscussionPage extends Page<import("../../common/components/Page").IPageAttrs> {
constructor();
useBrowserScrollRestoration: boolean | undefined;
/** /**
* The discussion that is being viewed. * The discussion that is being viewed.
*
* @type {Discussion}
*/ */
protected discussion: Discussion | null; discussion: any;
/**
* A public API for interacting with the post stream.
*/
protected stream: PostStreamState | null;
/** /**
* The number of the first post that is currently visible in the viewport. * The number of the first post that is currently visible in the viewport.
*
* @type {number}
*/ */
protected near: number; near: number | undefined;
protected useBrowserScrollRestoration: boolean; bodyClass: string | undefined;
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
onremove(vnode: Mithril.VnodeDOM<CustomAttrs, this>): void;
view(): JSX.Element;
/** /**
* List of components shown while the discussion is loading. * List of components shown while the discussion is loading.
* *
* @returns {ItemList} * @returns {ItemList}
*/ */
loadingItems(): ItemList<unknown>; loadingItems(): ItemList;
/** /**
* Function that renders the `sidebarItems` ItemList. * Function that renders the `sidebarItems` ItemList.
* *
* @returns {import('mithril').Children} * @returns {import('mithril').Children}
*/ */
sidebar(): JSX.Element; sidebar(): import('mithril').Children;
/** /**
* Renders the discussion's hero. * Renders the discussion's hero.
* *
* @returns {import('mithril').Children} * @returns {import('mithril').Children}
*/ */
hero(): JSX.Element; hero(): import('mithril').Children;
/** /**
* List of items rendered as the main page content. * List of items rendered as the main page content.
* *
* @returns {ItemList} * @returns {ItemList}
*/ */
pageContent(): ItemList<unknown>; pageContent(): ItemList;
/** /**
* List of items rendered inside the main page content container. * List of items rendered inside the main page content container.
* *
* @returns {ItemList} * @returns {ItemList}
*/ */
mainContent(): ItemList<unknown>; mainContent(): ItemList;
/** /**
* Load the discussion from the API or use the preloaded one. * Load the discussion from the API or use the preloaded one.
*/ */
@@ -69,23 +58,29 @@ export default class DiscussionPage<CustomAttrs extends IDiscussionPageAttrs = I
* *
* @return {Object} * @return {Object}
*/ */
requestParams(): { requestParams(): Object;
bySlug: boolean;
page: {
near: number;
};
};
/** /**
* Initialize the component to display the given discussion. * Initialize the component to display the given discussion.
*
* @param {Discussion} discussion
*/ */
show(discussion: ApiResponseSingle<Discussion>): void; show(discussion: any): void;
stream: PostStreamState | undefined;
/** /**
* Build an item list for the contents of the sidebar. * Build an item list for the contents of the sidebar.
*
* @return {ItemList}
*/ */
sidebarItems(): ItemList<Mithril.Vnode<{}, {}>>; sidebarItems(): ItemList;
/** /**
* When the posts that are visible in the post stream change (i.e. the user * 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. * scrolls up or down), then we update the URL and mark the posts as read.
*
* @param {Integer} startNumber
* @param {Integer} endNumber
*/ */
positionChanged(startNumber: number, endNumber: number): void; positionChanged(startNumber: any, endNumber: any): void;
} }
import Page from "../../common/components/Page";
import ItemList from "../../common/utils/ItemList";
import PostStreamState from "../states/PostStreamState";

View File

@@ -1,12 +1,11 @@
import { SearchSource } from './Search'; import { SearchSource } from './Search';
import type Mithril from 'mithril'; import type Mithril from 'mithril';
import Discussion from '../../common/models/Discussion';
/** /**
* The `DiscussionsSearchSource` finds and displays discussion search results in * The `DiscussionsSearchSource` finds and displays discussion search results in
* the search dropdown. * the search dropdown.
*/ */
export default class DiscussionsSearchSource implements SearchSource { export default class DiscussionsSearchSource implements SearchSource {
protected results: Map<string, Discussion[]>; protected results: Map<string, unknown[]>;
search(query: string): Promise<void>; search(query: string): Promise<Map<string, unknown[]>>;
view(query: string): Array<Mithril.Vnode>; view(query: string): Array<Mithril.Vnode>;
} }

View File

@@ -1,25 +1,24 @@
/// <reference path="../../../src/common/translator-icu-rich.d.ts" />
import Modal, { IInternalModalAttrs } from '../../common/components/Modal';
import Stream from '../../common/utils/Stream';
import Mithril from 'mithril';
import RequestError from '../../common/utils/RequestError';
export interface IForgotPasswordModalAttrs extends IInternalModalAttrs {
email?: string;
}
/** /**
* The `ForgotPasswordModal` component displays a modal which allows the user to * The `ForgotPasswordModal` component displays a modal which allows the user to
* enter their email address and request a link to reset their password. * enter their email address and request a link to reset their password.
*
* ### Attrs
*
* - `email`
*/ */
export default class ForgotPasswordModal<CustomAttrs extends IForgotPasswordModalAttrs = IForgotPasswordModalAttrs> extends Modal<CustomAttrs> { export default class ForgotPasswordModal extends Modal {
/** /**
* The value of the email input. * The value of the email input.
*
* @type {Function}
*/ */
email: Stream<string>; email: Function | undefined;
success: boolean; /**
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void; * Whether or not the password reset email was sent successfully.
className(): string; *
title(): import("@askvortsov/rich-icu-message-formatter").NestedStringArray; * @type {Boolean}
content(): JSX.Element; */
onsubmit(e: SubmitEvent): void; success: boolean | undefined;
onerror(error: RequestError): void; alert: any;
} }
import Modal from "../../common/components/Modal";

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