mirror of
https://github.com/flarum/core.git
synced 2025-08-15 12:54:47 +02:00
Compare commits
3 Commits
as/search-
...
as/cleanup
Author | SHA1 | Date | |
---|---|---|---|
|
d17cc1beee | ||
|
759eb80ff9 | ||
|
46cb32046d |
97
CHANGELOG.md
97
CHANGELOG.md
@@ -1,102 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.0-beta.14](https://github.com/flarum/core/compare/v0.1.0-beta.13...v0.1.0-beta.14)
|
||||
|
||||
### Added
|
||||
|
||||
- Check dependencies before enabling / disabling extensions (https://github.com/flarum/core/pull/2188)
|
||||
- Set up temporary infrastructure for TypeScript in core (https://github.com/flarum/core/pull/2206)
|
||||
- Better UI for request error modals (https://github.com/flarum/core/pull/1929)
|
||||
- Display name extender, tests, frontend UI (https://github.com/flarum/core/pull/2174)
|
||||
- Scroll to post or show alert when editing a post from another page (https://github.com/flarum/core/pull/2108)
|
||||
- Feature to test email config by sending an email to the current user (https://github.com/flarum/core/pull/2023)
|
||||
- Allow searching users by group ID using the group gambit (https://github.com/flarum/core/pull/2192)
|
||||
- Use `liveHumanTimes` helper to update times without reload/rerender (https://github.com/flarum/core/pull/2208)
|
||||
- View extender, tests (https://github.com/flarum/core/pull/2134)
|
||||
- User extender to replace `PrepareUserGroups` (https://github.com/flarum/core/pull/2110)
|
||||
- Increase extensibility of skeleton PHP (https://github.com/flarum/core/pull/2308, https://github.com/flarum/core/pull/2318)
|
||||
- Pass a translator instance to `getEmailSubject` in `MailableInterface` (https://github.com/flarum/core/pull/2244)
|
||||
- Force LF line endings on windows (https://github.com/flarum/core/pull/2321)
|
||||
- Add a `Link` component for internal and external links (https://github.com/flarum/core/pull/2315)
|
||||
- `ConfirmDocumentUnload` component
|
||||
- Error handler middleware can now be manipulated by the middleware extender
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Mithril 2 (https://github.com/flarum/core/pull/2255)
|
||||
- Stop storing component instances (https://github.com/flarum/core/issues/1821, https://github.com/flarum/core/issues/2144)
|
||||
- Update to Laravel 6.x (https://github.com/flarum/core/issues/2055)
|
||||
- `Flarum\Foundation\Application` no longer implements `Illuminate\Contracts\Foundation\Application` (#2142)
|
||||
- `Flarum\Foundation\Application` no longer inherits `Illuminate\Container\Container` (#2142)
|
||||
- `paths` have been split off from `Flarum\Foundation\Application` into `Flarum\Foundation\Paths`, which can be injected where needed (#2142)
|
||||
- `Flarum\User\Gate` no longer implements `Illuminate\Contracts\Auth\Access\Gate` (https://github.com/flarum/core/pull/2181)
|
||||
- Improve Group Gambit performance (https://github.com/flarum/core/pull/2192)
|
||||
- Switch to `dayjs` from `momentjs` (https://github.com/flarum/core/pull/2219)
|
||||
- Don't create a `bio` column in `users` for new installations (https://github.com/flarum/core/pull/2215)
|
||||
- Start converting core JS to TypeScript (https://github.com/flarum/core/pull/2207)
|
||||
- Make Carbon an explicit dependency (https://github.com/flarum/core/commit/3b39c212e0fef7522e7d541a9214ff3817138d5d)
|
||||
- Use Symfony's translator interface instead of Laravel's (https://github.com/flarum/core/pull/2243)
|
||||
- Use newer versions of fontawesome (https://github.com/flarum/core/pull/2274)
|
||||
- Use URL generator instead of `app()->url()` where possible (https://github.com/flarum/core/pull/2302)
|
||||
- Move config from `config.php` into an injectable helper class (https://github.com/flarum/core/pull/2271)
|
||||
- Use reserved TLD for bogus and test urls (https://github.com/flarum/core/commit/6860b24b70bd04544dde90e537ce021a5fc5a689)
|
||||
- Replace `m.stream` with `flarum/utils/Stream` (https://github.com/flarum/core/pull/2316)
|
||||
- Replace `affixedSidebar` util with `AffixedSidebar` component
|
||||
- Replace `m.withAttr` with `flarum/utils/withAttr`
|
||||
- Scroll Listener is now passive, performance improvement (https://github.com/flarum/core/pull/2387)
|
||||
|
||||
### Fixed
|
||||
|
||||
- `generate:migration` command for extensions (https://github.com/flarum/core/commit/443949f7b9d7558dbc1e0994cb898cbac59bec87)
|
||||
- Container config for `UninstalledSite` (https://github.com/flarum/core/commit/ecdce44d555dd36a365fd472b2916e677ef173cf)
|
||||
- Tooltip glitch on page chang (https://github.com/flarum/core/issues/2118)
|
||||
- Using multiple extenders in tests (https://github.com/flarum/core/commit/c4f4f218bf4b175a30880b807f9ccb1a37a25330)
|
||||
- Header glitch when opening modals (https://github.com/flarum/core/pull/2131)
|
||||
- Ensure `SameSite` is explicitly set for cookies (https://github.com/flarum/core/pull/2159)
|
||||
- Ensure `Flarum\User\Event\AvatarChanged` event is properly dispatched (https://github.com/flarum/core/pull/2197)
|
||||
- Show correct error message on wrong password when changing email (https://github.com/flarum/core/pull/2171)
|
||||
- Discussion unreadCount could be higher than commentCount if posts deleted (https://github.com/flarum/core/pull/2195)
|
||||
- Don't show page title on the default route (https://github.com/flarum/core/pull/2047)
|
||||
- Add page title to `All Discussions` page when it isn't the default route (https://github.com/flarum/core/pull/2047)
|
||||
- Accept `'0'` as `false` for `flarum/components/Checkbox` (https://github.com/flarum/core/pull/2210)
|
||||
- Fix PostStreamScrubber background (https://github.com/flarum/core/pull/2222)
|
||||
- Test port on BaseUrl tests (https://github.com/flarum/core/pull/2226)
|
||||
- `UrlGenerator` can now generate urls with optional parameters (https://github.com/flarum/core/pull/2246)
|
||||
- Allow `less` to be compiled independently of Flarum (https://github.com/flarum/core/pull/2252)
|
||||
- Use correct number abbreviation (https://github.com/flarum/core/pull/2261)
|
||||
- Ensure avatar html uses alt tags for accessibility (https://github.com/flarum/core/pull/2269)
|
||||
- Escape regex when searching (https://github.com/flarum/core/pull/2273)
|
||||
- Remove unneeded semicolons inserted during JS compilation (https://github.com/flarum/core/pull/2280)
|
||||
- Don't require a username/password for SMTP (https://github.com/flarum/core/pull/2287)
|
||||
- Allow uppercase entries for SMTP encryption validation (https://github.com/flarum/core/pull/2289)
|
||||
- Ensure that the right number of posts is returned from list posts API (https://github.com/flarum/core/pull/2291)
|
||||
- Fix a variety of PostStream bugs (https://github.com/flarum/core/pull/2160, https://github.com/flarum/core/pull/2160)
|
||||
- Sliding discussion glitch on mobile (https://github.com/flarum/core/pull/2324)
|
||||
- Sliding discussion button in wrong place (https://github.com/flarum/core/pull/2330, https://github.com/flarum/core/pull/2383)
|
||||
- Sliding discussion glitch on mobile (https://github.com/flarum/core/pull/2381)
|
||||
- Fix PostStream for posts with top margins, and scrubber position when scrolling below posts (https://github.com/flarum/core/pull/2369)
|
||||
|
||||
### Removed
|
||||
|
||||
- `Flarum\Event\AbstractConfigureRoutes` event class
|
||||
- `Flarum\Event\ConfigureApiRoutes` event class
|
||||
- `Flarum\Event\ConfigureForumRoutes` event class
|
||||
- `Flarum\Console\Event\Configuring` event class
|
||||
- `Flarum\Event\ConfigureModelDates` event class
|
||||
- `Flarum\Event\ConfigureLocales` event class
|
||||
- `Flarum\Event\ConfigureModelDefaultAttributes` event class
|
||||
- `Flarum\Event\GetModelRelationship` event class
|
||||
- `Flarum\User\Event\BioChanged` event class
|
||||
- `Flarum\Database\MigrationServiceProvider` moved into `Flarum\Database\DatabaseServiceProvider`
|
||||
- Unused `admin/components/Widget` component (`admin/component/DashboardWidget` should be used instead)
|
||||
- Mandrill mail driver (https://github.com/flarum/core/commit/bca833d3f1c34d45d95bf905902368a2753b8908)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `Flarum\User\Event\GetDisplayName` event class
|
||||
- Global path helpers, `Flarum\Foundation\Application` path methods (https://github.com/flarum/core/pull/2155)
|
||||
- `Flarum\User\AssertPermissionTrait` (https://github.com/flarum/core/pull/2044)
|
||||
|
||||
## [0.1.0-beta.13](https://github.com/flarum/core/compare/v0.1.0-beta.12...v0.1.0-beta.13)
|
||||
|
||||
### Added
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"email": "franz@develophp.org"
|
||||
},
|
||||
{
|
||||
"name": "Daniël Klabbers",
|
||||
"name": "Daniel Klabbers",
|
||||
"email": "daniel@klabbers.email",
|
||||
"homepage": "https://luceos.com"
|
||||
},
|
||||
@@ -27,10 +27,6 @@
|
||||
{
|
||||
"name": "Matthew Kilgore",
|
||||
"email": "matthew@kilgore.dev"
|
||||
},
|
||||
{
|
||||
"name": "Alexander (Sasha) Skvortsov",
|
||||
"email": "askvortsov@flarum.org"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
@@ -76,11 +72,11 @@
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"s9e/text-formatter": "^2.3.6",
|
||||
"symfony/config": "^4.3.4",
|
||||
"symfony/console": "^4.3.4",
|
||||
"symfony/event-dispatcher": "^4.3.4",
|
||||
"symfony/translation": "^4.3.4",
|
||||
"symfony/yaml": "^4.3.4",
|
||||
"symfony/config": "^3.3",
|
||||
"symfony/console": "^4.2",
|
||||
"symfony/event-dispatcher": "^4.3.2",
|
||||
"symfony/translation": "^3.3",
|
||||
"symfony/yaml": "^3.3",
|
||||
"tobscure/json-api": "^0.3.0",
|
||||
"wikimedia/less.php": "^3.0"
|
||||
},
|
||||
|
14
js/dist/admin.js
vendored
14
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
16
js/dist/forum.js
vendored
16
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
27
js/package-lock.json
generated
27
js/package-lock.json
generated
@@ -3556,9 +3556,9 @@
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
||||
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
|
||||
"integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw=="
|
||||
},
|
||||
"jquery.hotkeys": {
|
||||
"version": "0.1.0",
|
||||
@@ -4546,6 +4546,11 @@
|
||||
"integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ=="
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
@@ -4895,29 +4900,21 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
||||
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
||||
"requires": {
|
||||
"cacache": "^12.0.2",
|
||||
"find-cache-dir": "^2.1.0",
|
||||
"is-wsl": "^1.1.0",
|
||||
"schema-utils": "^1.0.0",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"serialize-javascript": "^2.1.2",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^4.1.2",
|
||||
"webpack-sources": "^1.4.0",
|
||||
"worker-farm": "^1.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"dayjs": "^1.8.28",
|
||||
"expose-loader": "^0.7.5",
|
||||
"flarum-webpack-config": "0.1.0-beta.10",
|
||||
"jquery": "^3.5.1",
|
||||
"jquery": "^3.4.1",
|
||||
"jquery.hotkeys": "^0.1.0",
|
||||
"lodash-es": "^4.17.14",
|
||||
"m.attrs.bidi": "github:tobscure/m.attrs.bidi",
|
||||
|
@@ -27,19 +27,20 @@ export default class AdminApplication extends Application {
|
||||
* @inheritdoc
|
||||
*/
|
||||
mount() {
|
||||
// Mithril does not render the home route on https://example.com/admin, so
|
||||
// we need to go to https://example.com/admin#/ explicitly.
|
||||
if (!document.location.hash) document.location.hash = '#/';
|
||||
|
||||
m.route.prefix = '#';
|
||||
super.mount();
|
||||
|
||||
m.mount(document.getElementById('app-navigation'), { view: () => Navigation.component({ className: 'App-backControl', drawer: true }) });
|
||||
m.mount(document.getElementById('header-navigation'), Navigation);
|
||||
m.mount(document.getElementById('header-primary'), HeaderPrimary);
|
||||
m.mount(document.getElementById('header-secondary'), HeaderSecondary);
|
||||
m.mount(document.getElementById('admin-navigation'), AdminNav);
|
||||
|
||||
// Mithril does not render the home route on https://example.com/admin, so
|
||||
// we need to go to https://example.com/admin#/ explicitly.
|
||||
if (!document.location.hash) document.location.hash = '#/';
|
||||
|
||||
m.route.prefix = '#';
|
||||
|
||||
super.mount();
|
||||
|
||||
// If an extension has just been enabled, then we will run its settings
|
||||
// callback.
|
||||
const enabled = localStorage.getItem('enabledExtension');
|
||||
|
@@ -139,20 +139,16 @@ export default class ExtensionsPage extends Page {
|
||||
// TODO: This workaround should be removed when we move away from bootstrap JS for modals.
|
||||
setTimeout(() => {
|
||||
app.modal.close();
|
||||
}, 300); // Bootstrap's Modal.TRANSITION_DURATION is 300 ms.
|
||||
|
||||
if (e.status !== 409) {
|
||||
throw e;
|
||||
}
|
||||
const error = JSON.parse(e.responseText).errors[0];
|
||||
|
||||
const error = e.response.errors[0];
|
||||
|
||||
app.alerts.show(
|
||||
{ type: 'error' },
|
||||
app.translator.trans(`core.lib.error.${error.code}_message`, {
|
||||
extension: error.extension,
|
||||
extensions: error.extensions.join(', '),
|
||||
})
|
||||
);
|
||||
app.alerts.show(
|
||||
{ type: 'error' },
|
||||
app.translator.trans(`core.lib.error.${error.code}_message`, {
|
||||
extension: error.extension,
|
||||
extensions: error.extensions.join(', '),
|
||||
})
|
||||
);
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
@@ -198,19 +198,13 @@ export default class Application {
|
||||
m.route(document.getElementById('content'), basePath + '/', mapRoutes(this.routes, basePath));
|
||||
|
||||
// Add a class to the body which indicates that the page has been scrolled
|
||||
// down. When this happens, we'll add classes to the header and app body
|
||||
// which will set the navbar's position to fixed. We don't want to always
|
||||
// have it fixed, as that could overlap with custom headers.
|
||||
const scrollListener = new ScrollListener((top) => {
|
||||
// down.
|
||||
new ScrollListener((top) => {
|
||||
const $app = $('#app');
|
||||
const offset = $app.offset().top;
|
||||
|
||||
$app.toggleClass('affix', top >= offset).toggleClass('scrolled', top > offset);
|
||||
$('.App-header').toggleClass('navbar-fixed-top', top >= offset);
|
||||
});
|
||||
|
||||
scrollListener.start();
|
||||
scrollListener.update();
|
||||
}).start();
|
||||
|
||||
$(() => {
|
||||
$('body').addClass('ontouchstart' in window ? 'touch' : 'no-touch');
|
||||
@@ -278,7 +272,7 @@ export default class Application {
|
||||
/**
|
||||
* Make an AJAX request, handling any low-level errors that may occur.
|
||||
*
|
||||
* @see https://mithril.js.org/request.html
|
||||
* @see https://lhorie.github.io/mithril/mithril.request.html
|
||||
* @param {Object} options
|
||||
* @return {Promise}
|
||||
* @public
|
||||
|
@@ -83,7 +83,7 @@ export default class Store {
|
||||
*/
|
||||
find(type, id, query = {}, options = {}) {
|
||||
let params = query;
|
||||
let url = app.forum.attribute('apiUrl') + (query.search ? '/search/' : '/') + type;
|
||||
let url = app.forum.attribute('apiUrl') + '/' + type;
|
||||
|
||||
if (id instanceof Array) {
|
||||
url += '?filter[id]=' + id.join(',');
|
||||
|
@@ -19,7 +19,6 @@ import extract from './utils/extract';
|
||||
import ScrollListener from './utils/ScrollListener';
|
||||
import stringToColor from './utils/stringToColor';
|
||||
import subclassOf from './utils/subclassOf';
|
||||
import SuperTextarea from './utils/SuperTextarea';
|
||||
import patchMithril from './utils/patchMithril';
|
||||
import classList from './utils/classList';
|
||||
import extractText from './utils/extractText';
|
||||
@@ -68,7 +67,6 @@ import username from './helpers/username';
|
||||
import userOnline from './helpers/userOnline';
|
||||
import listItems from './helpers/listItems';
|
||||
import Fragment from './Fragment';
|
||||
import DefaultResolver from './resolvers/DefaultResolver';
|
||||
|
||||
export default {
|
||||
extend: extend,
|
||||
@@ -91,7 +89,6 @@ export default {
|
||||
'utils/stringToColor': stringToColor,
|
||||
'utils/Stream': Stream,
|
||||
'utils/subclassOf': subclassOf,
|
||||
'utils/SuperTextarea': SuperTextarea,
|
||||
'utils/setRouteWithForcedRefresh': setRouteWithForcedRefresh,
|
||||
'utils/patchMithril': patchMithril,
|
||||
'utils/classList': classList,
|
||||
@@ -141,5 +138,4 @@ export default {
|
||||
'helpers/username': username,
|
||||
'helpers/userOnline': userOnline,
|
||||
'helpers/listItems': listItems,
|
||||
'resolvers/DefaultResolver': DefaultResolver,
|
||||
};
|
||||
|
@@ -12,14 +12,12 @@ import Link from './Link';
|
||||
* active.
|
||||
* - `href` The URL to link to. If the current URL `m.route()` matches this,
|
||||
* the `active` prop will automatically be set to true.
|
||||
* - `force` Whether the page should be fully rerendered. Defaults to `true`.
|
||||
*/
|
||||
export default class LinkButton extends Button {
|
||||
static initAttrs(attrs) {
|
||||
super.initAttrs(attrs);
|
||||
|
||||
attrs.active = this.isActive(attrs);
|
||||
if (attrs.force === undefined) attrs.force = true;
|
||||
}
|
||||
|
||||
view(vnode) {
|
||||
|
@@ -35,8 +35,7 @@ export default class Modal extends Component {
|
||||
this.attrs.animateHide();
|
||||
// Here, we ensure that the animation has time to complete.
|
||||
// See https://mithril.js.org/lifecycle-methods.html#onbeforeremove
|
||||
// Bootstrap's Modal.TRANSITION_DURATION is 300 ms.
|
||||
return new Promise((resolve) => setTimeout(resolve, 300));
|
||||
return new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,11 +10,7 @@ export default class Page extends Component {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.previous = app.current;
|
||||
app.current = new PageState(this.constructor, { routeName: this.attrs.routeName });
|
||||
|
||||
app.drawer.hide();
|
||||
app.modal.close();
|
||||
this.onNewRoute();
|
||||
|
||||
/**
|
||||
* A class name to apply to the body while the route is active.
|
||||
@@ -23,12 +19,35 @@ export default class Page extends Component {
|
||||
*/
|
||||
this.bodyClass = '';
|
||||
|
||||
/**
|
||||
* Whether we should scroll to the top of the page when its rendered.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.scrollTopOnCreate = true;
|
||||
this.currentPath = m.route.get();
|
||||
}
|
||||
|
||||
routeHasChanged() {
|
||||
return this.currentPath !== m.route.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* A collections of actions to run when the route changes.
|
||||
* This is extracted here, and not hardcoded in oninit, as oninit is not called
|
||||
* when a different route is handled by the same component, but we still need to
|
||||
* adjust the current route name.
|
||||
*/
|
||||
onNewRoute() {
|
||||
this.currentPath = m.route.get();
|
||||
|
||||
app.previous = app.current;
|
||||
app.current = new PageState(this.constructor, { routeName: this.attrs.routeName });
|
||||
|
||||
app.drawer.hide();
|
||||
app.modal.close();
|
||||
}
|
||||
|
||||
onbeforeupdate(vnode, old) {
|
||||
super.onbeforeupdate(vnode, old);
|
||||
|
||||
if (this.routeHasChanged()) {
|
||||
this.onNewRoute();
|
||||
}
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
@@ -37,10 +56,6 @@ export default class Page extends Component {
|
||||
if (this.bodyClass) {
|
||||
$('#app').addClass(this.bodyClass);
|
||||
}
|
||||
|
||||
if (this.scrollTopOnCreate) {
|
||||
$(window).scrollTop(0);
|
||||
}
|
||||
}
|
||||
|
||||
onremove() {
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as Mithril from 'mithril';
|
||||
|
||||
/**
|
||||
* The `fullTime` helper displays a formatted time string wrapped in a <time>
|
||||
* tag.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function fullTime(time: Date): Mithril.Vnode {
|
||||
export default function fullTime(time) {
|
||||
const d = dayjs(time);
|
||||
|
||||
const datetime = d.format();
|
@@ -1,13 +1,14 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as Mithril from 'mithril';
|
||||
import humanTimeUtil from '../utils/humanTime';
|
||||
|
||||
/**
|
||||
* The `humanTime` helper displays a time in a human-friendly time-ago format
|
||||
* (e.g. '12 days ago'), wrapped in a <time> tag with other information about
|
||||
* the time.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function humanTime(time: Date): Mithril.Vnode {
|
||||
export default function humanTime(time) {
|
||||
const d = dayjs(time);
|
||||
|
||||
const datetime = d.format();
|
12
js/src/common/helpers/icon.js
Normal file
12
js/src/common/helpers/icon.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* The `icon` helper displays an icon.
|
||||
*
|
||||
* @param {String} fontClass The full icon class, prefix and the icon’s name.
|
||||
* @param {Object} attrs Any other attributes to apply.
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function icon(fontClass, attrs = {}) {
|
||||
attrs.className = 'icon ' + fontClass + ' ' + (attrs.className || '');
|
||||
|
||||
return <i {...attrs} />;
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
import * as Mithril from 'mithril';
|
||||
|
||||
/**
|
||||
* The `icon` helper displays an icon.
|
||||
*
|
||||
* @param fontClass The full icon class, prefix and the icon’s name.
|
||||
* @param attrs Any other attributes to apply.
|
||||
*/
|
||||
export default function icon(fontClass: string, attrs: Mithril.Attributes = {}): Mithril.Vnode {
|
||||
attrs.className = 'icon ' + fontClass + ' ' + (attrs.className || '');
|
||||
|
||||
return <i {...attrs} />;
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
import Mithril from 'mithril';
|
||||
|
||||
/**
|
||||
* Generates a route resolver for a given component.
|
||||
* In addition to regular route resolver functionality:
|
||||
* - 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.
|
||||
*/
|
||||
export default class DefaultResolver {
|
||||
component: Mithril.Component;
|
||||
routeName: string;
|
||||
|
||||
constructor(component, routeName) {
|
||||
this.component = component;
|
||||
this.routeName = routeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a route change results in a changed key, a full page
|
||||
* rerender occurs. This method can be overriden in subclasses
|
||||
* to prevent rerenders on some route changes.
|
||||
*/
|
||||
makeKey() {
|
||||
return this.routeName + JSON.stringify(m.route.param());
|
||||
}
|
||||
|
||||
makeAttrs(vnode) {
|
||||
return {
|
||||
...vnode.attrs,
|
||||
routeName: this.routeName,
|
||||
};
|
||||
}
|
||||
|
||||
onmatch(args, requestedPath, route) {
|
||||
return this.component;
|
||||
}
|
||||
|
||||
render(vnode) {
|
||||
return [{ ...vnode, attrs: this.makeAttrs(vnode), key: this.makeKey() }];
|
||||
}
|
||||
}
|
@@ -58,7 +58,7 @@ export default class ScrollListener {
|
||||
*/
|
||||
start() {
|
||||
if (!this.active) {
|
||||
window.addEventListener('scroll', (this.active = this.loop.bind(this)), { passive: true });
|
||||
window.addEventListener('scroll', (this.active = this.loop.bind(this)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -28,9 +28,6 @@ export default class SubtreeRetainer {
|
||||
constructor(...callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
this.data = {};
|
||||
// Build the initial data, so it is available when calling
|
||||
// needsRebuild from the onbeforeupdate hook.
|
||||
this.needsRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +60,6 @@ export default class SubtreeRetainer {
|
||||
*/
|
||||
check(...callbacks) {
|
||||
this.callbacks = this.callbacks.concat(callbacks);
|
||||
// Update the data cache when new checks are added.
|
||||
this.needsRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,3 @@
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/plugin/relativeTime';
|
||||
|
||||
/**
|
||||
* The `humanTime` utility converts a date to a localized, human-readable time-
|
||||
* ago string.
|
||||
|
@@ -1,9 +1,6 @@
|
||||
import DefaultResolver from '../resolvers/DefaultResolver';
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to provide each route with the current route name.
|
||||
* format that can be understood by Mithril.
|
||||
*
|
||||
* @see https://mithril.js.org/route.html#signature
|
||||
* @param {Object} routes
|
||||
@@ -13,17 +10,14 @@ import DefaultResolver from '../resolvers/DefaultResolver';
|
||||
export default function mapRoutes(routes, basePath = '') {
|
||||
const map = {};
|
||||
|
||||
for (const routeName in routes) {
|
||||
const route = routes[routeName];
|
||||
for (const key in routes) {
|
||||
const route = routes[key];
|
||||
|
||||
if ('resolver' in route) {
|
||||
map[basePath + route.path] = route.resolver;
|
||||
} else if ('component' in route) {
|
||||
const resolverClass = 'resolverClass' in route ? route.resolverClass : DefaultResolver;
|
||||
map[basePath + route.path] = new resolverClass(route.component, routeName);
|
||||
} else {
|
||||
throw new Error(`Either a resolver or a component must be provided for the route [${routeName}]`);
|
||||
}
|
||||
map[basePath + route.path] = {
|
||||
render() {
|
||||
return m(route.component, { routeName: key });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return map;
|
||||
|
@@ -115,19 +115,17 @@ export default class ForumApplication extends Application {
|
||||
this.routes[defaultAction].path = '/';
|
||||
this.history.push(defaultAction, this.translator.trans('core.forum.header.back_to_index_tooltip'), '/');
|
||||
|
||||
this.pane = new Pane(document.getElementById('app'));
|
||||
|
||||
m.route.prefix = '';
|
||||
super.mount(this.forum.attribute('basePath'));
|
||||
|
||||
// We mount navigation and header components after the page, so components
|
||||
// like the back button can access the updated state when rendering.
|
||||
m.mount(document.getElementById('app-navigation'), { view: () => Navigation.component({ className: 'App-backControl', drawer: true }) });
|
||||
m.mount(document.getElementById('header-navigation'), Navigation);
|
||||
m.mount(document.getElementById('header-primary'), HeaderPrimary);
|
||||
m.mount(document.getElementById('header-secondary'), HeaderSecondary);
|
||||
m.mount(document.getElementById('composer'), { view: () => Composer.component({ state: this.composer }) });
|
||||
|
||||
this.pane = new Pane(document.getElementById('app'));
|
||||
|
||||
m.route.prefix = '';
|
||||
super.mount(this.forum.attribute('basePath'));
|
||||
|
||||
alertEmailConfirmation(this);
|
||||
|
||||
// Route the home link back home when clicked. We do not want it to register
|
||||
|
@@ -71,7 +71,6 @@ import Search from './components/Search';
|
||||
import DiscussionListItem from './components/DiscussionListItem';
|
||||
import LoadingPost from './components/LoadingPost';
|
||||
import PostsUserPage from './components/PostsUserPage';
|
||||
import DiscussionPageResolver from './resolvers/DiscussionPageResolver';
|
||||
import routes from './routes';
|
||||
import ForumApplication from './ForumApplication';
|
||||
|
||||
@@ -147,7 +146,6 @@ export default Object.assign(compat, {
|
||||
'components/DiscussionListItem': DiscussionListItem,
|
||||
'components/LoadingPost': LoadingPost,
|
||||
'components/PostsUserPage': PostsUserPage,
|
||||
'resolvers/DiscussionPageResolver': DiscussionPageResolver,
|
||||
routes: routes,
|
||||
ForumApplication: ForumApplication,
|
||||
});
|
||||
|
@@ -56,7 +56,9 @@ export default class CommentPost extends Post {
|
||||
]);
|
||||
}
|
||||
|
||||
refreshContent() {
|
||||
onupdate(vnode) {
|
||||
super.onupdate();
|
||||
|
||||
const contentHtml = this.isEditing() ? '' : this.attrs.post.contentHtml();
|
||||
|
||||
// If the post content has changed since the last render, we'll run through
|
||||
@@ -64,28 +66,13 @@ export default class CommentPost extends Post {
|
||||
// necessary because TextFormatter outputs them for e.g. syntax highlighting.
|
||||
if (this.contentHtml !== contentHtml) {
|
||||
this.$('.Post-body script').each(function () {
|
||||
const script = document.createElement('script');
|
||||
script.textContent = this.textContent;
|
||||
Array.from(this.attributes).forEach((attr) => script.setAttribute(attr.name, attr.value));
|
||||
this.parentNode.replaceChild(script, this);
|
||||
eval.call(window, $(this).text());
|
||||
});
|
||||
}
|
||||
|
||||
this.contentHtml = contentHtml;
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
this.refreshContent();
|
||||
}
|
||||
|
||||
onupdate(vnode) {
|
||||
super.onupdate(vnode);
|
||||
|
||||
this.refreshContent();
|
||||
}
|
||||
|
||||
isEditing() {
|
||||
return app.composer.bodyMatches(EditPostComposer, { post: this.attrs.post });
|
||||
}
|
||||
|
@@ -91,12 +91,12 @@ export default class DiscussionListItem extends Component {
|
||||
)
|
||||
: ''}
|
||||
|
||||
<span
|
||||
<a
|
||||
className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
|
||||
onclick={this.markAsRead.bind(this)}
|
||||
>
|
||||
{icon('fas fa-check')}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div className={'DiscussionListItem-content Slidable-content' + (isUnread ? ' unread' : '') + (isRead ? ' read' : '')}>
|
||||
<Link
|
||||
|
@@ -32,25 +32,12 @@ export default class DiscussionPage extends Page {
|
||||
*/
|
||||
this.near = m.route.param('near') || 0;
|
||||
|
||||
this.load();
|
||||
|
||||
// If the discussion list has been loaded, then we'll enable the pane (and
|
||||
// hide it by default). Also, if we've just come from another discussion
|
||||
// page, then we don't want Mithril to redraw the whole page – if it did,
|
||||
// then the pane would redraw which would be slow and would cause problems with
|
||||
// event handlers.
|
||||
if (app.discussions.hasDiscussions()) {
|
||||
app.pane.enable();
|
||||
app.pane.hide();
|
||||
}
|
||||
|
||||
app.history.push('discussion');
|
||||
|
||||
this.bodyClass = 'App--discussion';
|
||||
}
|
||||
|
||||
onremove() {
|
||||
super.onremove();
|
||||
// If we are indeed navigating away from this discussion, then disable the
|
||||
// discussion list pane. Also, if we're composing a reply to this
|
||||
// discussion, minimize the composer – unless it's empty, in which case
|
||||
@@ -82,6 +69,7 @@ export default class DiscussionPage extends Page {
|
||||
{PostStream.component({
|
||||
discussion,
|
||||
stream: this.stream,
|
||||
targetPost: this.stream.targetPost,
|
||||
onPositionChange: this.positionChanged.bind(this),
|
||||
})}
|
||||
</div>
|
||||
@@ -93,6 +81,36 @@ export default class DiscussionPage extends Page {
|
||||
);
|
||||
}
|
||||
|
||||
onNewRoute() {
|
||||
// If we have routed to the same discussion as we were viewing previously,
|
||||
// prompt the post stream to jump to the new 'near' param.
|
||||
// Otherwise, load in a new discussion. The `else` branch will
|
||||
// be followed when `onNewRoute` is called from `oninit`.
|
||||
const idParam = m.route.param('id');
|
||||
if (this.discussion && idParam && idParam.split('-')[0] === this.discussion.id()) {
|
||||
const near = m.route.param('near') || '1';
|
||||
|
||||
if (near !== String(this.near)) {
|
||||
this.stream.goToNumber(near);
|
||||
}
|
||||
|
||||
this.near = near;
|
||||
} else {
|
||||
super.onNewRoute();
|
||||
|
||||
this.discussion = null;
|
||||
|
||||
this.load();
|
||||
|
||||
// If the discussion list has been loaded, then we'll enable the pane (and
|
||||
// hide it by default).
|
||||
if (app.discussions.hasDiscussions()) {
|
||||
app.pane.enable();
|
||||
app.pane.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the discussion from the API or use the preloaded one.
|
||||
*/
|
||||
@@ -155,7 +173,7 @@ export default class DiscussionPage extends Page {
|
||||
record.relationships.discussion.data.id === discussionId
|
||||
)
|
||||
.map((record) => app.store.getById('posts', record.id))
|
||||
.sort((a, b) => a.createdAt() - b.createdAt())
|
||||
.sort((a, b) => a.id() - b.id())
|
||||
.slice(0, 20);
|
||||
}
|
||||
|
||||
@@ -217,7 +235,10 @@ export default class DiscussionPage extends Page {
|
||||
// replace it into the window's history and our own history stack.
|
||||
const url = app.route.discussion(discussion, (this.near = startNumber));
|
||||
|
||||
this.prevRoute = url;
|
||||
m.route.set(url, null, { replace: true });
|
||||
window.history.replaceState(null, document.title, url);
|
||||
|
||||
app.history.push('discussion', discussion.title());
|
||||
|
||||
// If the user hasn't read past here before, then we'll update their read
|
||||
|
@@ -24,7 +24,7 @@ export default class DiscussionsSearchSource {
|
||||
include: 'mostRelevantPost',
|
||||
};
|
||||
|
||||
return app.store.find('discussions', params, { search: query }).then((results) => (this.results[query] = results));
|
||||
return app.store.find('discussions', params).then((results) => (this.results[query] = results));
|
||||
}
|
||||
|
||||
view(query) {
|
||||
|
@@ -29,6 +29,14 @@ export default class IndexPage extends Page {
|
||||
this.lastDiscussion = app.previous.get('discussion');
|
||||
}
|
||||
|
||||
app.history.push('index', app.translator.trans('core.forum.header.back_to_index_tooltip'));
|
||||
|
||||
this.bodyClass = 'App--index';
|
||||
}
|
||||
|
||||
onNewRoute() {
|
||||
super.onNewRoute();
|
||||
|
||||
// If the user is coming from the discussion list, then they have either
|
||||
// just switched one of the parameters (filter, sort, search) or they
|
||||
// probably want to refresh the results. We will clear the discussion list
|
||||
@@ -39,10 +47,7 @@ export default class IndexPage extends Page {
|
||||
|
||||
app.discussions.refreshParams(app.search.params());
|
||||
|
||||
app.history.push('index', app.translator.trans('core.forum.header.back_to_index_tooltip'));
|
||||
|
||||
this.bodyClass = 'App--index';
|
||||
this.scrollTopOnCreate = false;
|
||||
this.setTitle();
|
||||
}
|
||||
|
||||
view() {
|
||||
@@ -86,22 +91,18 @@ export default class IndexPage extends Page {
|
||||
|
||||
$('#app').css('min-height', $(window).height() + heroHeight);
|
||||
|
||||
// Let browser handle scrolling on page reload.
|
||||
if (app.previous.type == null) return;
|
||||
|
||||
// When on mobile, only retain scroll if we're coming from a discussion page.
|
||||
// Otherwise, we've just changed the filter, so we should go to the top of the page.
|
||||
if (app.screen() == 'desktop' || app.screen() == 'desktop-hd' || this.lastDiscussion) {
|
||||
$(window).scrollTop(scrollTop - oldHeroHeight + heroHeight);
|
||||
} else {
|
||||
$(window).scrollTop(0);
|
||||
}
|
||||
// Scroll to the remembered position. We do this after a short delay so that
|
||||
// it happens after the browser has done its own "back button" scrolling,
|
||||
// which isn't right. https://github.com/flarum/core/issues/835
|
||||
const scroll = () => $(window).scrollTop(scrollTop - oldHeroHeight + heroHeight);
|
||||
scroll();
|
||||
setTimeout(scroll, 1);
|
||||
|
||||
// If we've just returned from a discussion page, then the constructor will
|
||||
// have set the `lastDiscussion` property. If this is the case, we want to
|
||||
// scroll down to that discussion so that it's in view.
|
||||
if (this.lastDiscussion) {
|
||||
const $discussion = this.$(`li[data-id="${this.lastDiscussion.id()}"] .DiscussionListItem`);
|
||||
const $discussion = this.$(`.DiscussionListItem[data-id="${this.lastDiscussion.id()}"]`);
|
||||
|
||||
if ($discussion.length) {
|
||||
const indexTop = $('#header').outerHeight();
|
||||
@@ -116,16 +117,14 @@ export default class IndexPage extends Page {
|
||||
}
|
||||
}
|
||||
|
||||
onbeforeremove() {
|
||||
// Save the scroll position so we can restore it when we return to the
|
||||
// discussion list.
|
||||
app.cache.scrollTop = $(window).scrollTop();
|
||||
}
|
||||
|
||||
onremove() {
|
||||
super.onremove();
|
||||
|
||||
$('#app').css('min-height', '');
|
||||
|
||||
// Save the scroll position so we can restore it when we return to the
|
||||
// discussion list.
|
||||
app.cache.scrollTop = $(window).scrollTop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,7 +24,7 @@ export default class Notification extends Component {
|
||||
<Link
|
||||
className={'Notification Notification--' + notification.contentType() + ' ' + (!notification.isRead() ? 'unread' : '')}
|
||||
href={href}
|
||||
external={href.includes('://')}
|
||||
external={href.indexOf('://') === -1}
|
||||
onclick={this.markAsRead.bind(this)}
|
||||
>
|
||||
{!notification.isRead() &&
|
||||
|
@@ -32,13 +32,6 @@ export default class PostStream extends Component {
|
||||
const posts = this.stream.posts();
|
||||
const postIds = this.discussion.postIds();
|
||||
|
||||
const postFadeIn = (vnode) => {
|
||||
$(vnode.dom).addClass('fadeIn');
|
||||
// 500 is the duration of the fadeIn CSS animation + 100ms,
|
||||
// so the animation has time to complete
|
||||
setTimeout(() => $(vnode.dom).removeClass('fadeIn'), 500);
|
||||
};
|
||||
|
||||
const items = posts.map((post, i) => {
|
||||
let content;
|
||||
const attrs = { 'data-index': this.stream.visibleStart + i };
|
||||
@@ -49,7 +42,6 @@ export default class PostStream extends Component {
|
||||
content = PostComponent ? PostComponent.component({ post }) : '';
|
||||
|
||||
attrs.key = 'post' + post.id();
|
||||
attrs.oncreate = postFadeIn;
|
||||
attrs['data-time'] = time.toISOString();
|
||||
attrs['data-number'] = post.number();
|
||||
attrs['data-id'] = post.id();
|
||||
@@ -97,7 +89,7 @@ export default class PostStream extends Component {
|
||||
// is not already doing so, then show a 'write a reply' placeholder.
|
||||
if (viewingEnd && (!app.session.user || this.discussion.canReply())) {
|
||||
items.push(
|
||||
<div className="PostStream-item" key="reply" data-index={this.stream.count()} oncreate={postFadeIn}>
|
||||
<div className="PostStream-item" key="reply">
|
||||
{ReplyPlaceholder.component({ discussion: this.discussion })}
|
||||
</div>
|
||||
);
|
||||
@@ -129,15 +121,16 @@ export default class PostStream extends Component {
|
||||
* Start scrolling, if appropriate, to a newly-targeted post.
|
||||
*/
|
||||
triggerScroll() {
|
||||
if (!this.stream.needsScroll) return;
|
||||
if (!this.attrs.targetPost || !this.stream.needsScroll) return;
|
||||
|
||||
const target = this.stream.targetPost;
|
||||
const newTarget = this.attrs.targetPost;
|
||||
this.stream.needsScroll = false;
|
||||
|
||||
if ('number' in target) {
|
||||
this.scrollToNumber(target.number, this.stream.animateScroll);
|
||||
} else if ('index' in target) {
|
||||
this.scrollToIndex(target.index, this.stream.animateScroll, target.reply);
|
||||
if ('number' in newTarget) {
|
||||
this.scrollToNumber(newTarget.number, this.stream.animateScroll);
|
||||
} else if ('index' in newTarget) {
|
||||
const backwards = newTarget.index === this.stream.count() - 1;
|
||||
this.scrollToIndex(newTarget.index, this.stream.animateScroll, backwards);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,9 +181,9 @@ export default class PostStream extends Component {
|
||||
// seen if the browser were scrolled right up to the top of the page,
|
||||
// and the viewport had a height of 0.
|
||||
const $items = this.$('.PostStream-item[data-index]');
|
||||
let index = $items.first().data('index') || 0;
|
||||
let visible = 0;
|
||||
let period = '';
|
||||
let indexFromViewPort = null;
|
||||
|
||||
// Now loop through each of the items in the discussion. An 'item' is
|
||||
// either a single post or a 'gap' of one or more posts that haven't
|
||||
@@ -216,10 +209,8 @@ export default class PostStream extends Component {
|
||||
const visibleBottom = Math.min(height, viewportTop + viewportHeight - top);
|
||||
const visiblePost = visibleBottom - visibleTop;
|
||||
|
||||
// We take the index of the first item that passed the previous checks.
|
||||
// It is the item that is first visible in the viewport.
|
||||
if (indexFromViewPort === null) {
|
||||
indexFromViewPort = parseFloat($this.data('index')) + visibleTop / height;
|
||||
if (top <= viewportTop) {
|
||||
index = parseFloat($this.data('index')) + visibleTop / height;
|
||||
}
|
||||
|
||||
if (visiblePost > 0) {
|
||||
@@ -232,10 +223,7 @@ export default class PostStream extends Component {
|
||||
if (time) period = time;
|
||||
});
|
||||
|
||||
// If indexFromViewPort is null, it means no posts are visible in the
|
||||
// viewport. This can happen, when drafting a long reply post. In that case
|
||||
// set the index to the last post.
|
||||
this.stream.index = indexFromViewPort !== null ? indexFromViewPort + 1 : this.stream.count();
|
||||
this.stream.index = index + 1;
|
||||
this.stream.visible = visible;
|
||||
if (period) this.stream.description = dayjs(period).format('MMMM YYYY');
|
||||
}
|
||||
@@ -308,17 +296,18 @@ export default class PostStream extends Component {
|
||||
*
|
||||
* @param {Integer} index
|
||||
* @param {Boolean} animate
|
||||
* @param {Boolean} reply Whether or not to scroll to the reply placeholder.
|
||||
* @param {Boolean} bottom Whether or not to scroll to the bottom of the post
|
||||
* at the given index, instead of the top of it.
|
||||
* @return {jQuery.Deferred}
|
||||
*/
|
||||
scrollToIndex(index, animate, reply) {
|
||||
const $item = reply ? $('.PostStream-item:last-child') : this.$(`.PostStream-item[data-index=${index}]`);
|
||||
scrollToIndex(index, animate, bottom) {
|
||||
const $item = this.$(`.PostStream-item[data-index=${index}]`);
|
||||
|
||||
this.scrollToItem($item, animate, true, reply);
|
||||
|
||||
if (reply) {
|
||||
this.flashItem($item);
|
||||
}
|
||||
return this.scrollToItem($item, animate, true, bottom).then(() => {
|
||||
if (index == this.stream.count() - 1) {
|
||||
this.flashItem(this.$('.PostStream-item:last-child'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,10 +317,11 @@ export default class PostStream extends Component {
|
||||
* @param {Boolean} animate
|
||||
* @param {Boolean} force Whether or not to force scrolling to the item, even
|
||||
* if it is already in the viewport.
|
||||
* @param {Boolean} reply Whether or not to scroll to the reply placeholder.
|
||||
* @param {Boolean} bottom Whether or not to scroll to the bottom of the post
|
||||
* at the given index, instead of the top of it.
|
||||
* @return {jQuery.Deferred}
|
||||
*/
|
||||
scrollToItem($item, animate, force, reply) {
|
||||
scrollToItem($item, animate, force, bottom) {
|
||||
const $container = $('html, body').stop(true);
|
||||
const index = $item.data('index');
|
||||
|
||||
@@ -342,10 +332,10 @@ export default class PostStream extends Component {
|
||||
const scrollBottom = scrollTop + $(window).height();
|
||||
|
||||
// If the item is already in the viewport, we may not need to scroll.
|
||||
// If we're scrolling to the reply placeholder, we'll make sure its
|
||||
// If we're scrolling to the bottom of an item, then we'll make sure the
|
||||
// bottom will line up with the top of the composer.
|
||||
if (force || itemTop < scrollTop || itemBottom > scrollBottom) {
|
||||
const top = reply ? itemBottom - $(window).height() + app.composer.computedHeight() : $item.is(':first-child') ? 0 : itemTop;
|
||||
const top = bottom ? itemBottom - $(window).height() + app.composer.computedHeight() : $item.is(':first-child') ? 0 : itemTop;
|
||||
|
||||
if (!animate) {
|
||||
$container.scrollTop(top);
|
||||
@@ -359,7 +349,7 @@ export default class PostStream extends Component {
|
||||
// We manually set the index because we want to display the index of the
|
||||
// exact post we've scrolled to, not just that of the first post within viewport.
|
||||
this.updateScrubber();
|
||||
if (index !== undefined) this.stream.index = index + 1;
|
||||
this.stream.index = index;
|
||||
};
|
||||
|
||||
// If we don't update this before the scroll, the scrubber will start
|
||||
@@ -369,29 +359,24 @@ export default class PostStream extends Component {
|
||||
|
||||
return Promise.all([$container.promise(), this.stream.loadPromise]).then(() => {
|
||||
m.redraw.sync();
|
||||
|
||||
// Rendering post contents will probably throw off our position.
|
||||
// To counter this, we'll scroll either:
|
||||
// - To the reply placeholder (aligned with composer top)
|
||||
// - To the top of the page if we're on the first post
|
||||
// - To the top of a post (if that post exists)
|
||||
// If the post does not currently exist, it's probably
|
||||
// outside of the range we loaded in, so we won't adjust anything,
|
||||
// as it will soon be rendered by the "load more" system.
|
||||
let itemOffset;
|
||||
if (reply) {
|
||||
const $placeholder = $('.PostStream-item:last-child');
|
||||
$(window).scrollTop($placeholder.offset().top + $placeholder.height() - $(window).height() + app.composer.computedHeight());
|
||||
} else if (index === 0) {
|
||||
$(window).scrollTop(0);
|
||||
} else if ((itemOffset = $(`.PostStream-item[data-index=${index}]`).offset())) {
|
||||
$(window).scrollTop(itemOffset.top - this.getMarginTop());
|
||||
}
|
||||
|
||||
// We want to adjust this again after posts have been loaded in
|
||||
// and position adjusted so that the scrubber's height is accurate.
|
||||
// We want to adjust this again after posts have been loaded in so that
|
||||
// the height of the scrubber is accurate.
|
||||
updateScrubberHeight();
|
||||
|
||||
// After post data has been loaded in, we will attempt to scroll back
|
||||
// to the top of the requested post (or to the top of the page if the
|
||||
// first post was requested). In some cases, we may have scrolled to
|
||||
// the end of the available post range, in which case, the next range
|
||||
// of posts will be loaded in. However, in those cases, the post we
|
||||
// requested won't exist, so scrolling to it would cause an error.
|
||||
// Accordingly, we start by checking that it's offset is defined.
|
||||
const offset = $(`.PostStream-item[data-index=${index}]`).offset();
|
||||
if (index === 0) {
|
||||
$(window).scrollTop(0);
|
||||
} else if (offset) {
|
||||
$(window).scrollTop($(`.PostStream-item[data-index=${index}]`).offset().top - this.getMarginTop());
|
||||
}
|
||||
|
||||
this.calculatePosition();
|
||||
this.stream.paused = false;
|
||||
});
|
||||
@@ -403,11 +388,10 @@ export default class PostStream extends Component {
|
||||
* @param {jQuery} $item
|
||||
*/
|
||||
flashItem($item) {
|
||||
// This might execute before the fadeIn class has been removed in PostStreamItem's
|
||||
// oncreate, so we remove it just to be safe and avoid a double animation.
|
||||
$item.removeClass('fadeIn');
|
||||
$item.addClass('flash').on('animationend webkitAnimationEnd', (e) => {
|
||||
$item.removeClass('flash');
|
||||
if (e.animationName === 'fadeIn') {
|
||||
$item.removeClass('flash');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ export default class ReplyPlaceholder extends Component {
|
||||
}
|
||||
|
||||
const reply = () => {
|
||||
DiscussionControls.replyAction.call(this.attrs.discussion, true).catch(() => {});
|
||||
DiscussionControls.replyAction.call(this.attrs.discussion, true);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -29,6 +29,14 @@ export default class UserPage extends Page {
|
||||
this.bodyClass = 'App--user';
|
||||
}
|
||||
|
||||
onNewRoute() {
|
||||
super.onNewRoute();
|
||||
|
||||
if (m.route.param('username')) {
|
||||
this.loadUser(m.route.param('username'));
|
||||
}
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="UserPage">
|
||||
@@ -135,7 +143,7 @@ export default class UserPage extends Page {
|
||||
|
||||
items.add(
|
||||
'posts',
|
||||
<LinkButton href={app.route('user.posts', { username: user.username() })} icon="far fa-comment">
|
||||
<LinkButton href={app.route('user.posts', { username: user.username() })} force icon="far fa-comment">
|
||||
{app.translator.trans('core.forum.user.posts_link')} <span className="Button-badge">{user.commentCount()}</span>
|
||||
</LinkButton>,
|
||||
100
|
||||
@@ -143,7 +151,7 @@ export default class UserPage extends Page {
|
||||
|
||||
items.add(
|
||||
'discussions',
|
||||
<LinkButton href={app.route('user.discussions', { username: user.username() })} icon="fas fa-bars">
|
||||
<LinkButton href={app.route('user.discussions', { username: user.username() })} force icon="fas fa-bars">
|
||||
{app.translator.trans('core.forum.user.discussions_link')} <span className="Button-badge">{user.discussionCount()}</span>
|
||||
</LinkButton>,
|
||||
90
|
||||
|
@@ -16,14 +16,10 @@ export default class UsersSearchResults {
|
||||
|
||||
search(query) {
|
||||
return app.store
|
||||
.find(
|
||||
'users',
|
||||
{
|
||||
filter: { q: query },
|
||||
page: { limit: 5 },
|
||||
},
|
||||
{ search: query }
|
||||
)
|
||||
.find('users', {
|
||||
filter: { q: query },
|
||||
page: { limit: 5 },
|
||||
})
|
||||
.then((results) => {
|
||||
this.results[query] = results;
|
||||
m.redraw();
|
||||
|
@@ -1,49 +0,0 @@
|
||||
import DefaultResolver from '../../common/resolvers/DefaultResolver';
|
||||
import DiscussionPage from '../components/DiscussionPage';
|
||||
|
||||
/**
|
||||
* This isn't exported as it is a temporary measure.
|
||||
* A more robust system will be implemented alongside UTF-8 support in beta 15.
|
||||
*/
|
||||
function getDiscussionIdFromSlug(slug: string | undefined) {
|
||||
if (!slug) return;
|
||||
return slug.split('-')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom route resolver for DiscussionPage that generates the same key to all posts
|
||||
* on the same discussion. It triggers a scroll when going from one post to another
|
||||
* in the same discussion.
|
||||
*/
|
||||
export default class DiscussionPageResolver extends DefaultResolver {
|
||||
static scrollToPostNumber: string | null = null;
|
||||
|
||||
makeKey() {
|
||||
const params = { ...m.route.param() };
|
||||
if ('near' in params) {
|
||||
delete params.near;
|
||||
}
|
||||
params.id = getDiscussionIdFromSlug(params.id);
|
||||
return this.routeName.replace('.near', '') + JSON.stringify(params);
|
||||
}
|
||||
|
||||
onmatch(args, requestedPath, route) {
|
||||
if (app.current.matches(DiscussionPage) && getDiscussionIdFromSlug(args.id) === getDiscussionIdFromSlug(m.route.param('id'))) {
|
||||
// By default, the first post number of any discussion is 1
|
||||
DiscussionPageResolver.scrollToPostNumber = args.near || '1';
|
||||
}
|
||||
|
||||
return super.onmatch(args, requestedPath, route);
|
||||
}
|
||||
|
||||
render(vnode) {
|
||||
if (DiscussionPageResolver.scrollToPostNumber !== null) {
|
||||
const number = DiscussionPageResolver.scrollToPostNumber;
|
||||
// Scroll after a timeout to avoid clashes with the render.
|
||||
setTimeout(() => app.current.get('stream').goToNumber(number));
|
||||
DiscussionPageResolver.scrollToPostNumber = null;
|
||||
}
|
||||
|
||||
return super.render(vnode);
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@ import PostsUserPage from './components/PostsUserPage';
|
||||
import DiscussionsUserPage from './components/DiscussionsUserPage';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
import NotificationsPage from './components/NotificationsPage';
|
||||
import DiscussionPageResolver from './resolvers/DiscussionPageResolver';
|
||||
|
||||
/**
|
||||
* The `routes` initializer defines the forum app's routes.
|
||||
@@ -15,8 +14,8 @@ export default function (app) {
|
||||
app.routes = {
|
||||
index: { path: '/all', component: IndexPage },
|
||||
|
||||
discussion: { path: '/d/:id', component: DiscussionPage, resolverClass: DiscussionPageResolver },
|
||||
'discussion.near': { path: '/d/:id/:near', component: DiscussionPage, resolverClass: DiscussionPageResolver },
|
||||
discussion: { path: '/d/:id', component: DiscussionPage },
|
||||
'discussion.near': { path: '/d/:id/:near', component: DiscussionPage },
|
||||
|
||||
user: { path: '/u/:username', component: PostsUserPage },
|
||||
'user.posts': { path: '/u/:username', component: PostsUserPage },
|
||||
|
@@ -119,7 +119,7 @@ export default class DiscussionListState {
|
||||
params.page = { offset };
|
||||
params.include = params.include.join(',');
|
||||
|
||||
return this.app.store.find('discussions', params, { search: params.filter.q });
|
||||
return this.app.store.find('discussions', params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -96,9 +96,7 @@ class PostStreamState {
|
||||
// If we want to go to the reply preview, then we will go to the end of the
|
||||
// discussion and then scroll to the very bottom of the page.
|
||||
if (number === 'reply') {
|
||||
const resultPromise = this.goToLast();
|
||||
this.targetPost.reply = true;
|
||||
return resultPromise;
|
||||
return this.goToLast();
|
||||
}
|
||||
|
||||
this.paused = true;
|
||||
@@ -282,13 +280,7 @@ class PostStreamState {
|
||||
}
|
||||
});
|
||||
|
||||
if (loadIds.length) {
|
||||
return app.store.find('posts', loadIds).then((newPosts) => {
|
||||
return loaded.concat(newPosts).sort((a, b) => a.createdAt() - b.createdAt());
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(loaded);
|
||||
return loadIds.length ? app.store.find('posts', loadIds) : Promise.resolve(loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -236,16 +236,12 @@
|
||||
.App-header {
|
||||
padding: 8px;
|
||||
height: @header-height;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: @zindex-header;
|
||||
|
||||
.affix & {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
& when (@config-colored-header = true) {
|
||||
.light-contents(@header-color, @header-control-bg, @header-control-color);
|
||||
}
|
||||
|
@@ -105,10 +105,6 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.off.Checkbox--switch .Checkbox-display {
|
||||
background: @muted-more-color;
|
||||
}
|
||||
}
|
||||
.Modal-footer {
|
||||
border: 0;
|
||||
|
@@ -6,7 +6,18 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {opacity: 0}
|
||||
100% {opacity: 1}
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
0% {opacity: 0}
|
||||
100% {opacity: 1}
|
||||
}
|
||||
.PostStream-item {
|
||||
.animation(fadeIn 0.4s ease-in-out);
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid @control-bg;
|
||||
|
||||
@@ -93,16 +104,3 @@
|
||||
.animation(pulsate 0.2s ease-in-out);
|
||||
.animation-iteration-count(1);
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {opacity: 0}
|
||||
100% {opacity: 1}
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
0% {opacity: 0}
|
||||
100% {opacity: 1}
|
||||
}
|
||||
.fadeIn {
|
||||
.animation(fadeIn 0.4s ease-in-out);
|
||||
.animation-iteration-count(1);
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@
|
||||
|
||||
.sliding& {
|
||||
position: relative;
|
||||
background: @control-bg;
|
||||
background: #fff;
|
||||
z-index: 2;
|
||||
border-radius: 2px;
|
||||
.box-shadow(0 2px 6px @shadow-color);
|
||||
|
@@ -54,10 +54,9 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.admin.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
Middleware\RequireAdministrateAbility::class
|
||||
HttpMiddleware\SetLocale::class,
|
||||
Middleware\RequireAdministrateAbility::class,
|
||||
];
|
||||
});
|
||||
|
||||
@@ -69,10 +68,6 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.admin.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.admin.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.admin.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -80,7 +75,7 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.admin.routes')));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
@@ -51,9 +51,8 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\AuthenticateWithHeader::class,
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.api.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class
|
||||
];
|
||||
});
|
||||
|
||||
@@ -65,10 +64,6 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.api.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.api.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -76,16 +71,10 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.api.routes')));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.notification_serializers', function () {
|
||||
return [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +82,7 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->setNotificationSerializers();
|
||||
$this->registerNotificationSerializers();
|
||||
|
||||
AbstractSerializeController::setContainer($this->app);
|
||||
AbstractSerializeController::setEventDispatcher($events = $this->app->make('events'));
|
||||
@@ -105,12 +94,13 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
/**
|
||||
* Register notification serializers.
|
||||
*/
|
||||
protected function setNotificationSerializers()
|
||||
protected function registerNotificationSerializers()
|
||||
{
|
||||
$blueprints = [];
|
||||
$serializers = $this->app->make('flarum.api.notification_serializers');
|
||||
$serializers = [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigureNotificationTypes($blueprints, $serializers)
|
||||
);
|
||||
|
@@ -11,10 +11,10 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Filter\Filterer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -49,14 +49,9 @@ class ListDiscussionsController extends AbstractListController
|
||||
public $sortFields = ['lastPostedAt', 'commentCount', 'createdAt'];
|
||||
|
||||
/**
|
||||
* @var DiscussionRepository
|
||||
* @var DiscussionSearcher
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @var Filterer
|
||||
*/
|
||||
protected $filterer;
|
||||
protected $searcher;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
@@ -67,10 +62,9 @@ class ListDiscussionsController extends AbstractListController
|
||||
* @param DiscussionSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(DiscussionRepository $discussions, Filterer $filterer, UrlGenerator $url)
|
||||
public function __construct(DiscussionSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
$this->filterer = $filterer;
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
@@ -80,16 +74,16 @@ class ListDiscussionsController extends AbstractListController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
$filters = $this->extractFilter($request);
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
$query = $this->discussions->query();
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = array_merge($this->extractInclude($request), ['state']);
|
||||
|
||||
$results = $this->filterer->filter($actor, $query, $filters, $sort, $limit, $offset, $load);
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('discussions.index'),
|
||||
|
@@ -10,9 +10,10 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Filter\Filterer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\User\UserRepository;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -40,9 +41,9 @@ class ListUsersController extends AbstractListController
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Filterer
|
||||
* @var UserSearcher
|
||||
*/
|
||||
protected $filterer;
|
||||
protected $searcher;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
@@ -50,20 +51,13 @@ class ListUsersController extends AbstractListController
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param Filterer $filterer
|
||||
* @param UserSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
* @param UserRepository $users
|
||||
*/
|
||||
public function __construct(Filterer $filterer, UrlGenerator $url, UserRepository $users)
|
||||
public function __construct(UserSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->filterer = $filterer;
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,16 +69,16 @@ class ListUsersController extends AbstractListController
|
||||
|
||||
$actor->assertCan('viewUserList');
|
||||
|
||||
$query = $this->users->query();
|
||||
|
||||
$filters = $this->extractFilter($request);
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = $this->extractInclude($request);
|
||||
|
||||
$results = $this->filterer->filter($actor, $query, $filters, $sort, $limit, $offset, $load);
|
||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('users.index'),
|
||||
|
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class SearchDiscussionsController extends AbstractListController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = DiscussionSerializer::class;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $include = [
|
||||
'user',
|
||||
'lastPostedUser',
|
||||
'mostRelevantPost',
|
||||
'mostRelevantPost.user'
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $optionalInclude = [
|
||||
'firstPost',
|
||||
'lastPost'
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $sortFields = ['lastPostedAt', 'commentCount', 'createdAt'];
|
||||
|
||||
/**
|
||||
* @var DiscussionSearcher
|
||||
*/
|
||||
protected $searcher;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @param DiscussionSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(DiscussionSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = array_merge($this->extractInclude($request), ['state']);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('discussions.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
Discussion::setStateUser($actor);
|
||||
|
||||
$results = $results->getResults()->load($load);
|
||||
|
||||
if ($relations = array_intersect($load, ['firstPost', 'lastPost'])) {
|
||||
foreach ($results as $discussion) {
|
||||
foreach ($relations as $relation) {
|
||||
if ($discussion->$relation) {
|
||||
$discussion->$relation->discussion = $discussion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class SearchUsersController extends AbstractListController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = UserSerializer::class;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $include = ['groups'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $sortFields = [
|
||||
'username',
|
||||
'commentCount',
|
||||
'discussionCount',
|
||||
'lastSeenAt',
|
||||
'joinedAt'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var UserSearcher
|
||||
*/
|
||||
protected $searcher;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @param UserSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(UserSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
$actor->assertCan('viewUserList');
|
||||
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = $this->extractInclude($request);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('users.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
return $results->getResults();
|
||||
}
|
||||
}
|
@@ -95,13 +95,6 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$route->toController(Controller\SendConfirmationEmailController::class)
|
||||
);
|
||||
|
||||
// List users
|
||||
$map->get(
|
||||
'/search/users',
|
||||
'users.search',
|
||||
$route->toController(Controller\SearchUsersController::class)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Notifications
|
||||
@@ -170,13 +163,6 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$route->toController(Controller\DeleteDiscussionController::class)
|
||||
);
|
||||
|
||||
// Search discussions
|
||||
$map->get(
|
||||
'/search/discussions',
|
||||
'discussions.search',
|
||||
$route->toController(Controller\SearchDiscussionsController::class)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Posts
|
||||
|
@@ -82,7 +82,7 @@ abstract class AbstractModel extends Eloquent
|
||||
}
|
||||
|
||||
$this->attributes = array_map(function ($item) {
|
||||
return is_callable($item) ? $item($this) : $item;
|
||||
return is_callable($item) ? $item() : $item;
|
||||
}, $this->attributes);
|
||||
|
||||
parent::__construct($attributes);
|
||||
|
@@ -13,9 +13,6 @@ use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16
|
||||
*/
|
||||
class ConfigureNotificationTypes
|
||||
{
|
||||
/**
|
||||
|
@@ -9,9 +9,6 @@
|
||||
|
||||
namespace Flarum\Event;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, remove in beta 16. Use the Post extender instead.
|
||||
*/
|
||||
class ConfigurePostTypes
|
||||
{
|
||||
private $models;
|
||||
|
@@ -14,28 +14,11 @@ use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Csrf implements ExtenderInterface
|
||||
{
|
||||
protected $csrfExemptRoutes = [];
|
||||
protected $csrfExemptPaths = [];
|
||||
|
||||
/**
|
||||
* Exempt a named route from CSRF checks.
|
||||
*
|
||||
* @param string $routeName
|
||||
*/
|
||||
public function exemptRoute(string $routeName)
|
||||
{
|
||||
$this->csrfExemptRoutes[] = $routeName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exempt a path from csrf checks. Wildcards are supported.
|
||||
*
|
||||
* @deprecated beta 15, remove beta 16. Exempt routes should be used instead.
|
||||
*/
|
||||
public function exemptPath(string $path)
|
||||
{
|
||||
$this->csrfExemptRoutes[] = $path;
|
||||
$this->csrfExemptPaths[] = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -43,7 +26,7 @@ class Csrf implements ExtenderInterface
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.http.csrfExemptPaths', function ($existingExemptPaths) {
|
||||
return array_merge($existingExemptPaths, $this->csrfExemptRoutes);
|
||||
return array_merge($existingExemptPaths, $this->csrfExemptPaths);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ class Event implements ExtenderInterface
|
||||
* - the class attribute of a class with a public `handle` method, which accepts an instance of the event as a parameter
|
||||
*
|
||||
* @param string $event
|
||||
* @param callable|string $listener
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function listen(string $event, $listener)
|
||||
{
|
||||
|
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Filter\Filterer;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Filter implements ExtenderInterface
|
||||
{
|
||||
private $resource;
|
||||
private $filters = [];
|
||||
private $filterMutators = [];
|
||||
|
||||
/**
|
||||
* @param string $resource: The ::class attribute of the resource this applies to, which is typically an Eloquent model.
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to run when the resource is filtered.
|
||||
*
|
||||
* @param string $filterClass: The ::class attribute of the filter you are adding.
|
||||
*/
|
||||
public function addFilter(string $filterClass)
|
||||
{
|
||||
$this->filters[] = $filterClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback through which to run all filter queries after filters have been applied.
|
||||
*/
|
||||
public function addFilterMutator($callback)
|
||||
{
|
||||
$this->filterMutators[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->filters as $filter) {
|
||||
Filterer::addFilter($this->resource, $container->make($filter));
|
||||
}
|
||||
|
||||
foreach ($this->filterMutators as $mutator) {
|
||||
Filterer::addFilterMutator($this->resource, ContainerUtil::wrapCallback($mutator, $container));
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,93 +10,38 @@
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Formatter\Event\Configuring;
|
||||
use Flarum\Formatter\Formatter as ActualFormatter;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
{
|
||||
private $configurationCallbacks = [];
|
||||
private $parsingCallbacks = [];
|
||||
private $renderingCallbacks = [];
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* Configure the formatter. This can be used to add support for custom markdown/bbcode/etc tags,
|
||||
* or otherwise change the formatter. Please see documentation for the s9e text formatter library for more
|
||||
* information on how to use this.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Configurator $configurator
|
||||
*/
|
||||
public function configure($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for parsing. This can be used to modify the text that will be parsed, or to modify the parser.
|
||||
* Please note that the text to be parsed must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Parser $parser
|
||||
* - mixed $context
|
||||
* - string $text: The text to be parsed.
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $text: The text to be parsed.
|
||||
*/
|
||||
public function parse($callback)
|
||||
{
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for rendering. This can be used to modify the xml that will be rendered, or to modify the renderer.
|
||||
* Please note that the xml to be rendered must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Rendered $renderer
|
||||
* - mixed $context
|
||||
* - string $xml: The xml to be rendered.
|
||||
* - ServerRequestInterface $request
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $xml: The xml to be rendered.
|
||||
*/
|
||||
public function render($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
$this->callback = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.formatter', function ($formatter, $container) {
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$formatter->addConfigurationCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
$events = $container->make(Dispatcher::class);
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$formatter->addParsingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
$events->listen(
|
||||
Configuring::class,
|
||||
function (Configuring $event) use ($container) {
|
||||
if (is_string($this->callback)) {
|
||||
$callback = $container->make($this->callback);
|
||||
} else {
|
||||
$callback = $this->callback;
|
||||
}
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$formatter->addRenderingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
$callback($event->configurator);
|
||||
}
|
||||
|
||||
return $formatter;
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
public function onEnable(Container $container, Extension $extension)
|
||||
|
@@ -12,7 +12,6 @@ namespace Flarum\Extend;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\Foundation\Event\ClearingCache;
|
||||
use Flarum\Frontend\Assets;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
@@ -172,7 +171,11 @@ class Frontend implements ExtenderInterface
|
||||
"flarum.frontend.$this->frontend",
|
||||
function (ActualFrontend $frontend, Container $container) {
|
||||
foreach ($this->content as $content) {
|
||||
$frontend->content(ContainerUtil::wrapCallback($content, $container));
|
||||
if (is_string($content)) {
|
||||
$content = $container->make($content);
|
||||
}
|
||||
|
||||
$frontend->content($content);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -11,14 +11,12 @@ namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Model implements ExtenderInterface
|
||||
{
|
||||
private $modelClass;
|
||||
private $customRelations = [];
|
||||
|
||||
/**
|
||||
* @param string $modelClass The ::class attribute of the model you are modifying.
|
||||
@@ -50,9 +48,7 @@ class Model implements ExtenderInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default value for a given attribute, which can be an explicit value, a closure,
|
||||
* or an instance of an invokable class. Unlike with some other extenders,
|
||||
* it CANNOT be the `::class` attribute of an invokable class.
|
||||
* Add a default value for a given attribute, which can be an explicit value, or a closure.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
@@ -161,7 +157,7 @@ class Model implements ExtenderInterface
|
||||
* @param string $name: The name of the relation. This doesn't have to be anything in particular,
|
||||
* but has to be unique from other relation names for this model, and should
|
||||
* work as the name of a method.
|
||||
* @param callable|string $callback
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - $instance: An instance of this model.
|
||||
@@ -172,17 +168,15 @@ class Model implements ExtenderInterface
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function relationship(string $name, $callback)
|
||||
public function relationship(string $name, callable $callable)
|
||||
{
|
||||
$this->customRelations[$name] = $callback;
|
||||
Arr::set(AbstractModel::$customRelations, "$this->modelClass.$name", $callable);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->customRelations as $name => $callback) {
|
||||
Arr::set(AbstractModel::$customRelations, "$this->modelClass.$name", ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
// Nothing needed here.
|
||||
}
|
||||
}
|
||||
|
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Notification implements ExtenderInterface
|
||||
{
|
||||
private $blueprints = [];
|
||||
private $serializers = [];
|
||||
private $drivers = [];
|
||||
private $typesEnabledByDefault = [];
|
||||
|
||||
/**
|
||||
* @param string $blueprint The ::class attribute of the blueprint class.
|
||||
* This blueprint should implement \Flarum\Notification\Blueprint\BlueprintInterface.
|
||||
* @param string $serializer The ::class attribute of the serializer class.
|
||||
* This serializer should extend from \Flarum\Api\Serializer\AbstractSerializer.
|
||||
* @param string[] $driversEnabledByDefault The names of the drivers enabled by default for this notification type.
|
||||
* (example: alert, email).
|
||||
* @return self
|
||||
*/
|
||||
public function type(string $blueprint, string $serializer, array $driversEnabledByDefault = [])
|
||||
{
|
||||
$this->blueprints[$blueprint] = $driversEnabledByDefault;
|
||||
$this->serializers[$blueprint::getType()] = $serializer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $driverName The name of the notification driver.
|
||||
* @param string $driver The ::class attribute of the driver class.
|
||||
* This driver should implement \Flarum\Notification\Driver\NotificationDriverInterface.
|
||||
* @param string[] $typesEnabledByDefault The names of blueprint classes of types enabled by default for this driver.
|
||||
* @return self
|
||||
*/
|
||||
public function driver(string $driverName, string $driver, array $typesEnabledByDefault = [])
|
||||
{
|
||||
$this->drivers[$driverName] = $driver;
|
||||
$this->typesEnabledByDefault[$driverName] = $typesEnabledByDefault;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.notification.blueprints', function ($existingBlueprints) {
|
||||
$existingBlueprints = array_merge($existingBlueprints, $this->blueprints);
|
||||
|
||||
foreach ($this->typesEnabledByDefault as $driverName => $typesEnabledByDefault) {
|
||||
foreach ($typesEnabledByDefault as $blueprintClass) {
|
||||
if (isset($existingBlueprints[$blueprintClass]) && (! in_array($driverName, $existingBlueprints[$blueprintClass]))) {
|
||||
$existingBlueprints[$blueprintClass][] = $driverName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $existingBlueprints;
|
||||
});
|
||||
|
||||
$container->extend('flarum.api.notification_serializers', function ($existingSerializers) {
|
||||
return array_merge($existingSerializers, $this->serializers);
|
||||
});
|
||||
|
||||
$container->extend('flarum.notification.drivers', function ($existingDrivers) {
|
||||
return array_merge($existingDrivers, $this->drivers);
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Post\Post as PostModel;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Post implements ExtenderInterface
|
||||
{
|
||||
private $postTypes = [];
|
||||
|
||||
/**
|
||||
* Register a new post type. This is generally done for custom 'event posts',
|
||||
* such as those that appear when a discussion is renamed.
|
||||
*
|
||||
* @param string $postType: The ::class attribute of the custom Post type that is being added.
|
||||
*/
|
||||
public function type(string $postType)
|
||||
{
|
||||
$this->postTypes[] = $postType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->postTypes as $postType) {
|
||||
PostModel::setModel($postType::$type, $postType);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ServiceProvider implements ExtenderInterface
|
||||
{
|
||||
private $providers = [];
|
||||
|
||||
/**
|
||||
* Register a service provider.
|
||||
*
|
||||
* @param string $serviceProviderClass The ::class attribute of the service provider class.
|
||||
* @return self
|
||||
*/
|
||||
public function register(string $serviceProviderClass)
|
||||
{
|
||||
$this->providers[] = $serviceProviderClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$app = $container->make('flarum');
|
||||
|
||||
foreach ($this->providers as $provider) {
|
||||
$app->register($provider);
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ class User implements ExtenderInterface
|
||||
* This can be used to give a user permissions for groups they aren't actually in, based on context.
|
||||
* It will not change the group badges displayed for the user.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - \Flarum\User\User $user: the user in question.
|
||||
@@ -44,9 +44,9 @@ class User implements ExtenderInterface
|
||||
* The callable should return:
|
||||
* - array $groupIds: an array of ids for the groups the user belongs to.
|
||||
*/
|
||||
public function permissionGroups($callback)
|
||||
public function permissionGroups(callable $callable)
|
||||
{
|
||||
$this->groupProcessors[] = $callback;
|
||||
$this->groupProcessors[] = $callable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Validator implements ExtenderInterface
|
||||
{
|
||||
private $configurationCallbacks = [];
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* @param string $validatorClass: The ::class attribute of the validator you are modifying.
|
||||
* The validator should inherit from \Flarum\Foundation\AbstractValidator.
|
||||
*/
|
||||
public function __construct($validatorClass)
|
||||
{
|
||||
$this->validator = $validatorClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator. This is often used to adjust validation rules, but can be
|
||||
* used to make other changes to the validator as well.
|
||||
*
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - \Flarum\Foundation\AbstractValidator $flarumValidator: The Flarum validator wrapper
|
||||
* - \Illuminate\Validation\Validator $validator: The Laravel validator instance
|
||||
*/
|
||||
public function configure($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->resolving($this->validator, function ($validator, $container) {
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$validator->addConfiguration(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* This filter will only be run when a query contains a filter param with this key.
|
||||
*/
|
||||
public function getKey(): string;
|
||||
|
||||
/**
|
||||
* Filters a query.
|
||||
*
|
||||
* @param WrappedFilter $filter
|
||||
* @param string $value The value of the requested filter
|
||||
*/
|
||||
public function apply(WrappedFilter $wrappedFilter, $filterValue);
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\Search\ApplySearchParametersTrait;
|
||||
use Flarum\Search\SearchResults;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Filterer
|
||||
{
|
||||
use ApplySearchParametersTrait;
|
||||
|
||||
protected static $filters = [];
|
||||
|
||||
protected static $filterMutators = [];
|
||||
|
||||
public static function addFilter($resource, FilterInterface $filter)
|
||||
{
|
||||
if (! array_key_exists($resource, static::$filters)) {
|
||||
static::$filters[$resource] = [];
|
||||
}
|
||||
|
||||
if (! array_key_exists($filter->getKey(), static::$filters[$resource])) {
|
||||
static::$filters[$resource][$filter->getKey()] = [];
|
||||
}
|
||||
|
||||
static::$filters[$resource][$filter->getKey()][] = $filter;
|
||||
}
|
||||
|
||||
public static function addFilterMutator($resource, $mutator)
|
||||
{
|
||||
if (! array_key_exists($resource, static::$filterMutators)) {
|
||||
static::$filterMutators[$resource] = [];
|
||||
}
|
||||
|
||||
static::$filterMutators[$resource][] = $mutator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilterCriteria $criteria
|
||||
* @param int|null $limit
|
||||
* @param int $offset
|
||||
*
|
||||
* @return FilterResults
|
||||
*/
|
||||
public function filter($actor, $query, $filters, $sort = null, $limit = null, $offset = 0, array $load = [])
|
||||
{
|
||||
$resource = get_class($query->getModel());
|
||||
|
||||
$query->whereVisibleTo($actor);
|
||||
|
||||
$wrappedFilter = new WrappedFilter($query->getQuery(), $actor);
|
||||
|
||||
foreach ($filters as $filterKey => $filterValue) {
|
||||
foreach (Arr::get(static::$filters, "$resource.$filterKey", []) as $filter) {
|
||||
$filter->apply($wrappedFilter, $filterValue);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applySort($wrappedFilter, $sort);
|
||||
$this->applyOffset($wrappedFilter, $offset);
|
||||
$this->applyLimit($wrappedFilter, $limit + 1);
|
||||
|
||||
foreach (Arr::get(static::$filterMutators, $resource, []) as $mutator) {
|
||||
$mutator($query, $actor, $filters, $sort);
|
||||
}
|
||||
|
||||
// Execute the filter query and retrieve the results. We get one more
|
||||
// results than the user asked for, so that we can say if there are more
|
||||
// results. If there are, we will get rid of that extra result.
|
||||
$results = $query->get();
|
||||
|
||||
if ($areMoreResults = $limit > 0 && $results->count() > $limit) {
|
||||
$results->pop();
|
||||
}
|
||||
|
||||
$results->load($load);
|
||||
|
||||
return new SearchResults($results, $areMoreResults);
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\Search\AbstractSearch;
|
||||
|
||||
class WrappedFilter extends AbstractSearch
|
||||
{
|
||||
}
|
@@ -11,9 +11,6 @@ namespace Flarum\Formatter\Event;
|
||||
|
||||
use s9e\TextFormatter\Configurator;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Configuring
|
||||
{
|
||||
/**
|
||||
|
@@ -11,9 +11,6 @@ namespace Flarum\Formatter\Event;
|
||||
|
||||
use s9e\TextFormatter\Parser;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Parsing
|
||||
{
|
||||
/**
|
||||
|
@@ -12,9 +12,6 @@ namespace Flarum\Formatter\Event;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Rendering
|
||||
{
|
||||
/**
|
||||
|
@@ -20,12 +20,6 @@ use s9e\TextFormatter\Unparser;
|
||||
|
||||
class Formatter
|
||||
{
|
||||
protected $configurationCallbacks = [];
|
||||
|
||||
protected $parsingCallbacks = [];
|
||||
|
||||
protected $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
@@ -53,21 +47,6 @@ class Formatter
|
||||
$this->cacheDir = $cacheDir;
|
||||
}
|
||||
|
||||
public function addConfigurationCallback($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addParsingCallback($callback)
|
||||
{
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addRenderingCallback($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text.
|
||||
*
|
||||
@@ -79,13 +58,8 @@ class Formatter
|
||||
{
|
||||
$parser = $this->getParser($context);
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Parsing($parser, $context, $text));
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$text = $callback($parser, $context, $text);
|
||||
}
|
||||
|
||||
return $parser->parse($text);
|
||||
}
|
||||
|
||||
@@ -101,13 +75,8 @@ class Formatter
|
||||
{
|
||||
$renderer = $this->getRenderer();
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Rendering($renderer, $context, $xml, $request));
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$xml = $callback($renderer, $context, $xml, $request);
|
||||
}
|
||||
|
||||
return $renderer->render($xml);
|
||||
}
|
||||
|
||||
@@ -153,13 +122,8 @@ class Formatter
|
||||
$configurator->Autolink;
|
||||
$configurator->tags->onDuplicate('replace');
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Configuring($configurator));
|
||||
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$callback($configurator);
|
||||
}
|
||||
|
||||
$this->configureExternalLinks($configurator);
|
||||
|
||||
return $configurator;
|
||||
|
@@ -11,7 +11,6 @@ namespace Flarum\Forum\Content;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Controller\ListDiscussionsController;
|
||||
use Flarum\Api\Controller\SearchDiscussionsController;
|
||||
use Flarum\Frontend\Document;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
@@ -115,6 +114,6 @@ class Index
|
||||
*/
|
||||
private function getApiDocument(User $actor, array $params)
|
||||
{
|
||||
return json_decode($this->api->send(($params['filter']['q'] ? SearchDiscussionsController::class : ListDiscussionsController::class), $actor, $params)->getBody());
|
||||
return json_decode($this->api->send(ListDiscussionsController::class, $actor, $params)->getBody());
|
||||
}
|
||||
}
|
||||
|
@@ -64,9 +64,8 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.forum.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
HttpMiddleware\ShareErrorsFromSession::class
|
||||
];
|
||||
});
|
||||
@@ -79,10 +78,6 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.forum.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.forum.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.forum.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -90,7 +85,7 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.forum.routes')));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
@@ -203,8 +198,8 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$factory = $this->app->make(RouteHandlerFactory::class);
|
||||
$defaultRoute = $this->app->make('flarum.settings')->get('default_route');
|
||||
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute]['handler'])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute]['handler'];
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute];
|
||||
} else {
|
||||
$toDefaultController = $factory->toForum(Content\Index::class);
|
||||
}
|
||||
|
@@ -18,16 +18,6 @@ use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
abstract class AbstractValidator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
public function addConfiguration($callable)
|
||||
{
|
||||
$this->configuration[] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -102,17 +92,10 @@ abstract class AbstractValidator
|
||||
|
||||
$validator = $this->validator->make($attributes, $rules, $this->getMessages());
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16.
|
||||
*/
|
||||
$this->events->dispatch(
|
||||
new Validating($this, $validator)
|
||||
);
|
||||
|
||||
foreach ($this->configuration as $callable) {
|
||||
$callable($this, $validator);
|
||||
}
|
||||
|
||||
return $validator;
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ class Application
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '0.1.0-beta.14';
|
||||
const VERSION = '0.1.0-beta.14-dev';
|
||||
|
||||
/**
|
||||
* The IoC container for the Flarum application.
|
||||
|
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ContainerUtil
|
||||
{
|
||||
/**
|
||||
* Wraps a callback so that string-based invokable classes get resolved only when actually used.
|
||||
*
|
||||
* @internal Backwards compatability not guaranteed.
|
||||
*
|
||||
* @param callable|string $callback: A callable, or a ::class attribute of an invokable class
|
||||
* @param Container $container
|
||||
*/
|
||||
public static function wrapCallback($callback, Container $container)
|
||||
{
|
||||
if (is_string($callback)) {
|
||||
$callback = function () use ($container, $callback) {
|
||||
$callback = $container->make($callback);
|
||||
|
||||
return call_user_func_array($callback, func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
return $callback;
|
||||
}
|
||||
}
|
@@ -13,7 +13,6 @@ use Flarum\Foundation\AbstractValidator;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* @deprecated in Beta 15, remove in beta 16. Use the Validator extender instead.
|
||||
* The `Validating` event is called when a validator instance for a
|
||||
* model is being built. This event can be used to add custom rules/extensions
|
||||
* to the validator for when validation takes place.
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
@@ -85,9 +85,8 @@ class InstalledApp implements AppInterface
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new BasePath($this->basePath()));
|
||||
$pipe->pipe(
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.update.routes'))
|
||||
new DispatchRoute($this->container->make('flarum.update.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ class HttpServiceProvider extends AbstractServiceProvider
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.http.csrfExemptPaths', function () {
|
||||
return ['token'];
|
||||
return ['/api/token'];
|
||||
});
|
||||
|
||||
$this->app->bind(Middleware\CheckCsrfToken::class, function ($app) {
|
||||
|
@@ -28,10 +28,7 @@ class CheckCsrfToken implements Middleware
|
||||
{
|
||||
$path = $request->getAttribute('originalUri')->getPath();
|
||||
foreach ($this->exemptRoutes as $exemptRoute) {
|
||||
/**
|
||||
* @deprecated path match should be removed in beta 16, only route name match should be supported.
|
||||
*/
|
||||
if ($exemptRoute === $request->getAttribute('routeName') || fnmatch($exemptRoute, $path)) {
|
||||
if (fnmatch($exemptRoute, $path)) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class ResolveRoute implements Middleware
|
||||
class DispatchRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* @var RouteCollection
|
||||
@@ -41,7 +41,7 @@ class ResolveRoute implements Middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given request from our route collection.
|
||||
* Dispatch the given request to our route collection.
|
||||
*
|
||||
* @throws MethodNotAllowedException
|
||||
* @throws RouteNotFoundException
|
||||
@@ -59,12 +59,10 @@ class ResolveRoute implements Middleware
|
||||
case Dispatcher::METHOD_NOT_ALLOWED:
|
||||
throw new MethodNotAllowedException($method);
|
||||
case Dispatcher::FOUND:
|
||||
$request = $request
|
||||
->withAttribute('routeName', $routeInfo[1]['name'])
|
||||
->withAttribute('routeHandler', $routeInfo[1]['handler'])
|
||||
->withAttribute('routeParameters', $routeInfo[2]);
|
||||
$handler = $routeInfo[1];
|
||||
$parameters = $routeInfo[2];
|
||||
|
||||
return $handler->handle($request);
|
||||
return $handler($request, $parameters);
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class ExecuteRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* Executes the route handler resolved in ResolveRoute.
|
||||
*/
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$handler = $request->getAttribute('routeHandler');
|
||||
$parameters = $request->getAttribute('routeParameters');
|
||||
|
||||
return $handler($request, $parameters);
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class RequestUtil
|
||||
{
|
||||
public static function getActor(Request $request): User
|
||||
{
|
||||
return $request->getAttribute('actor');
|
||||
}
|
||||
|
||||
public function withActor(Request $request, User $actor): Request
|
||||
{
|
||||
return $request->withAttribute('actor', $actor);
|
||||
}
|
||||
|
||||
public function getSession(Request $request): Session
|
||||
{
|
||||
return $request->getAttribute('session');
|
||||
}
|
||||
|
||||
public function withSession(Request $request, Session $session): Request
|
||||
{
|
||||
return $request->withAttribute('session', $session);
|
||||
}
|
||||
|
||||
public function getLocale(Request $request): string
|
||||
{
|
||||
return $request->getAttribute('bypassCsrfToken');
|
||||
}
|
||||
|
||||
public function withLocale(Request $request, string $locale): Request
|
||||
{
|
||||
return $request->withAttribute('locale', $locale);
|
||||
}
|
||||
|
||||
public function getRouteName(Request $request): string
|
||||
{
|
||||
return $request->getAttribute('routeName');
|
||||
}
|
||||
|
||||
public function withRouteName(Request $request, string $routeName): Request
|
||||
{
|
||||
return $request->withAttribute('routeName', $routeName);
|
||||
}
|
||||
}
|
@@ -66,7 +66,7 @@ class RouteCollection
|
||||
$routeDatas = $this->routeParser->parse($path);
|
||||
|
||||
foreach ($routeDatas as $routeData) {
|
||||
$this->dataGenerator->addRoute($method, $routeData, ['name' => $name, 'handler' => $handler]);
|
||||
$this->dataGenerator->addRoute($method, $routeData, $handler);
|
||||
}
|
||||
|
||||
$this->reverse[$name] = $routeDatas;
|
||||
|
@@ -13,7 +13,9 @@ use Flarum\Foundation\AppInterface;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\WhoopsFormatter;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Http\Middleware\HandleErrors;
|
||||
use Flarum\Http\Middleware\StartSession;
|
||||
use Flarum\Install\Console\InstallCommand;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
@@ -36,16 +38,15 @@ class Installer implements AppInterface
|
||||
public function getRequestHandler()
|
||||
{
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new HttpMiddleware\HandleErrors(
|
||||
$pipe->pipe(new HandleErrors(
|
||||
$this->container->make(Registry::class),
|
||||
$this->container->make(WhoopsFormatter::class),
|
||||
$this->container->tagged(Reporter::class)
|
||||
));
|
||||
$pipe->pipe($this->container->make(HttpMiddleware\StartSession::class));
|
||||
$pipe->pipe($this->container->make(StartSession::class));
|
||||
$pipe->pipe(
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.install.routes'))
|
||||
new DispatchRoute($this->container->make('flarum.install.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class WritablePaths implements PrerequisiteInterface
|
||||
})->map(function ($path, $index) {
|
||||
return [
|
||||
'message' => 'The '.$this->getAbsolutePath($path).' directory is not writable.',
|
||||
'detail' => 'Please make sure your web server/PHP user has write access to this directory'.(in_array($index, $this->wildcards) ? ' and its contents' : '').'. Read the <a href="https://docs.flarum.org/install.html#folder-ownership">installation documentation</a> for a detailed explanation and steps to resolve this error.'
|
||||
'detail' => 'Please chmod this directory'.(in_array($index, $this->wildcards) ? ' and its contents' : '').' to 0775.'
|
||||
];
|
||||
});
|
||||
}
|
||||
|
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendNotificationsJob;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
|
||||
class AlertNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
if (count($users)) {
|
||||
$this->queue->push(new SendNotificationsJob($blueprint, $users));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($blueprintClass::getType(), 'alert'),
|
||||
'boolval',
|
||||
in_array('alert', $driversEnabledByDefault)
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendEmailNotificationJob;
|
||||
use Flarum\Notification\MailableInterface;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use ReflectionClass;
|
||||
|
||||
class EmailNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
if ($blueprint instanceof MailableInterface) {
|
||||
$this->mailNotifications($blueprint, $users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail a notification to a list of users.
|
||||
*
|
||||
* @param MailableInterface $blueprint
|
||||
* @param User[] $recipients
|
||||
*/
|
||||
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
|
||||
{
|
||||
foreach ($recipients as $user) {
|
||||
if ($user->shouldEmail($blueprint::getType())) {
|
||||
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
if ((new ReflectionClass($blueprintClass))->implementsInterface(MailableInterface::class)) {
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($blueprintClass::getType(), 'email'),
|
||||
'boolval',
|
||||
in_array('email', $driversEnabledByDefault)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\User\User;
|
||||
|
||||
interface NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* Conditionally sends a notification to users, generally using a queue.
|
||||
*
|
||||
* @param BlueprintInterface $blueprint
|
||||
* @param User[] $users
|
||||
* @return void
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void;
|
||||
|
||||
/**
|
||||
* Logic for registering a notification type, generally used for adding a user preference.
|
||||
*
|
||||
* @param string $blueprintClass
|
||||
* @param array $driversEnabledByDefault
|
||||
* @return void
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void;
|
||||
}
|
@@ -11,9 +11,6 @@ namespace Flarum\Notification\Event;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16
|
||||
*/
|
||||
class Sending
|
||||
{
|
||||
/**
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Notification\Job;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Event\Sending;
|
||||
use Flarum\Notification\Notification;
|
||||
use Flarum\Queue\AbstractJob;
|
||||
use Flarum\User\User;
|
||||
@@ -34,6 +35,8 @@ class SendNotificationsJob extends AbstractJob
|
||||
|
||||
public function handle()
|
||||
{
|
||||
event(new Sending($this->blueprint, $this->recipients));
|
||||
|
||||
Notification::notify($this->recipients, $this->blueprint);
|
||||
}
|
||||
}
|
||||
|
@@ -12,76 +12,51 @@ namespace Flarum\Notification;
|
||||
use Flarum\Event\ConfigureNotificationTypes;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Notification\Blueprint\DiscussionRenamedBlueprint;
|
||||
use Flarum\User\User;
|
||||
use ReflectionClass;
|
||||
|
||||
class NotificationServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.notification.drivers', function () {
|
||||
return [
|
||||
'alert' => Driver\AlertNotificationDriver::class,
|
||||
'email' => Driver\EmailNotificationDriver::class,
|
||||
];
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.notification.blueprints', function () {
|
||||
return [
|
||||
DiscussionRenamedBlueprint::class => ['alert']
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->setNotificationDrivers();
|
||||
$this->setNotificationTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register notification drivers.
|
||||
*/
|
||||
protected function setNotificationDrivers()
|
||||
{
|
||||
foreach ($this->app->make('flarum.notification.drivers') as $driverName => $driver) {
|
||||
NotificationSyncer::addNotificationDriver($driverName, $this->app->make($driver));
|
||||
}
|
||||
$this->registerNotificationTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register notification types.
|
||||
*/
|
||||
protected function setNotificationTypes()
|
||||
public function registerNotificationTypes()
|
||||
{
|
||||
$blueprints = $this->app->make('flarum.notification.blueprints');
|
||||
$blueprints = [
|
||||
DiscussionRenamedBlueprint::class => ['alert']
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigureNotificationTypes($blueprints)
|
||||
);
|
||||
|
||||
foreach ($blueprints as $blueprint => $driversEnabledByDefault) {
|
||||
$this->addType($blueprint, $driversEnabledByDefault);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addType(string $blueprint, array $driversEnabledByDefault)
|
||||
{
|
||||
Notification::setSubjectModel(
|
||||
$type = $blueprint::getType(),
|
||||
$blueprint::getSubjectModel()
|
||||
);
|
||||
|
||||
foreach (NotificationSyncer::getNotificationDrivers() as $driverName => $driver) {
|
||||
$driver->registerType(
|
||||
$blueprint,
|
||||
$driversEnabledByDefault
|
||||
foreach ($blueprints as $blueprint => $enabled) {
|
||||
Notification::setSubjectModel(
|
||||
$type = $blueprint::getType(),
|
||||
$blueprint::getSubjectModel()
|
||||
);
|
||||
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($type, 'alert'),
|
||||
'boolval',
|
||||
in_array('alert', $enabled)
|
||||
);
|
||||
|
||||
if ((new ReflectionClass($blueprint))->implementsInterface(MailableInterface::class)) {
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($type, 'email'),
|
||||
'boolval',
|
||||
in_array('email', $enabled)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,9 +10,10 @@
|
||||
namespace Flarum\Notification;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Driver\NotificationDriverInterface;
|
||||
use Flarum\Notification\Event\Sending;
|
||||
use Flarum\Notification\Job\SendEmailNotificationJob;
|
||||
use Flarum\Notification\Job\SendNotificationsJob;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
|
||||
/**
|
||||
* The Notification Syncer commits notification blueprints to the database, and
|
||||
@@ -37,11 +38,14 @@ class NotificationSyncer
|
||||
protected static $sentTo = [];
|
||||
|
||||
/**
|
||||
* A map of notification drivers.
|
||||
*
|
||||
* @var NotificationDriverInterface[]
|
||||
* @var Queue
|
||||
*/
|
||||
protected static $notificationDrivers = [];
|
||||
protected $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a notification so that it is visible to the specified users, and not
|
||||
@@ -98,13 +102,12 @@ class NotificationSyncer
|
||||
// receiving this notification for the first time (we know because they
|
||||
// didn't have a record in the database). As both operations can be
|
||||
// intensive on resources (database and mail server), we queue them.
|
||||
foreach (static::getNotificationDrivers() as $driverName => $driver) {
|
||||
$driver->send($blueprint, $newRecipients);
|
||||
if (count($newRecipients)) {
|
||||
$this->queue->push(new SendNotificationsJob($blueprint, $newRecipients));
|
||||
}
|
||||
|
||||
if (count($newRecipients)) {
|
||||
// Deprecated in beta 15, removed in beta 16
|
||||
event(new Sending($blueprint, $newRecipients));
|
||||
if ($blueprint instanceof MailableInterface) {
|
||||
$this->mailNotifications($blueprint, $newRecipients);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +150,21 @@ class NotificationSyncer
|
||||
static::$onePerUser = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail a notification to a list of users.
|
||||
*
|
||||
* @param MailableInterface $blueprint
|
||||
* @param User[] $recipients
|
||||
*/
|
||||
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
|
||||
{
|
||||
foreach ($recipients as $user) {
|
||||
if ($user->shouldEmail($blueprint::getType())) {
|
||||
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the deleted status of a list of notification records.
|
||||
*
|
||||
@@ -157,23 +175,4 @@ class NotificationSyncer
|
||||
{
|
||||
Notification::whereIn('id', $ids)->update(['is_deleted' => $isDeleted]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification driver to the list.
|
||||
*
|
||||
* @param string $driverName
|
||||
* @param NotificationDriverInterface $driver
|
||||
*/
|
||||
public static function addNotificationDriver(string $driverName, NotificationDriverInterface $driver)
|
||||
{
|
||||
static::$notificationDrivers[$driverName] = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotificationDriverInterface[]
|
||||
*/
|
||||
public static function getNotificationDrivers(): array
|
||||
{
|
||||
return static::$notificationDrivers;
|
||||
}
|
||||
}
|
||||
|
@@ -218,7 +218,7 @@ class Post extends AbstractModel
|
||||
* @param string $model The class name of the model for that type.
|
||||
* @return void
|
||||
*/
|
||||
public static function setModel(string $type, string $model)
|
||||
public static function setModel($type, $model)
|
||||
{
|
||||
static::$models[$type] = $model;
|
||||
}
|
||||
|
@@ -21,20 +21,19 @@ class PostServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
CommentPost::setFormatter($this->app->make('flarum.formatter'));
|
||||
|
||||
$this->setPostTypes();
|
||||
$this->registerPostTypes();
|
||||
|
||||
$events = $this->app->make('events');
|
||||
$events->subscribe(PostPolicy::class);
|
||||
}
|
||||
|
||||
protected function setPostTypes()
|
||||
public function registerPostTypes()
|
||||
{
|
||||
$models = [
|
||||
CommentPost::class,
|
||||
DiscussionRenamedPost::class
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16.
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigurePostTypes($models)
|
||||
);
|
||||
|
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Queue\Console;
|
||||
|
||||
use Flarum\Foundation\Config;
|
||||
|
||||
class WorkCommand extends \Illuminate\Queue\Console\WorkCommand
|
||||
{
|
||||
protected function downForMaintenance()
|
||||
{
|
||||
if ($this->option('force')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->laravel->make(Config::class);
|
||||
|
||||
return $config->inMaintenanceMode();
|
||||
}
|
||||
}
|
61
src/Queue/HackyManagerForWorker.php
Normal file
61
src/Queue/HackyManagerForWorker.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Queue;
|
||||
|
||||
use Illuminate\Contracts\Queue\Factory;
|
||||
use Illuminate\Queue\QueueManager;
|
||||
|
||||
/**
|
||||
* A hacky workaround to avoid injecting an entire QueueManager (which we don't
|
||||
* want to build) into Laravel's queue worker class.
|
||||
*
|
||||
* Laravel 6.0 will clean this up; once we upgrade, we can remove this hack and
|
||||
* directly inject the factory.
|
||||
*/
|
||||
class HackyManagerForWorker extends QueueManager implements Factory
|
||||
{
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* HackyManagerForWorker constructor.
|
||||
*
|
||||
* Needs a real connection factory to delegate to.
|
||||
*
|
||||
* @param Factory $factory
|
||||
*/
|
||||
public function __construct(Factory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a queue connection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Illuminate\Contracts\Queue\Queue
|
||||
*/
|
||||
public function connection($name = null)
|
||||
{
|
||||
return $this->factory->connection($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is in maintenance mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDownForMaintenance()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -9,8 +9,8 @@
|
||||
|
||||
namespace Flarum\Queue;
|
||||
|
||||
use Flarum\Console\Event\Configuring;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\Paths;
|
||||
@@ -22,7 +22,6 @@ use Illuminate\Queue\Console as Commands;
|
||||
use Illuminate\Queue\Events\JobFailed;
|
||||
use Illuminate\Queue\Failed\NullFailedJobProvider;
|
||||
use Illuminate\Queue\Listener as QueueListener;
|
||||
use Illuminate\Queue\QueueManager;
|
||||
use Illuminate\Queue\SyncQueue;
|
||||
use Illuminate\Queue\Worker;
|
||||
|
||||
@@ -35,7 +34,7 @@ class QueueServiceProvider extends AbstractServiceProvider
|
||||
Commands\ListFailedCommand::class,
|
||||
Commands\RestartCommand::class,
|
||||
Commands\RetryCommand::class,
|
||||
Console\WorkCommand::class,
|
||||
Commands\WorkCommand::class,
|
||||
];
|
||||
|
||||
public function register()
|
||||
@@ -62,16 +61,10 @@ class QueueServiceProvider extends AbstractServiceProvider
|
||||
});
|
||||
|
||||
$this->app->singleton(Worker::class, function ($app) {
|
||||
/** @var Config $config */
|
||||
$config = $app->make(Config::class);
|
||||
|
||||
return new Worker(
|
||||
new QueueManager($app),
|
||||
new HackyManagerForWorker($app[Factory::class]),
|
||||
$app['events'],
|
||||
$app[ExceptionHandling::class],
|
||||
function () use ($config) {
|
||||
return $config->inMaintenanceMode();
|
||||
}
|
||||
$app[ExceptionHandling::class]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -117,17 +110,17 @@ class QueueServiceProvider extends AbstractServiceProvider
|
||||
|
||||
protected function registerCommands()
|
||||
{
|
||||
$this->app->extend('flarum.console.commands', function ($commands) {
|
||||
$this->app['events']->listen(Configuring::class, function (Configuring $event) {
|
||||
$queue = $this->app->make(Queue::class);
|
||||
|
||||
// There is no need to have the queue commands when using the sync driver.
|
||||
if ($queue instanceof SyncQueue) {
|
||||
return $commands;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise add our commands, while allowing them to be overridden by those
|
||||
// already added through the container.
|
||||
return array_merge($this->commands, $commands);
|
||||
foreach ($this->commands as $command) {
|
||||
$event->addCommand($command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user