Fix global typings for extensions (#2992)

* Fix global typings for extensions

* Deprecate global `app` typings

See https://github.com/flarum/core/issues/2857#issuecomment-889841326

* Add `app` export for common namespace

* Add missing `app` imports within core

* Add missing `app` imports to JS files

* Fix incorrect import

* Fix admin file importing forum `app`

* Add `flarum` global variable

* Format

* Update JSDoc comment

* Update JSDoc comment

Co-authored-by: Alexander Skvortsov <sasha.skvortsov109@gmail.com>

* Fix frontend JS error

* Empty commit

Co-authored-by: Alexander Skvortsov <sasha.skvortsov109@gmail.com>
This commit is contained in:
David Wheatley 2021-08-19 11:10:40 +02:00 committed by GitHub
parent 8fe2332f98
commit 2831ce226c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 243 additions and 77 deletions

View File

@ -1,53 +0,0 @@
// Mithril
import Mithril from 'mithril';
// Other third-party libs
import * as _dayjs from 'dayjs';
import 'dayjs/plugin/relativeTime';
import * as _$ from 'jquery';
// Globals from flarum/core
import Application from '../../src/common/Application';
import type { TooltipJQueryFunction } from '../tooltips';
/**
* flarum/core exposes several extensions globally:
*
* - jQuery for convenient DOM manipulation
* - Mithril for VDOM and components
* - dayjs for date/time operations
*
* Since these are already part of the global namespace, extensions won't need
* to (and should not) bundle these themselves.
*/
declare global {
// $ is already defined by `@types/jquery`
const m: Mithril.Static;
const dayjs: typeof _dayjs;
// Extend JQuery with our custom functions, defined with $.fn
interface JQuery {
tooltip: TooltipJQueryFunction;
}
/**
* For more info, see: https://www.typescriptlang.org/docs/handbook/jsx.html#attribute-type-checking
*
* In a nutshell, we need to add `ElementAttributesProperty` to tell Typescript
* what property on component classes to look at for attribute typings. For our
* Component class, this would be `attrs` (e.g. `this.attrs...`)
*/
namespace JSX {
interface ElementAttributesProperty {
attrs: Record<string, unknown>;
}
}
}
/**
* All global variables owned by flarum/core.
*/
declare global {
const app: Application;
}

View File

@ -41,6 +41,6 @@
"format": "prettier --write src",
"format-check": "prettier --check src",
"clean-typings": "npx rimraf dist-typings && mkdir dist-typings",
"build-typings": "npm run clean-typings && cp -r @types dist-typings/@types && tsc"
"build-typings": "npm run clean-typings && cp -r src/@types dist-typings/@types && tsc"
}
}

82
js/src/@types/global.d.ts vendored Normal file
View File

