mirror of
https://github.com/flarum/core.git
synced 2025-08-06 08:27:42 +02:00
chore: handle deprecations from 1.x (#3909)
* chore: drop deprecated `post_number_index` column * chore: remove deprecated `FlagsWillBeDeleted` event * chore: `Migration::addSettings` can still be needed * chore: `settings->get` default can still be needed * chore: deprecated `$default` in `Settings::serializeToForum` extender * chore: deprecated request `actor` attribute * chore: already handled * chore: remove `RecompileFrontendAssets::whenSettingsSaved` * chore: remove `getReadIds` * chore: `Model::dateAttribute` extender * chore: `evented` js util * chore: `WelcomeHero` js hidden prop * chore: attributes pushData with relations * chore: app request options `extract` * chore: itemlist deprecations * chore: `search` state * chore: `getMentionText` * chore: deprecated non-registered store type * chore: `Button` title * chore: `Modal` deprecations * chore * chore: deprecated `less` mixins * Apply fixes from StyleCI * fix * fix: phpstan * fix * fix --------- Co-authored-by: StyleCI Bot <bot@styleci.io>
This commit is contained in:
@@ -4,10 +4,9 @@ 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 = false;
|
||||
protected static readonly isDismissibleViaCloseButton: boolean = false;
|
||||
protected static readonly isDismissibleViaEscKey: boolean = false;
|
||||
protected static readonly isDismissibleViaBackdropClick: boolean = false;
|
||||
|
||||
className() {
|
||||
return 'LoadingModal Modal--small';
|
||||
|
@@ -45,13 +45,6 @@ export type FlarumGenericRoute = RouteItem<any, any, any>;
|
||||
export interface FlarumRequestOptions<ResponseType> extends Omit<Mithril.RequestOptions<ResponseType>, 'extract'> {
|
||||
errorHandler?: (error: RequestError) => void;
|
||||
url: string;
|
||||
// TODO: [Flarum 2.0] Remove deprecated option
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -434,7 +427,7 @@ export default class Application {
|
||||
}
|
||||
|
||||
protected transformRequestOptions<ResponseType>(flarumOptions: FlarumRequestOptions<ResponseType>): InternalFlarumRequestOptions<ResponseType> {
|
||||
const { background, deserialize, extract, modifyText, ...tmpOptions } = { ...flarumOptions };
|
||||
const { background, deserialize, modifyText, ...tmpOptions } = { ...flarumOptions };
|
||||
|
||||
// Unless specified otherwise, requests should run asynchronously in the
|
||||
// background, so that they don't prevent redraws from occurring.
|
||||
@@ -446,11 +439,6 @@ export default class Application {
|
||||
|
||||
const defaultDeserialize = (response: string) => response as ResponseType;
|
||||
|
||||
// When extracting the data from the response, we can check the server
|
||||
// response code and show an error message to the user if something's gone
|
||||
// awry.
|
||||
const originalExtract = modifyText || extract;
|
||||
|
||||
const options: InternalFlarumRequestOptions<ResponseType> = {
|
||||
background: background ?? defaultBackground,
|
||||
deserialize: deserialize ?? defaultDeserialize,
|
||||
@@ -474,11 +462,14 @@ export default class Application {
|
||||
options.method = 'POST';
|
||||
}
|
||||
|
||||
// When extracting the data from the response, we can check the server
|
||||
// response code and show an error message to the user if something's gone
|
||||
// awry.
|
||||
options.extract = (xhr: XMLHttpRequest) => {
|
||||
let responseText;
|
||||
|
||||
if (originalExtract) {
|
||||
responseText = originalExtract(xhr.responseText);
|
||||
if (modifyText) {
|
||||
responseText = modifyText(xhr.responseText);
|
||||
} else {
|
||||
responseText = xhr.responseText;
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import app from '../common/app';
|
||||
import { FlarumRequestOptions } from './Application';
|
||||
import { fireDeprecationWarning } from './helpers/fireDebugWarning';
|
||||
import Store, { ApiPayloadSingle, ApiResponseSingle, MetaInformation } from './Store';
|
||||
|
||||
export interface ModelIdentifier {
|
||||
@@ -113,15 +112,11 @@ export default abstract class Model {
|
||||
if ('attributes' in data) {
|
||||
this.data.attributes ||= {};
|
||||
|
||||
// @deprecated
|
||||
// Filter out relationships that got in by accident.
|
||||
for (const key in data.attributes) {
|
||||
const val = data.attributes[key];
|
||||
if (val && val instanceof Model) {
|
||||
fireDeprecationWarning('Providing models as attributes to `Model.pushData()` or `Model.pushAttributes()` is deprecated.', '3249');
|
||||
delete data.attributes[key];
|
||||
data.relationships ||= {};
|
||||
data.relationships[key] = { data: Model.getIdentifier(val) };
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import app from '../common/app';
|
||||
import { FlarumRequestOptions } from './Application';
|
||||
import { fireDeprecationWarning } from './helpers/fireDebugWarning';
|
||||
import Model, { ModelData, SavedModelData } from './Model';
|
||||
|
||||
export interface MetaInformation {
|
||||
@@ -100,7 +99,7 @@ export default class Store {
|
||||
pushPayload<M extends Model | Model[]>(payload: ApiPayload): ApiResponse<FlatArray<M, 1>> {
|
||||
if (payload.included) payload.included.map(this.pushObject.bind(this));
|
||||
|
||||
const models = payload.data instanceof Array ? payload.data.map((o) => this.pushObject(o, false)) : this.pushObject(payload.data, false);
|
||||
const models = payload.data instanceof Array ? payload.data.map((o) => this.pushObject(o)) : this.pushObject(payload.data);
|
||||
const result = models as ApiResponse<FlatArray<M, 1>>;
|
||||
|
||||
// Attach the original payload to the model that we give back. This is
|
||||
@@ -120,14 +119,11 @@ export default class Store {
|
||||
* registered for this resource type.
|
||||
*/
|
||||
pushObject<M extends Model>(data: SavedModelData): M | null;
|
||||
pushObject<M extends Model>(data: SavedModelData, allowUnregistered: false): M;
|
||||
pushObject<M extends Model>(data: SavedModelData, allowUnregistered = true): M | null {
|
||||
pushObject<M extends Model>(data: SavedModelData): M | null {
|
||||
if (!this.models[data.type]) {
|
||||
if (!allowUnregistered) {
|
||||
setTimeout(() =>
|
||||
fireDeprecationWarning(`Pushing object of type \`${data.type}\` not allowed, as type not yet registered in the store.`, '3206')
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
throw new Error(`Pushing object of type \`${data.type}\` not allowed, as type not yet registered in the store.`);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@@ -27,18 +27,6 @@ export interface IButtonAttrs extends ComponentAttrs {
|
||||
* Default: `false`
|
||||
*/
|
||||
loading?: boolean;
|
||||
/**
|
||||
* **DEPRECATED:** Please use the `aria-label` attribute instead. For tooltips, use
|
||||
* the `<Tooltip>` component.
|
||||
*
|
||||
* Accessible text for the button. This should always be present if the button only
|
||||
* contains an icon.
|
||||
*
|
||||
* The textual content of this attribute is passed to the DOM element as `aria-label`.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
title?: string | Mithril.ChildArray;
|
||||
/**
|
||||
* Accessible text for the button. This should always be present if the button only
|
||||
* contains an icon.
|
||||
@@ -68,14 +56,11 @@ export interface IButtonAttrs extends ComponentAttrs {
|
||||
*/
|
||||
export default class Button<CustomAttrs extends IButtonAttrs = IButtonAttrs> extends Component<CustomAttrs> {
|
||||
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
||||
let { type, title, 'aria-label': ariaLabel, icon: iconName, disabled, loading, className, class: _class, ...attrs } = this.attrs;
|
||||
let { type, 'aria-label': ariaLabel, icon: iconName, disabled, loading, className, class: _class, ...attrs } = this.attrs;
|
||||
|
||||
// If no `type` attr provided, set to "button"
|
||||
type ||= 'button';
|
||||
|
||||
// Use `title` attribute as `aria-label` if none provided
|
||||
ariaLabel ||= title;
|
||||
|
||||
// If given a translation object, extract the text.
|
||||
if (typeof ariaLabel === 'object') {
|
||||
ariaLabel = extractText(ariaLabel);
|
||||
|
@@ -17,10 +17,6 @@ export interface IInternalModalAttrs {
|
||||
}
|
||||
|
||||
export interface IDismissibleOptions {
|
||||
/**
|
||||
* @deprecated Check specific individual attributes instead. Will be removed in Flarum 2.0.
|
||||
*/
|
||||
isDismissible: boolean;
|
||||
viaCloseButton: boolean;
|
||||
viaEscKey: boolean;
|
||||
viaBackdropClick: boolean;
|
||||
@@ -34,14 +30,6 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
|
||||
ModalAttrs,
|
||||
CustomState
|
||||
> {
|
||||
// TODO: [Flarum 2.0] remove `isDismissible` static attribute
|
||||
/**
|
||||
* Determine whether or not the modal should be dismissible via an 'x' button.
|
||||
*
|
||||
* @deprecated Use the individual `isDismissibleVia...` attributes instead and remove references to this.
|
||||
*/
|
||||
static readonly isDismissible: boolean = true;
|
||||
|
||||
/**
|
||||
* Can the model be dismissed with a close button (X)?
|
||||
*
|
||||
@@ -58,18 +46,7 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
|
||||
protected static readonly isDismissibleViaBackdropClick: boolean = true;
|
||||
|
||||
static get dismissibleOptions(): IDismissibleOptions {
|
||||
// If someone sets this to `false`, provide the same behaviour as previous versions of Flarum.
|
||||
if (!this.isDismissible) {
|
||||
return {
|
||||
isDismissible: false,
|
||||
viaCloseButton: false,
|
||||
viaEscKey: false,
|
||||
viaBackdropClick: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isDismissible: true,
|
||||
viaCloseButton: this.isDismissibleViaCloseButton,
|
||||
viaEscKey: this.isDismissibleViaEscKey,
|
||||
viaBackdropClick: this.isDismissibleViaBackdropClick,
|
||||
@@ -83,31 +60,6 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
|
||||
*/
|
||||
alertAttrs: AlertAttrs | null = null;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<ModalAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
// TODO: [Flarum 2.0] Remove the code below.
|
||||
// This code prevents extensions which do not implement all abstract methods of this class from breaking
|
||||
// the forum frontend. Without it, function calls would would error rather than returning `undefined.`
|
||||
|
||||
const missingMethods: string[] = [];
|
||||
|
||||
['className', 'title', 'content', 'onsubmit'].forEach((method) => {
|
||||
if (!(this as any)[method]) {
|
||||
(this as any)[method] = function (): void {};
|
||||
missingMethods.push(method);
|
||||
}
|
||||
});
|
||||
|
||||
if (missingMethods.length > 0) {
|
||||
fireDebugWarning(
|
||||
`Modal \`${this.constructor.name}\` does not implement all abstract methods of the Modal super class. Missing methods: ${missingMethods.join(
|
||||
', '
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
oncreate(vnode: Mithril.VnodeDOM<ModalAttrs, this>) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
|
@@ -26,24 +26,6 @@ export default class ItemList<T> {
|
||||
*/
|
||||
protected _items: Record<string, Item<T>> = {};
|
||||
|
||||
// TODO: [Flarum 2.0] Remove `.items` getter.
|
||||
/**
|
||||
* 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>>> {
|
||||
return new Proxy(this._items, {
|
||||
set() {
|
||||
console.warn('Modifying `ItemList.items` is not allowed.');
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the list is empty.
|
||||
*/
|
||||
@@ -86,44 +68,6 @@ export default class ItemList<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: [Flarum 2.0] Remove deprecated `.replace()` method.
|
||||
/**
|
||||
* Replace an item and/or priority 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 = null, priority: number | null = null): this {
|
||||
if (!this.has(key)) return this;
|
||||
|
||||
if (content !== null) {
|
||||
this._items[key].content = content;
|
||||
}
|
||||
|
||||
if (priority !== null) {
|
||||
this._items[key].priority = priority;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an item's content, if the provided item key exists.
|
||||
*
|
||||
@@ -147,8 +91,11 @@ export default class ItemList<T> {
|
||||
throw new Error(`[ItemList] Cannot set content of Item. Key \`${key}\` is not present.`);
|
||||
}
|
||||
|
||||
// Saves on bundle size to call the deprecated method internally
|
||||
return this.replace(key, content);
|
||||
if (content !== null) {
|
||||
this._items[key].content = content;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,109 +0,0 @@
|
||||
import { fireDeprecationWarning } from '../helpers/fireDebugWarning';
|
||||
|
||||
const deprecatedNotice = 'The `evented` util is deprecated and no longer supported.';
|
||||
const deprecationIssueId = '2547';
|
||||
|
||||
/**
|
||||
* The `evented` mixin provides methods allowing an object to trigger events,
|
||||
* running externally registered event handlers.
|
||||
*
|
||||
* @deprecated v1.2, to be removed in v2.0
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Arrays of registered event handlers, grouped by the event name.
|
||||
*
|
||||
* @type {Record<string, unknown>}
|
||||
* @protected
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
handlers: null,
|
||||
|
||||
/**
|
||||
* Get all of the registered handlers for an event.
|
||||
*
|
||||
* @param {string} event The name of the event.
|
||||
* @return {Function[]}
|
||||
* @protected
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
getHandlers(event) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
|
||||
this.handlers = this.handlers || {};
|
||||
|
||||
this.handlers[event] = this.handlers[event] || [];
|
||||
|
||||
return this.handlers[event];
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger an event.
|
||||
*
|
||||
* @param {string} event The name of the event.
|
||||
* @param {any[]} args Arguments to pass to event handlers.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
trigger(event, ...args) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
|
||||
this.getHandlers(event).forEach((handler) => handler.apply(this, args));
|
||||
},
|
||||
|
||||
/**
|
||||
* Register an event handler.
|
||||
*
|
||||
* @param {string} event The name of the event.
|
||||
* @param {Function} handler The function to handle the event.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
on(event, handler) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
|
||||
this.getHandlers(event).push(handler);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register an event handler so that it will run only once, and then
|
||||
* unregister itself.
|
||||
*
|
||||
* @param {string} event The name of the event.
|
||||
* @param {Function} handler The function to handle the event.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
one(event, handler) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
|
||||
const wrapper = function () {
|
||||
handler.apply(this, arguments);
|
||||
|
||||
this.off(event, wrapper);
|
||||
};
|
||||
|
||||
this.getHandlers(event).push(wrapper);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister an event handler.
|
||||
*
|
||||
* @param {string} event The name of the event.
|
||||
* @param {Function} handler The function that handles the event.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
off(event, handler) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
|
||||
const handlers = this.getHandlers(event);
|
||||
const index = handlers.indexOf(handler);
|
||||
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
@@ -8,7 +8,6 @@ import KeyboardNavigatable from '../../common/utils/KeyboardNavigatable';
|
||||
import SearchState from '../states/SearchState';
|
||||
import DiscussionsSearchSource from './DiscussionsSearchSource';
|
||||
import UsersSearchSource from './UsersSearchSource';
|
||||
import { fireDeprecationWarning } from '../../common/helpers/fireDebugWarning';
|
||||
import type Mithril from 'mithril';
|
||||
import Icon from '../../common/components/Icon';
|
||||
|
||||
@@ -64,24 +63,6 @@ export default class Search<T extends SearchAttrs = SearchAttrs> extends Compone
|
||||
*/
|
||||
protected searchState!: SearchState;
|
||||
|
||||
/**
|
||||
* The instance of `SearchState` for this component.
|
||||
*
|
||||
* @deprecated Replace with`this.searchState` instead.
|
||||
*/
|
||||
// TODO: [Flarum 2.0] Remove this.
|
||||
// @ts-expect-error This is a get accessor, while superclass defines this as a property. This is needed to prevent breaking changes, however.
|
||||
get state() {
|
||||
fireDeprecationWarning('`state` property of the Search component is deprecated', '3212');
|
||||
return this.searchState;
|
||||
}
|
||||
set state(state: SearchState) {
|
||||
// Workaround to prevent triggering deprecation warnings due to Mithril
|
||||
// setting state to undefined when creating components
|
||||
state !== undefined && fireDeprecationWarning('`state` property of the Search component is deprecated', '3212');
|
||||
this.searchState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the search input has focus.
|
||||
*/
|
||||
|
@@ -13,11 +13,6 @@ const LOCAL_STORAGE_KEY = 'welcomeHidden';
|
||||
* forum.
|
||||
*/
|
||||
export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
|
||||
/**
|
||||
* @deprecated Extend the `isHidden` method instead.
|
||||
*/
|
||||
hidden: boolean = false;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
}
|
||||
@@ -60,7 +55,6 @@ export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
|
||||
isHidden(): boolean {
|
||||
if (!app.forum.attribute<string>('welcomeTitle')?.trim()) return true;
|
||||
if (localStorage.getItem(LOCAL_STORAGE_KEY)) return true;
|
||||
if (this.hidden) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
Reference in New Issue
Block a user