- Extensions now have their own pages
- The API for extensions to register permissions and settings has been overhauled via the `flarum/admin/utils/ExtensionData` util
- An extension grid has been added as a widget to the Dashboard page
- Split DispatchRoute. This allows us to run middleware after we figure out which route we're on, but before we actually execute the controller for that route.
- By making the route name explicitly available to middlewares, applications like CSRF and floodgate can set patterns based on route names instead of the path, which is an implementation detail.
- Support using route name match for CSRF extender, deprecate path match
- Standardize signatures and variable names for extenders that take callbacks
- Adjust model extender docblock to clarify that default calue can't be an invokable class.
- Make invokable classes provided to Model->relationship
- Add integration tests to ensure Model->relationship and User->groupProcessor extenders accept callbacks
- Extract code for wrapping callbacks into central util
- Deprecated all events involved with Formatter
- Refactor ->configure() method on extender not to use events
- Add extender methods for ->render() and ->parse()
- Add integration tests
- Reintroduce cancellable scroll top on page change
- IndexPage: rely on browser to retain scroll position on page reload
- Remove obsolete browser hack
- Fix broken selector
- When on mobile, only retain scroll for IndexPage if we're coming from a discussion
- Move app.cache.scrollTop save into `onbeforeremove` so we make sure to do it before DOM is detached
The default first post number is '1', so we scroll to that if we're calling `m.route.set` without a `near` parameter, as that means we're scrolling to the top.
This was present in beta 13's implementation, but accidentially omitted in 988b6c9.
We also remove unnecessary typecasting for simpler logic and increased consistency with beta 13.
The current implementation for checking whether we are on a discussion page, and going to a discussion page, checks the route we are going to. This is problematic, because the route resolver represents the route being considered, not the route we are currently on. So, if we are currently using a DiscussionPageResolver, we must be going to a route handled by DiscussionPage. Instead, we need to check the route that we are currently on, which is done via `app.current.matches(DiscussionPage)`.