@ -0,0 +1,82 @@
/**
* @deprecated Please import `app` from a namespace instead of using it as a global variable.
*
* @example App in forum JS
* ```
* import app from 'flarum/forum/app';
* ```
*
* @example App in admin JS
* ```
* import app from 'flarum/admin/app';
* ```
*
* @example App in common JS
* ```
* import app from 'flarum/common/app';
* ```
*/
declare const app: never;
declare const m: import('mithril').Static;
declare const dayjs: typeof import('dayjs');
type ESModule = { __esModule: true; [key: string]: unknown };
/**
* The global `flarum` variable.
*
* Contains the compiled ES Modules for all Flarum extensions and core.
*
* @example <caption>Check if `flarum-tags` is present</captions>
* if ('flarum-tags' in flarum.extensions) {
* // Tags is installed and enabled!
* }
*/
interface FlarumObject {
/**
* Contains the compiled ES Module for Flarum's core.
*
* You shouldn't need to access this directly for any reason.
*/
core: Readonly<ESModule>;
/**
* Contains the compiled ES Modules for all Flarum extensions.
*
* @example <caption>Check if `flarum-tags` is present</captions>
* if ('flarum-tags' in flarum.extensions) {
* // Tags is installed and enabled!
* }
*/
extensions: Readonly<Record<string, ESModule>>;
}
declare const flarum: FlarumObject;
// Extend JQuery with our custom functions, defined with $.fn
interface JQuery {
/**
* Flarum's tooltip JQuery plugin.
*
* Do not use this directly. Instead use the `<Tooltip>` component that
* is exported from `flarum/common/components/Tooltip`.
*
* This will be removed in a future version of Flarum.
*
* @deprecated
*/
tooltip: import('./tooltips/index').TooltipJQueryFunction;
}
/**
* For more info, see: https://www.typescriptlang.org/docs/handbook/jsx.html#attribute-type-checking
*
* In a nutshell, we need to add `ElementAttributesProperty` to tell Typescript
* what property on component classes to look at for attribute typings. For our
* Component class, this would be `attrs` (e.g. `this.attrs...`)
*/
interface JSX {
ElementAttributesProperty: {
attrs: Record<string, unknown>;
};
}

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import ExtensionLinkButton from './ExtensionLinkButton';
import Component from '../../common/Component';
import LinkButton from '../../common/components/LinkButton';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Page from '../../common/components/Page';
import Button from '../../common/components/Button';
import Switch from '../../common/components/Switch';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Button from '../../common/components/Button';
import EditCustomCssModal from './EditCustomCssModal';
import EditCustomHeaderModal from './EditCustomHeaderModal';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import FieldSet from '../../common/components/FieldSet';
import ItemList from '../../common/utils/ItemList';
import AdminPage from './AdminPage';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import StatusWidget from './StatusWidget';
import ExtensionsWidget from './ExtensionsWidget';
import ItemList from '../../common/utils/ItemList';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import SettingsModal from './SettingsModal';
export default class EditCustomCssModal extends SettingsModal {

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import SettingsModal from './SettingsModal';
export default class EditCustomFooterModal extends SettingsModal {

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import SettingsModal from './SettingsModal';
export default class EditCustomHeaderModal extends SettingsModal {

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Modal from '../../common/components/Modal';
import Button from '../../common/components/Button';
import Badge from '../../common/components/Badge';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import isExtensionEnabled from '../utils/isExtensionEnabled';
import LinkButton from '../../common/components/LinkButton';
import icon from '../../common/helpers/icon';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Button from '../../common/components/Button';
import Link from '../../common/components/Link';
import LinkButton from '../../common/components/LinkButton';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import PermissionGrid from './PermissionGrid';
import Button from '../../common/components/Button';
import ItemList from '../../common/utils/ItemList';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import DashboardWidget from './DashboardWidget';
import isExtensionEnabled from '../utils/isExtensionEnabled';
import getCategorizedExtensions from '../utils/getCategorizedExtensions';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Component from '../../common/Component';
import LinkButton from '../../common/components/LinkButton';
import SessionDropdown from './SessionDropdown';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Modal from '../../common/components/Modal';
export default class LoadingModal extends Modal {

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import FieldSet from '../../common/components/FieldSet';
import Button from '../../common/components/Button';
import Alert from '../../common/components/Alert';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Dropdown from '../../common/components/Dropdown';
import Button from '../../common/components/Button';
import Separator from '../../common/components/Separator';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Component from '../../common/Component';
import PermissionDropdown from './PermissionDropdown';
import SettingDropdown from './SettingDropdown';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import GroupBadge from '../../common/components/GroupBadge';
import EditGroupModal from './EditGroupModal';
import Group from '../../common/models/Group';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import avatar from '../../common/helpers/avatar';
import username from '../../common/helpers/username';
import Dropdown from '../../common/components/Dropdown';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import SelectDropdown from '../../common/components/SelectDropdown';
import Button from '../../common/components/Button';
import saveSettings from '../utils/saveSettings';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Modal from '../../common/components/Modal';
import Button from '../../common/components/Button';
import Stream from '../../common/utils/Stream';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import DashboardWidget from './DashboardWidget';
import listItems from '../../common/helpers/listItems';
import ItemList from '../../common/utils/ItemList';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import Button from '../../common/components/Button';
export default class UploadImageButton extends Button {

View File

@ -1,3 +1,5 @@
import app from '../../admin/app';
import EditUserModal from '../../common/components/EditUserModal';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import Button from '../../common/components/Button';

View File

@ -1,3 +1,4 @@
import app from '../../admin/app';
import DefaultResolver from '../../common/resolvers/DefaultResolver';
/**

View File

@ -1,3 +1,5 @@
import app from '../../admin/app';
export default function getCategorizedExtensions() {
let extensions = {};

View File

@ -1,3 +1,5 @@
import app from '../../admin/app';
export default function isExtensionEnabled(name) {
const enabled = JSON.parse(app.data.settings.extensions_enabled);

View File

@ -1,3 +1,5 @@
import app from '../../admin/app';
export default function saveSettings(settings) {
const oldSettings = JSON.parse(JSON.stringify(app.data.settings));

View File

@ -1,3 +1,5 @@
import app from '../common/app';
import ItemList from './utils/ItemList';
import Button from './components/Button';
import ModalManager from './components/ModalManager';

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
export interface ComponentAttrs extends Mithril.Attributes {}

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
/**
* The `Fragment` class represents a chunk of DOM that is rendered once with Mithril and then takes

View File

@ -1,3 +1,5 @@
import app from '../common/app';
/**
* The `Model` class represents a local data resource. It provides methods to
* persist changes via the API.

View File

@ -1,3 +1,5 @@
import app from '../common/app';
/**
* The `Session` class defines the current user session. It stores a reference
* to the current authenticated user, and provides methods to log in/out.

View File

@ -1,3 +1,4 @@
import app from '../common/app';
/**
* The `Store` class defines a local data store, and provides methods to
* retrieve data from the API.

31
js/src/common/app.ts Normal file
View File

@ -0,0 +1,31 @@
import type Application from './Application';
// Used to fix typings
const w = window as any;
/**
* Proxy app. Common JS is run first, at which point `window.app` is not
* set as this is done by the namespaced JS.
*
* When the corrent value is set, this code would retain the reference to
* the original invalid value.
*
* By using a proxy, we can ensure that our `window.app` value is always
* up-to-date with the latest reference.
*/
const appProxy = new Proxy(
{},
{
get(_, properties) {
return Reflect.get(w.app, properties, w.app);
},
set(_, properties, value) {
return Reflect.set(w.app, properties, value, w.app);
},
}
);
/**
* The instance of Application within the common namespace.
*/
export default appProxy as Application;

View File

@ -2,7 +2,7 @@ import Component, { ComponentAttrs } from '../Component';
import Button from './Button';
import listItems from '../helpers/listItems';
import extract from '../utils/extract';
import Mithril from 'mithril';
import type Mithril from 'mithril';
export interface AlertAttrs extends ComponentAttrs {
/** The type of alert this is. Will be used to give the alert a class name of `Alert--{type}`. */

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Component from '../Component';
import icon from '../helpers/icon';
import listItems from '../helpers/listItems';

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Modal from './Modal';
import Button from './Button';
import GroupBadge from './GroupBadge';

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Component, { ComponentAttrs } from '../Component';
import classList from '../utils/classList';

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Component from '../Component';
import Button from './Button';
import LinkButton from './LinkButton';

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Component from '../Component';
import PageState from '../states/PageState';

View File

@ -1,3 +1,5 @@
import app from '../../common/app';
import Component from '../Component';
import ItemList from '../utils/ItemList';
import listItems from '../helpers/listItems';

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
import User from '../models/User';
/**

View File

@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
/**
* The `fullTime` helper displays a formatted time string wrapped in a <time>

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
import { truncate } from '../utils/string';
/**

View File

@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
import humanTimeUtil from '../utils/humanTime';
/**

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
/**
* The `icon` helper displays an icon.

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
import Separator from '../components/Separator';
import classList from '../utils/classList';

View File

@ -1,3 +1,5 @@
import app from '../../common/app';
/**
* The `punctuateSeries` helper formats a list of strings (e.g. names) to read
* fluently in the application's locale.

View File

@ -1,4 +1,4 @@
import * as Mithril from 'mithril';
import type Mithril from 'mithril';
import User from '../models/User';
import icon from './icon';

View File

@ -1,4 +1,5 @@
import * as Mithril from 'mithril';
import app from '../../common/app';
import type Mithril from 'mithril';
import User from '../models/User';
/**

View File

@ -21,8 +21,9 @@ import patchMithril from './utils/patchMithril';
patchMithril(window);
import * as Extend from './extend/index';
import app from './app';
export { Extend };
export { Extend, app };
import './utils/arrayFlatPolyfill';

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Model from '../Model';
import computed from '../utils/computed';
import ItemList from '../utils/ItemList';

View File

@ -1,4 +1,4 @@
import Mithril from 'mithril';
import type Mithril from 'mithril';
/**
* Generates a route resolver for a given component.

View File

@ -1,4 +1,4 @@
import Mithril from 'mithril';
import type Mithril from 'mithril';
import Alert, { AlertAttrs } from '../components/Alert';
/**

View File

@ -1,3 +1,4 @@
import app from '../../common/app';
import Model from '../Model';
export interface Page<TModel> {

View File

@ -1,3 +1,5 @@
import app from '../../common/app';
/**
* The `abbreviateNumber` utility converts a number to a shorter localized form.
*

View File

@ -1,4 +1,4 @@
import Mithril from 'mithril';
import type Mithril from 'mithril';
/**
* Mithril 2 does not completely rerender the page if a route change leads to the same route

View File

@ -1,3 +1,4 @@
import app from '../forum/app';
import History from './utils/History';
import Pane from './utils/Pane';
import DiscussionPage from './components/DiscussionPage';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import avatar from '../../common/helpers/avatar';
import icon from '../../common/helpers/icon';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Modal from '../../common/components/Modal';
import Button from '../../common/components/Button';
import Stream from '../../common/utils/Stream';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Modal from '../../common/components/Modal';
import Button from '../../common/components/Button';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Post from './Post';
import classList from '../../common/utils/classList';
import PostUser from './PostUser';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import ItemList from '../../common/utils/ItemList';
import ComposerButton from './ComposerButton';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import ComposerBody from './ComposerBody';
import extractText from '../../common/utils/extractText';
import Stream from '../../common/utils/Stream';

View File

@ -1,9 +1,9 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import DiscussionListItem from './DiscussionListItem';
import Button from '../../common/components/Button';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import Placeholder from '../../common/components/Placeholder';
import Discussion from '../../common/models/Discussion';
/**
* The `DiscussionList` component displays a list of discussions.
@ -15,7 +15,7 @@ import Discussion from '../../common/models/Discussion';
export default class DiscussionList extends Component {
view() {
/**
* @type DiscussionListState
* @type {import('../states/DiscussionListState').default}
*/
const state = this.attrs.state;

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import Link from '../../common/components/Link';
import avatar from '../../common/helpers/avatar';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import DiscussionList from './DiscussionList';
import Component from '../../common/Component';
import DiscussionPage from './DiscussionPage';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Page from '../../common/components/Page';
import ItemList from '../../common/utils/ItemList';
import DiscussionHero from './DiscussionHero';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Notification from './Notification';
/**

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import EventPost from './EventPost';
import extractText from '../../common/utils/extractText';

View File

@ -1,8 +1,9 @@
import app from '../../forum/app';
import highlight from '../../common/helpers/highlight';
import LinkButton from '../../common/components/LinkButton';
import Link from '../../common/components/Link';
import { SearchSource } from './Search';
import Mithril from 'mithril';
import type Mithril from 'mithril';
/**
* The `DiscussionsSearchSource` finds and displays discussion search results in

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import ComposerBody from './ComposerBody';
import Button from '../../common/components/Button';
import Link from '../../common/components/Link';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Post from './Post';
import { ucfirst } from '../../common/utils/string';
import usernameHelper from '../../common/helpers/username';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Modal from '../../common/components/Modal';
import Button from '../../common/components/Button';
import extractText from '../../common/utils/extractText';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import Button from '../../common/components/Button';
import LogInModal from './LogInModal';

View File

@ -1,4 +1,4 @@
import { extend } from '../../common/extend';
import app from '../../forum/app';
import Page from '../../common/components/Page';
import ItemList from '../../common/utils/ItemList';
import listItems from '../../common/helpers/listItems';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Button from '../../common/components/Button';
/**

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Modal from '../../common/components/Modal';
import ForgotPasswordModal from './ForgotPasswordModal';
import SignUpModal from './SignUpModal';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import avatar from '../../common/helpers/avatar';
import icon from '../../common/helpers/icon';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import Checkbox from '../../common/components/Checkbox';
import icon from '../../common/helpers/icon';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import listItems from '../../common/helpers/listItems';
import Button from '../../common/components/Button';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Dropdown from '../../common/components/Dropdown';
import icon from '../../common/helpers/icon';
import classList from '../../common/utils/classList';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Page from '../../common/components/Page';
import NotificationList from './NotificationList';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
import Dropdown from '../../common/components/Dropdown';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import humanTime from '../../common/utils/humanTime';
import Tooltip from '../../common/components/Tooltip';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import humanTime from '../../common/helpers/humanTime';
import fullTime from '../../common/helpers/fullTime';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import Link from '../../common/components/Link';
import avatar from '../../common/helpers/avatar';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import ScrollListener from '../../common/utils/ScrollListener';
import PostLoading from './LoadingPost';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import icon from '../../common/helpers/icon';
import formatNumber from '../../common/utils/formatNumber';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Component from '../../common/Component';
import Link from '../../common/components/Link';
import UserCard from './UserCard';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import UserPage from './UserPage';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import Button from '../../common/components/Button';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import Modal from '../../common/components/Modal';
import Button from '../../common/components/Button';
import Stream from '../../common/utils/Stream';

View File

@ -1,3 +1,4 @@
import app from '../../forum/app';
import ComposerBody from './ComposerBody';
import Button from '../../common/components/Button';
import Link from '../../common/components/Link';

View File

@ -1,5 +1,4 @@
/*global s9e*/
import app from '../../forum/app';
import Component from '../../common/Component';
import avatar from '../../common/helpers/avatar';
import username from '../../common/helpers/username';

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