mirror of
https://github.com/flarum/core.git
synced 2025-10-11 23:14:29 +02:00
Merge remote-tracking branch 'upstream/master' into signup-fields-locking
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
language: php
|
language: php
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- 5.6
|
|
||||||
- 7.0
|
- 7.0
|
||||||
- 7.1
|
- 7.1
|
||||||
|
- 7.2
|
||||||
- hhvm
|
- hhvm
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014-2017 Toby Zerner
|
Copyright (c) 2014-2018 Toby Zerner
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@@ -20,24 +20,24 @@
|
|||||||
"docs": "http://flarum.org/docs"
|
"docs": "http://flarum.org/docs"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.6.0",
|
"php": ">=7.0",
|
||||||
"dflydev/fig-cookies": "^1.0.2",
|
"dflydev/fig-cookies": "^1.0.2",
|
||||||
"doctrine/dbal": "^2.5",
|
"doctrine/dbal": "^2.5",
|
||||||
"components/font-awesome": "^4.6",
|
"components/font-awesome": "^4.6",
|
||||||
"franzl/whoops-middleware": "^0.4.0",
|
"franzl/whoops-middleware": "^0.4.0",
|
||||||
"illuminate/bus": "5.1.*",
|
"illuminate/bus": "5.5.*",
|
||||||
"illuminate/cache": "5.1.*",
|
"illuminate/cache": "5.5.*",
|
||||||
"illuminate/config": "5.1.*",
|
"illuminate/config": "5.5.*",
|
||||||
"illuminate/container": "5.1.*",
|
"illuminate/container": "5.5.*",
|
||||||
"illuminate/contracts": "5.1.*",
|
"illuminate/contracts": "5.5.*",
|
||||||
"illuminate/database": "^5.1.31",
|
"illuminate/database": "5.5.*",
|
||||||
"illuminate/events": "5.1.*",
|
"illuminate/events": "5.5.*",
|
||||||
"illuminate/filesystem": "5.1.*",
|
"illuminate/filesystem": "5.5.*",
|
||||||
"illuminate/hashing": "5.1.*",
|
"illuminate/hashing": "5.5.*",
|
||||||
"illuminate/mail": "5.1.*",
|
"illuminate/mail": "5.5.*",
|
||||||
"illuminate/support": "5.1.*",
|
"illuminate/support": "5.5.*",
|
||||||
"illuminate/validation": "5.1.*",
|
"illuminate/validation": "5.5.*",
|
||||||
"illuminate/view": "5.1.*",
|
"illuminate/view": "5.5.*",
|
||||||
"intervention/image": "^2.3.0",
|
"intervention/image": "^2.3.0",
|
||||||
"league/flysystem": "^1.0.11",
|
"league/flysystem": "^1.0.11",
|
||||||
"league/oauth2-client": "~1.0",
|
"league/oauth2-client": "~1.0",
|
||||||
@@ -46,10 +46,11 @@
|
|||||||
"nikic/fast-route": "^0.6",
|
"nikic/fast-route": "^0.6",
|
||||||
"oyejorge/less.php": "~1.5",
|
"oyejorge/less.php": "~1.5",
|
||||||
"psr/http-message": "^1.0",
|
"psr/http-message": "^1.0",
|
||||||
"symfony/console": "^2.7",
|
"symfony/config": "^3.3",
|
||||||
"symfony/http-foundation": "^2.7",
|
"symfony/console": "^3.3",
|
||||||
"symfony/translation": "^2.7",
|
"symfony/http-foundation": "^3.3",
|
||||||
"symfony/yaml": "^2.7",
|
"symfony/translation": "^3.3",
|
||||||
|
"symfony/yaml": "^3.3",
|
||||||
"s9e/text-formatter": "^0.8.1",
|
"s9e/text-formatter": "^0.8.1",
|
||||||
"tobscure/json-api": "^0.3.0",
|
"tobscure/json-api": "^0.3.0",
|
||||||
"zendframework/zend-diactoros": "^1.6",
|
"zendframework/zend-diactoros": "^1.6",
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mockery/mockery": "^0.9.4",
|
"mockery/mockery": "^0.9.4",
|
||||||
"phpunit/phpunit": "^4.8"
|
"phpunit/phpunit": "^6.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>403 Forbidden</h1>
|
|
||||||
<p>You do not have permissions to access this page.</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>404 Not Found</h1>
|
|
||||||
<p>Looks like this page could not be found.</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>500 Internal Server Error</h1>
|
|
||||||
<p>Something went wrong on our server.</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>503 Service Unavailable</h1>
|
|
||||||
<p>This forum is down for maintenance.</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
277
js/admin/dist/app.js
vendored
277
js/admin/dist/app.js
vendored
@@ -17536,11 +17536,13 @@ System.register('flarum/components/AdminNav', ['flarum/Component', 'flarum/compo
|
|||||||
babelHelpers.createClass(AdminNav, [{
|
babelHelpers.createClass(AdminNav, [{
|
||||||
key: 'view',
|
key: 'view',
|
||||||
value: function view() {
|
value: function view() {
|
||||||
return m(SelectDropdown, {
|
return m(
|
||||||
className: 'AdminNav App-titleControl',
|
SelectDropdown,
|
||||||
buttonClassName: 'Button',
|
{
|
||||||
children: this.items().toArray()
|
className: 'AdminNav App-titleControl',
|
||||||
});
|
buttonClassName: 'Button' },
|
||||||
|
this.items().toArray()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'items',
|
key: 'items',
|
||||||
@@ -17832,8 +17834,8 @@ System.register('flarum/components/AppearancePage', ['flarum/components/Page', '
|
|||||||
m(
|
m(
|
||||||
'div',
|
'div',
|
||||||
{ className: 'AppearancePage-colors-input' },
|
{ className: 'AppearancePage-colors-input' },
|
||||||
m('input', { className: 'FormControl', type: 'color', placeholder: '#aaaaaa', value: this.primaryColor(), onchange: m.withAttr('value', this.primaryColor) }),
|
m('input', { className: 'FormControl', type: 'text', placeholder: '#aaaaaa', value: this.primaryColor(), onchange: m.withAttr('value', this.primaryColor) }),
|
||||||
m('input', { className: 'FormControl', type: 'color', placeholder: '#aaaaaa', value: this.secondaryColor(), onchange: m.withAttr('value', this.secondaryColor) })
|
m('input', { className: 'FormControl', type: 'text', placeholder: '#aaaaaa', value: this.secondaryColor(), onchange: m.withAttr('value', this.secondaryColor) })
|
||||||
),
|
),
|
||||||
Switch.component({
|
Switch.component({
|
||||||
state: this.darkMode(),
|
state: this.darkMode(),
|
||||||
@@ -18356,15 +18358,17 @@ System.register('flarum/components/Checkbox', ['flarum/Component', 'flarum/compo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});;
|
});;
|
||||||
"use strict";
|
'use strict';
|
||||||
|
|
||||||
System.register("flarum/components/DashboardPage", ["flarum/components/Page"], function (_export, _context) {
|
System.register('flarum/components/DashboardPage', ['flarum/components/Page', 'flarum/components/StatusWidget'], function (_export, _context) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Page, DashboardPage;
|
var Page, StatusWidget, DashboardPage;
|
||||||
return {
|
return {
|
||||||
setters: [function (_flarumComponentsPage) {
|
setters: [function (_flarumComponentsPage) {
|
||||||
Page = _flarumComponentsPage.default;
|
Page = _flarumComponentsPage.default;
|
||||||
|
}, function (_flarumComponentsStatusWidget) {
|
||||||
|
StatusWidget = _flarumComponentsStatusWidget.default;
|
||||||
}],
|
}],
|
||||||
execute: function () {
|
execute: function () {
|
||||||
DashboardPage = function (_Page) {
|
DashboardPage = function (_Page) {
|
||||||
@@ -18376,70 +18380,74 @@ System.register("flarum/components/DashboardPage", ["flarum/components/Page"], f
|
|||||||
}
|
}
|
||||||
|
|
||||||
babelHelpers.createClass(DashboardPage, [{
|
babelHelpers.createClass(DashboardPage, [{
|
||||||
key: "view",
|
key: 'view',
|
||||||
value: function view() {
|
value: function view() {
|
||||||
return m(
|
return m(
|
||||||
"div",
|
'div',
|
||||||
{ className: "DashboardPage" },
|
{ className: 'DashboardPage' },
|
||||||
m(
|
m(
|
||||||
"div",
|
'div',
|
||||||
{ className: "container" },
|
{ className: 'container' },
|
||||||
m(
|
this.availableWidgets()
|
||||||
"h2",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.welcome_text')
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.version_text', { version: m(
|
|
||||||
"strong",
|
|
||||||
null,
|
|
||||||
app.forum.attribute('version')
|
|
||||||
) })
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.beta_warning_text', { strong: m("strong", null) })
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"ul",
|
|
||||||
null,
|
|
||||||
m(
|
|
||||||
"li",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.contributing_text', { a: m("a", { href: "http://flarum.org/docs/contributing", target: "_blank" }) })
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"li",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.troubleshooting_text', { a: m("a", { href: "http://flarum.org/docs/troubleshooting", target: "_blank" }) })
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"li",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.support_text', { a: m("a", { href: "http://discuss.flarum.org/t/support", target: "_blank" }) })
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"li",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.features_text', { a: m("a", { href: "http://discuss.flarum.org/t/features", target: "_blank" }) })
|
|
||||||
),
|
|
||||||
m(
|
|
||||||
"li",
|
|
||||||
null,
|
|
||||||
app.translator.trans('core.admin.dashboard.extension_text', { a: m("a", { href: "http://flarum.org/docs/extend", target: "_blank" }) })
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'availableWidgets',
|
||||||
|
value: function availableWidgets() {
|
||||||
|
return [m(StatusWidget, null)];
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
return DashboardPage;
|
return DashboardPage;
|
||||||
}(Page);
|
}(Page);
|
||||||
|
|
||||||
_export("default", DashboardPage);
|
_export('default', DashboardPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});;
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
System.register('flarum/components/DashboardWidget', ['flarum/Component'], function (_export, _context) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Component, Widget;
|
||||||
|
return {
|
||||||
|
setters: [function (_flarumComponent) {
|
||||||
|
Component = _flarumComponent.default;
|
||||||
|
}],
|
||||||
|
execute: function () {
|
||||||
|
Widget = function (_Component) {
|
||||||
|
babelHelpers.inherits(Widget, _Component);
|
||||||
|
|
||||||
|
function Widget() {
|
||||||
|
babelHelpers.classCallCheck(this, Widget);
|
||||||
|
return babelHelpers.possibleConstructorReturn(this, (Widget.__proto__ || Object.getPrototypeOf(Widget)).apply(this, arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
babelHelpers.createClass(Widget, [{
|
||||||
|
key: 'view',
|
||||||
|
value: function view() {
|
||||||
|
return m(
|
||||||
|
'div',
|
||||||
|
{ className: "Widget " + this.className() },
|
||||||
|
this.content()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'className',
|
||||||
|
value: function className() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'content',
|
||||||
|
value: function content() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return Widget;
|
||||||
|
}(Component);
|
||||||
|
|
||||||
|
_export('default', Widget);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});;
|
});;
|
||||||
@@ -18509,6 +18517,10 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
|
|||||||
|
|
||||||
$menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height());
|
$menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height());
|
||||||
|
|
||||||
|
if ($menu.offset().top < 0) {
|
||||||
|
$menu.removeClass('Dropdown-menu--top');
|
||||||
|
}
|
||||||
|
|
||||||
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width());
|
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -21102,6 +21114,84 @@ System.register('flarum/components/SplitDropdown', ['flarum/components/Dropdown'
|
|||||||
});;
|
});;
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
System.register('flarum/components/StatusWidget', ['flarum/components/DashboardWidget', 'flarum/helpers/icon', 'flarum/helpers/listItems', 'flarum/utils/ItemList'], function (_export, _context) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var DashboardWidget, icon, listItems, ItemList, StatusWidget;
|
||||||
|
return {
|
||||||
|
setters: [function (_flarumComponentsDashboardWidget) {
|
||||||
|
DashboardWidget = _flarumComponentsDashboardWidget.default;
|
||||||
|
}, function (_flarumHelpersIcon) {
|
||||||
|
icon = _flarumHelpersIcon.default;
|
||||||
|
}, function (_flarumHelpersListItems) {
|
||||||
|
listItems = _flarumHelpersListItems.default;
|
||||||
|
}, function (_flarumUtilsItemList) {
|
||||||
|
ItemList = _flarumUtilsItemList.default;
|
||||||
|
}],
|
||||||
|
execute: function () {
|
||||||
|
StatusWidget = function (_DashboardWidget) {
|
||||||
|
babelHelpers.inherits(StatusWidget, _DashboardWidget);
|
||||||
|
|
||||||
|
function StatusWidget() {
|
||||||
|
babelHelpers.classCallCheck(this, StatusWidget);
|
||||||
|
return babelHelpers.possibleConstructorReturn(this, (StatusWidget.__proto__ || Object.getPrototypeOf(StatusWidget)).apply(this, arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
babelHelpers.createClass(StatusWidget, [{
|
||||||
|
key: 'className',
|
||||||
|
value: function className() {
|
||||||
|
return 'StatusWidget';
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'content',
|
||||||
|
value: function content() {
|
||||||
|
return m(
|
||||||
|
'ul',
|
||||||
|
null,
|
||||||
|
listItems(this.items().toArray())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'items',
|
||||||
|
value: function items() {
|
||||||
|
var items = new ItemList();
|
||||||
|
|
||||||
|
items.add('help', m(
|
||||||
|
'a',
|
||||||
|
{ href: 'http://flarum.org/docs/troubleshooting', target: '_blank' },
|
||||||
|
icon('question-circle'),
|
||||||
|
' ',
|
||||||
|
app.translator.trans('core.admin.dashboard.help_link')
|
||||||
|
));
|
||||||
|
|
||||||
|
items.add('version-flarum', [m(
|
||||||
|
'strong',
|
||||||
|
null,
|
||||||
|
'Flarum'
|
||||||
|
), m('br', null), app.forum.attribute('version')]);
|
||||||
|
items.add('version-php', [m(
|
||||||
|
'strong',
|
||||||
|
null,
|
||||||
|
'PHP'
|
||||||
|
), m('br', null), app.data.phpVersion]);
|
||||||
|
items.add('version-mysql', [m(
|
||||||
|
'strong',
|
||||||
|
null,
|
||||||
|
'MySQL'
|
||||||
|
), m('br', null), app.data.mysqlVersion]);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return StatusWidget;
|
||||||
|
}(DashboardWidget);
|
||||||
|
|
||||||
|
_export('default', StatusWidget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});;
|
||||||
|
'use strict';
|
||||||
|
|
||||||
System.register('flarum/components/Switch', ['flarum/components/Checkbox'], function (_export, _context) {
|
System.register('flarum/components/Switch', ['flarum/components/Checkbox'], function (_export, _context) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@@ -21255,6 +21345,52 @@ System.register('flarum/components/UploadImageButton', ['flarum/components/Butto
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});;
|
});;
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
System.register('flarum/components/Widget', ['flarum/Component'], function (_export, _context) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Component, DashboardWidget;
|
||||||
|
return {
|
||||||
|
setters: [function (_flarumComponent) {
|
||||||
|
Component = _flarumComponent.default;
|
||||||
|
}],
|
||||||
|
execute: function () {
|
||||||
|
DashboardWidget = function (_Component) {
|
||||||
|
babelHelpers.inherits(DashboardWidget, _Component);
|
||||||
|
|
||||||
|
function DashboardWidget() {
|
||||||
|
babelHelpers.classCallCheck(this, DashboardWidget);
|
||||||
|
return babelHelpers.possibleConstructorReturn(this, (DashboardWidget.__proto__ || Object.getPrototypeOf(DashboardWidget)).apply(this, arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
babelHelpers.createClass(DashboardWidget, [{
|
||||||
|
key: 'view',
|
||||||
|
value: function view() {
|
||||||
|
return m(
|
||||||
|
'div',
|
||||||
|
{ className: "DashboardWidget " + this.className() },
|
||||||
|
this.content()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'className',
|
||||||
|
value: function className() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'content',
|
||||||
|
value: function content() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return DashboardWidget;
|
||||||
|
}(Component);
|
||||||
|
|
||||||
|
_export('default', DashboardWidget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});;
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
System.register("flarum/extend", [], function (_export, _context) {
|
System.register("flarum/extend", [], function (_export, _context) {
|
||||||
@@ -22451,10 +22587,6 @@ System.register('flarum/models/User', ['flarum/Model', 'flarum/utils/stringToCol
|
|||||||
password: Model.attribute('password'),
|
password: Model.attribute('password'),
|
||||||
|
|
||||||
avatarUrl: Model.attribute('avatarUrl'),
|
avatarUrl: Model.attribute('avatarUrl'),
|
||||||
bio: Model.attribute('bio'),
|
|
||||||
bioHtml: computed('bio', function (bio) {
|
|
||||||
return bio ? '<p>' + $('<div/>').text(bio).html().replace(/\n/g, '<br>').autoLink({ rel: 'nofollow' }) + '</p>' : '';
|
|
||||||
}),
|
|
||||||
preferences: Model.attribute('preferences'),
|
preferences: Model.attribute('preferences'),
|
||||||
groups: Model.hasMany('groups'),
|
groups: Model.hasMany('groups'),
|
||||||
|
|
||||||
@@ -23317,7 +23449,7 @@ System.register('flarum/utils/extractText', [], function (_export, _context) {
|
|||||||
return vdom.map(function (element) {
|
return vdom.map(function (element) {
|
||||||
return extractText(element);
|
return extractText(element);
|
||||||
}).join('');
|
}).join('');
|
||||||
} else if ((typeof vdom === 'undefined' ? 'undefined' : babelHelpers.typeof(vdom)) === 'object') {
|
} else if ((typeof vdom === 'undefined' ? 'undefined' : babelHelpers.typeof(vdom)) === 'object' && vdom !== null) {
|
||||||
return extractText(vdom.children);
|
return extractText(vdom.children);
|
||||||
} else {
|
} else {
|
||||||
return vdom;
|
return vdom;
|
||||||
@@ -23603,7 +23735,12 @@ System.register('flarum/utils/patchMithril', ['../Component'], function (_export
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (comp.prototype && comp.prototype instanceof Component) {
|
if (comp.prototype && comp.prototype instanceof Component) {
|
||||||
return comp.component.apply(comp, args);
|
var children = args.slice(1);
|
||||||
|
if (children.length === 1 && Array.isArray(children[0])) {
|
||||||
|
children = children[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp.component(args[0], children);
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = mo.apply(this, arguments);
|
var node = mo.apply(this, arguments);
|
||||||
|
@@ -18,9 +18,9 @@ export default class AdminNav extends Component {
|
|||||||
return (
|
return (
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
className="AdminNav App-titleControl"
|
className="AdminNav App-titleControl"
|
||||||
buttonClassName="Button"
|
buttonClassName="Button">
|
||||||
children={this.items().toArray()}
|
{this.items().toArray()}
|
||||||
/>
|
</SelectDropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ import Button from 'flarum/components/Button';
|
|||||||
import Switch from 'flarum/components/Switch';
|
import Switch from 'flarum/components/Switch';
|
||||||
import EditCustomCssModal from 'flarum/components/EditCustomCssModal';
|
import EditCustomCssModal from 'flarum/components/EditCustomCssModal';
|
||||||
import EditCustomHeaderModal from 'flarum/components/EditCustomHeaderModal';
|
import EditCustomHeaderModal from 'flarum/components/EditCustomHeaderModal';
|
||||||
|
import EditCustomFooterModal from 'flarum/components/EditCustomFooterModal';
|
||||||
import UploadImageButton from 'flarum/components/UploadImageButton';
|
import UploadImageButton from 'flarum/components/UploadImageButton';
|
||||||
import saveSettings from 'flarum/utils/saveSettings';
|
import saveSettings from 'flarum/utils/saveSettings';
|
||||||
|
|
||||||
@@ -28,8 +29,8 @@ export default class AppearancePage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="AppearancePage-colors-input">
|
<div className="AppearancePage-colors-input">
|
||||||
<input className="FormControl" type="color" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
|
<input className="FormControl" type="text" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
|
||||||
<input className="FormControl" type="color" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
|
<input className="FormControl" type="text" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Switch.component({
|
{Switch.component({
|
||||||
@@ -81,6 +82,18 @@ export default class AppearancePage extends Page {
|
|||||||
})}
|
})}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
|
||||||
|
<div className="helpText">
|
||||||
|
{app.translator.trans('core.admin.appearance.custom_footer_text')}
|
||||||
|
</div>
|
||||||
|
{Button.component({
|
||||||
|
className: 'Button',
|
||||||
|
children: app.translator.trans('core.admin.appearance.edit_footer_button'),
|
||||||
|
onclick: () => app.modal.show(new EditCustomFooterModal())
|
||||||
|
})}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
|
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
|
||||||
<div className="helpText">
|
<div className="helpText">
|
||||||
|
@@ -1,22 +1,18 @@
|
|||||||
import Page from 'flarum/components/Page';
|
import Page from 'flarum/components/Page';
|
||||||
|
import StatusWidget from 'flarum/components/StatusWidget';
|
||||||
|
|
||||||
export default class DashboardPage extends Page {
|
export default class DashboardPage extends Page {
|
||||||
view() {
|
view() {
|
||||||
return (
|
return (
|
||||||
<div className="DashboardPage">
|
<div className="DashboardPage">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h2>{app.translator.trans('core.admin.dashboard.welcome_text')}</h2>
|
{this.availableWidgets()}
|
||||||
<p>{app.translator.trans('core.admin.dashboard.version_text', {version: <strong>{app.forum.attribute('version')}</strong>})}</p>
|
|
||||||
<p>{app.translator.trans('core.admin.dashboard.beta_warning_text', {strong: <strong/>})}</p>
|
|
||||||
<ul>
|
|
||||||
<li>{app.translator.trans('core.admin.dashboard.contributing_text', {a: <a href="http://flarum.org/docs/contributing" target="_blank"/>})}</li>
|
|
||||||
<li>{app.translator.trans('core.admin.dashboard.troubleshooting_text', {a: <a href="http://flarum.org/docs/troubleshooting" target="_blank"/>})}</li>
|
|
||||||
<li>{app.translator.trans('core.admin.dashboard.support_text', {a: <a href="http://discuss.flarum.org/t/support" target="_blank"/>})}</li>
|
|
||||||
<li>{app.translator.trans('core.admin.dashboard.features_text', {a: <a href="http://discuss.flarum.org/t/features" target="_blank"/>})}</li>
|
|
||||||
<li>{app.translator.trans('core.admin.dashboard.extension_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
availableWidgets() {
|
||||||
|
return [<StatusWidget/>];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
38
js/admin/src/components/DashboardWidget.js
Normal file
38
js/admin/src/components/DashboardWidget.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Component from 'flarum/Component';
|
||||||
|
|
||||||
|
export default class Widget extends Component {
|
||||||
|
view() {
|
||||||
|
return (
|
||||||
|
<div className={"Widget "+this.className()}>
|
||||||
|
{this.content()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the class name to apply to the widget.
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
className() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of the widget.
|
||||||
|
*
|
||||||
|
* @return {VirtualElement}
|
||||||
|
*/
|
||||||
|
content() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
24
js/admin/src/components/EditCustomFooterModal.js
Normal file
24
js/admin/src/components/EditCustomFooterModal.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import SettingsModal from 'flarum/components/SettingsModal';
|
||||||
|
|
||||||
|
export default class EditCustomFooterModal extends SettingsModal {
|
||||||
|
className() {
|
||||||
|
return 'EditCustomFooterModal Modal--large';
|
||||||
|
}
|
||||||
|
|
||||||
|
title() {
|
||||||
|
return app.translator.trans('core.admin.edit_footer.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
form() {
|
||||||
|
return [
|
||||||
|
<p>{app.translator.trans('core.admin.edit_footer.customize_text')}</p>,
|
||||||
|
<div className="Form-group">
|
||||||
|
<textarea className="FormControl" rows="30" bidi={this.setting('custom_footer')}/>
|
||||||
|
</div>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onsaved() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
41
js/admin/src/components/StatusWidget.js
Normal file
41
js/admin/src/components/StatusWidget.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import DashboardWidget from 'flarum/components/DashboardWidget';
|
||||||
|
import icon from 'flarum/helpers/icon';
|
||||||
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
|
export default class StatusWidget extends DashboardWidget {
|
||||||
|
className() {
|
||||||
|
return 'StatusWidget';
|
||||||
|
}
|
||||||
|
|
||||||
|
content() {
|
||||||
|
return (
|
||||||
|
<ul>{listItems(this.items().toArray())}</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
items() {
|
||||||
|
const items = new ItemList();
|
||||||
|
|
||||||
|
items.add('help', (
|
||||||
|
<a href="http://flarum.org/docs/troubleshooting" target="_blank">
|
||||||
|
{icon('question-circle')} {app.translator.trans('core.admin.dashboard.help_link')}
|
||||||
|
</a>
|
||||||
|
));
|
||||||
|
|
||||||
|
items.add('version-flarum', [<strong>Flarum</strong>, <br/>, app.forum.attribute('version')]);
|
||||||
|
items.add('version-php', [<strong>PHP</strong>, <br/>, app.data.phpVersion]);
|
||||||
|
items.add('version-mysql', [<strong>MySQL</strong>, <br/>, app.data.mysqlVersion]);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
38
js/admin/src/components/Widget.js
Normal file
38
js/admin/src/components/Widget.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Component from 'flarum/Component';
|
||||||
|
|
||||||
|
export default class DashboardWidget extends Component {
|
||||||
|
view() {
|
||||||
|
return (
|
||||||
|
<div className={"DashboardWidget "+this.className()}>
|
||||||
|
{this.content()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the class name to apply to the widget.
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
className() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of the widget.
|
||||||
|
*
|
||||||
|
* @return {VirtualElement}
|
||||||
|
*/
|
||||||
|
content() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@@ -9,8 +9,6 @@
|
|||||||
"color-thief": "v2.0",
|
"color-thief": "v2.0",
|
||||||
"mithril": "lhorie/mithril.js#v0.2.5",
|
"mithril": "lhorie/mithril.js#v0.2.5",
|
||||||
"es6-micro-loader": "caridy/es6-micro-loader#v0.2.1",
|
"es6-micro-loader": "caridy/es6-micro-loader#v0.2.1",
|
||||||
"fastclick": "~1.0.6",
|
|
||||||
"autolink": "~1.0.0",
|
|
||||||
"m.attrs.bidi": "tobscure/m.attrs.bidi",
|
"m.attrs.bidi": "tobscure/m.attrs.bidi",
|
||||||
"punycode": "http://cdnjs.cloudflare.com/ajax/libs/punycode/1.4.1/punycode.js"
|
"punycode": "http://cdnjs.cloudflare.com/ajax/libs/punycode/1.4.1/punycode.js"
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,6 @@ gulp({
|
|||||||
bowerDir + '/jquery.hotkeys/jquery.hotkeys.js',
|
bowerDir + '/jquery.hotkeys/jquery.hotkeys.js',
|
||||||
bowerDir + '/color-thief/src/color-thief.js',
|
bowerDir + '/color-thief/src/color-thief.js',
|
||||||
bowerDir + '/moment/moment.js',
|
bowerDir + '/moment/moment.js',
|
||||||
bowerDir + '/autolink/autolink-min.js',
|
|
||||||
|
|
||||||
bowerDir + '/bootstrap/js/affix.js',
|
bowerDir + '/bootstrap/js/affix.js',
|
||||||
bowerDir + '/bootstrap/js/dropdown.js',
|
bowerDir + '/bootstrap/js/dropdown.js',
|
||||||
@@ -23,7 +22,6 @@ gulp({
|
|||||||
|
|
||||||
bowerDir + '/spin.js/spin.js',
|
bowerDir + '/spin.js/spin.js',
|
||||||
bowerDir + '/spin.js/jquery.spin.js',
|
bowerDir + '/spin.js/jquery.spin.js',
|
||||||
bowerDir + '/fastclick/lib/fastclick.js',
|
|
||||||
bowerDir + '/punycode/index.js'
|
bowerDir + '/punycode/index.js'
|
||||||
],
|
],
|
||||||
modules: {
|
modules: {
|
||||||
|
1328
js/forum/dist/app.js
vendored
1328
js/forum/dist/app.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,13 @@ export default class AvatarEditor extends Component {
|
|||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not an image has been dragged over the dropzone.
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.isDraggedOver = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static initProps(props) {
|
static initProps(props) {
|
||||||
@@ -35,12 +42,17 @@ export default class AvatarEditor extends Component {
|
|||||||
const user = this.props.user;
|
const user = this.props.user;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '')}>
|
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
|
||||||
{avatar(user)}
|
{avatar(user)}
|
||||||
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
|
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
|
||||||
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||||
data-toggle="dropdown"
|
data-toggle="dropdown"
|
||||||
onclick={this.quickUpload.bind(this)}>
|
onclick={this.quickUpload.bind(this)}
|
||||||
|
ondragover={this.enableDragover.bind(this)}
|
||||||
|
ondragenter={this.enableDragover.bind(this)}
|
||||||
|
ondragleave={this.disableDragover.bind(this)}
|
||||||
|
ondragend={this.disableDragover.bind(this)}
|
||||||
|
ondrop={this.dropUpload.bind(this)}>
|
||||||
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
|
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
|
||||||
</a>
|
</a>
|
||||||
<ul className="Dropdown-menu Menu">
|
<ul className="Dropdown-menu Menu">
|
||||||
@@ -62,7 +74,7 @@ export default class AvatarEditor extends Component {
|
|||||||
Button.component({
|
Button.component({
|
||||||
icon: 'upload',
|
icon: 'upload',
|
||||||
children: app.translator.trans('core.forum.user.avatar_upload_button'),
|
children: app.translator.trans('core.forum.user.avatar_upload_button'),
|
||||||
onclick: this.upload.bind(this)
|
onclick: this.openPicker.bind(this)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -77,6 +89,40 @@ export default class AvatarEditor extends Component {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable dragover style
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
enableDragover(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.isDraggedOver = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable dragover style
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
disableDragover(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.isDraggedOver = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload avatar when file is dropped into dropzone.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
dropUpload(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.isDraggedOver = false;
|
||||||
|
this.upload(e.dataTransfer.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the user doesn't have an avatar, there's no point in showing the
|
* If the user doesn't have an avatar, there's no point in showing the
|
||||||
* controls dropdown, because only one option would be viable: uploading.
|
* controls dropdown, because only one option would be viable: uploading.
|
||||||
@@ -89,14 +135,14 @@ export default class AvatarEditor extends Component {
|
|||||||
if (!this.props.user.avatarUrl()) {
|
if (!this.props.user.avatarUrl()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.upload();
|
this.openPicker();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt the user to upload a new avatar.
|
* Upload avatar using file picker
|
||||||
*/
|
*/
|
||||||
upload() {
|
openPicker() {
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
|
|
||||||
// Create a hidden HTML input element and click on it so the user can select
|
// Create a hidden HTML input element and click on it so the user can select
|
||||||
@@ -105,24 +151,36 @@ export default class AvatarEditor extends Component {
|
|||||||
const $input = $('<input type="file">');
|
const $input = $('<input type="file">');
|
||||||
|
|
||||||
$input.appendTo('body').hide().click().on('change', e => {
|
$input.appendTo('body').hide().click().on('change', e => {
|
||||||
const data = new FormData();
|
this.upload($(e.target)[0].files[0]);
|
||||||
data.append('avatar', $(e.target)[0].files[0]);
|
|
||||||
|
|
||||||
this.loading = true;
|
|
||||||
m.redraw();
|
|
||||||
|
|
||||||
app.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
|
|
||||||
serialize: raw => raw,
|
|
||||||
data
|
|
||||||
}).then(
|
|
||||||
this.success.bind(this),
|
|
||||||
this.failure.bind(this)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload avatar
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
*/
|
||||||
|
upload(file) {
|
||||||
|
if (this.loading) return;
|
||||||
|
|
||||||
|
const user = this.props.user;
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('avatar', file);
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
m.redraw();
|
||||||
|
|
||||||
|
app.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: app.forum.attribute('apiUrl') + '/users/' + user.id() + '/avatar',
|
||||||
|
serialize: raw => raw,
|
||||||
|
data
|
||||||
|
}).then(
|
||||||
|
this.success.bind(this),
|
||||||
|
this.failure.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the user's avatar.
|
* Remove the user's avatar.
|
||||||
*/
|
*/
|
||||||
|
@@ -3,7 +3,6 @@ import ItemList from 'flarum/utils/ItemList';
|
|||||||
import ComposerButton from 'flarum/components/ComposerButton';
|
import ComposerButton from 'flarum/components/ComposerButton';
|
||||||
import listItems from 'flarum/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import classList from 'flarum/utils/classList';
|
import classList from 'flarum/utils/classList';
|
||||||
import computed from 'flarum/utils/computed';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Composer` component displays the composer. It can be loaded with a
|
* The `Composer` component displays the composer. It can be loaded with a
|
||||||
@@ -33,28 +32,6 @@ class Composer extends Component {
|
|||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
this.active = false;
|
this.active = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed the composer's current height, based on the intended height, and
|
|
||||||
* the composer's current state. This will be applied to the composer's
|
|
||||||
* content's DOM element.
|
|
||||||
*
|
|
||||||
* @return {Integer}
|
|
||||||
*/
|
|
||||||
this.computedHeight = computed('height', 'position', (height, position) => {
|
|
||||||
// If the composer is minimized, then we don't want to set a height; we'll
|
|
||||||
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
|
||||||
// make it as high as the window.
|
|
||||||
if (position === Composer.PositionEnum.MINIMIZED) {
|
|
||||||
return '';
|
|
||||||
} else if (position === Composer.PositionEnum.FULLSCREEN) {
|
|
||||||
return $(window).height();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, if it's normal or hidden, then we use the intended height.
|
|
||||||
// We don't let the composer get too small or too big, though.
|
|
||||||
return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
@@ -85,11 +62,9 @@ class Composer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config(isInitialized, context) {
|
config(isInitialized, context) {
|
||||||
let defaultHeight;
|
// Set the height of the Composer element and its contents on each redraw,
|
||||||
|
// so that they do not lose it if their DOM elements are recreated.
|
||||||
if (!isInitialized) {
|
this.updateHeight();
|
||||||
defaultHeight = this.$().height();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
|
|
||||||
@@ -97,11 +72,8 @@ class Composer extends Component {
|
|||||||
// routes, we will flag the DOM to be retained across route changes.
|
// routes, we will flag the DOM to be retained across route changes.
|
||||||
context.retain = true;
|
context.retain = true;
|
||||||
|
|
||||||
// Initialize the composer's intended height based on what the user has set
|
this.initializeHeight();
|
||||||
// it at previously, or otherwise the composer's default height. After that,
|
this.$().hide().css('bottom', -this.computedHeight());
|
||||||
// we'll hide the composer.
|
|
||||||
this.height = localStorage.getItem('composerHeight') || defaultHeight;
|
|
||||||
this.$().hide().css('bottom', -this.height);
|
|
||||||
|
|
||||||
// Whenever any of the inputs inside the composer are have focus, we want to
|
// Whenever any of the inputs inside the composer are have focus, we want to
|
||||||
// add a class to the composer to draw attention to it.
|
// add a class to the composer to draw attention to it.
|
||||||
@@ -172,8 +144,7 @@ class Composer extends Component {
|
|||||||
// height so that it fills the height of the composer, and update the
|
// height so that it fills the height of the composer, and update the
|
||||||
// body's padding.
|
// body's padding.
|
||||||
const deltaPixels = this.mouseStart - e.clientY;
|
const deltaPixels = this.mouseStart - e.clientY;
|
||||||
this.height = this.heightStart + deltaPixels;
|
this.changeHeight(this.heightStart + deltaPixels);
|
||||||
this.updateHeight();
|
|
||||||
|
|
||||||
// Update the body's padding-bottom so that no content on the page will ever
|
// Update the body's padding-bottom so that no content on the page will ever
|
||||||
// get permanently hidden behind the composer. If the user is already
|
// get permanently hidden behind the composer. If the user is already
|
||||||
@@ -182,8 +153,6 @@ class Composer extends Component {
|
|||||||
const scrollTop = $(window).scrollTop();
|
const scrollTop = $(window).scrollTop();
|
||||||
const anchorToBottom = scrollTop > 0 && scrollTop + $(window).height() >= $(document).height();
|
const anchorToBottom = scrollTop > 0 && scrollTop + $(window).height() >= $(document).height();
|
||||||
this.updateBodyPadding(anchorToBottom);
|
this.updateBodyPadding(anchorToBottom);
|
||||||
|
|
||||||
localStorage.setItem('composerHeight', this.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -482,6 +451,73 @@ class Composer extends Component {
|
|||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize default Composer height.
|
||||||
|
*/
|
||||||
|
initializeHeight() {
|
||||||
|
this.height = localStorage.getItem('composerHeight');
|
||||||
|
|
||||||
|
if (!this.height) {
|
||||||
|
this.height = this.defaultHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default height of the Composer in case none is saved.
|
||||||
|
* @returns {Integer}
|
||||||
|
*/
|
||||||
|
defaultHeight() {
|
||||||
|
return this.$().height();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum height of the Composer.
|
||||||
|
* @returns {Integer}
|
||||||
|
*/
|
||||||
|
minimumHeight() {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maxmimum height of the Composer.
|
||||||
|
* @returns {Integer}
|
||||||
|
*/
|
||||||
|
maximumHeight() {
|
||||||
|
return $(window).height() - $('#header').outerHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed the composer's current height, based on the intended height, and
|
||||||
|
* the composer's current state. This will be applied to the composer's
|
||||||
|
* content's DOM element.
|
||||||
|
* @returns {Integer|String}
|
||||||
|
*/
|
||||||
|
computedHeight() {
|
||||||
|
// If the composer is minimized, then we don't want to set a height; we'll
|
||||||
|
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
||||||
|
// make it as high as the window.
|
||||||
|
if (this.position === Composer.PositionEnum.MINIMIZED) {
|
||||||
|
return '';
|
||||||
|
} else if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||||
|
return $(window).height();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if it's normal or hidden, then we use the intended height.
|
||||||
|
// We don't let the composer get too small or too big, though.
|
||||||
|
return Math.max(this.minimumHeight(), Math.min(this.height, this.maximumHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a new Composer height and update the DOM.
|
||||||
|
* @param {Integer} height
|
||||||
|
*/
|
||||||
|
changeHeight(height) {
|
||||||
|
this.height = height;
|
||||||
|
this.updateHeight();
|
||||||
|
|
||||||
|
localStorage.setItem('composerHeight', this.height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Composer.PositionEnum = {
|
Composer.PositionEnum = {
|
||||||
|
@@ -16,39 +16,17 @@ export default class NotificationList extends Component {
|
|||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not there are more results that can be loaded.
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.moreResults = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
const groups = [];
|
const pages = app.cache.notifications || [];
|
||||||
|
|
||||||
if (app.cache.notifications) {
|
|
||||||
const discussions = {};
|
|
||||||
|
|
||||||
// Build an array of discussions which the notifications are related to,
|
|
||||||
// and add the notifications as children.
|
|
||||||
app.cache.notifications.forEach(notification => {
|
|
||||||
const subject = notification.subject();
|
|
||||||
|
|
||||||
if (typeof subject === 'undefined') return;
|
|
||||||
|
|
||||||
// Get the discussion that this notification is related to. If it's not
|
|
||||||
// directly related to a discussion, it may be related to a post or
|
|
||||||
// other entity which is related to a discussion.
|
|
||||||
let discussion = false;
|
|
||||||
if (subject instanceof Discussion) discussion = subject;
|
|
||||||
else if (subject && subject.discussion) discussion = subject.discussion();
|
|
||||||
|
|
||||||
// If the notification is not related to a discussion directly or
|
|
||||||
// indirectly, then we will assign it to a neutral group.
|
|
||||||
const key = discussion ? discussion.id() : 0;
|
|
||||||
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
|
||||||
discussions[key].notifications.push(notification);
|
|
||||||
|
|
||||||
if (groups.indexOf(discussions[key]) === -1) {
|
|
||||||
groups.push(discussions[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="NotificationList">
|
<div className="NotificationList">
|
||||||
@@ -66,8 +44,34 @@ export default class NotificationList extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="NotificationList-content">
|
<div className="NotificationList-content">
|
||||||
{groups.length
|
{pages.length ? pages.map(notifications => {
|
||||||
? groups.map(group => {
|
const groups = [];
|
||||||
|
const discussions = {};
|
||||||
|
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
const subject = notification.subject();
|
||||||
|
|
||||||
|
if (typeof subject === 'undefined') return;
|
||||||
|
|
||||||
|
// Get the discussion that this notification is related to. If it's not
|
||||||
|
// directly related to a discussion, it may be related to a post or
|
||||||
|
// other entity which is related to a discussion.
|
||||||
|
let discussion = false;
|
||||||
|
if (subject instanceof Discussion) discussion = subject;
|
||||||
|
else if (subject && subject.discussion) discussion = subject.discussion();
|
||||||
|
|
||||||
|
// If the notification is not related to a discussion directly or
|
||||||
|
// indirectly, then we will assign it to a neutral group.
|
||||||
|
const key = discussion ? discussion.id() : 0;
|
||||||
|
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||||
|
discussions[key].notifications.push(notification);
|
||||||
|
|
||||||
|
if (groups.indexOf(discussions[key]) === -1) {
|
||||||
|
groups.push(discussions[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups.map(group => {
|
||||||
const badges = group.discussion && group.discussion.badges().toArray();
|
const badges = group.discussion && group.discussion.badges().toArray();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -94,32 +98,71 @@ export default class NotificationList extends Component {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
: !this.loading
|
}) : ''}
|
||||||
? <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>
|
{this.loading
|
||||||
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
? <LoadingIndicator className="LoadingIndicator--block" />
|
||||||
|
: (pages.length ? '' : <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config(isInitialized, context) {
|
||||||
|
if (isInitialized) return;
|
||||||
|
|
||||||
|
const $notifications = this.$('.NotificationList-content');
|
||||||
|
const $scrollParent = $notifications.css('overflow') === 'auto' ? $notifications : $(window);
|
||||||
|
|
||||||
|
const scrollHandler = () => {
|
||||||
|
const scrollTop = $scrollParent.scrollTop();
|
||||||
|
const viewportHeight = $scrollParent.height();
|
||||||
|
const contentTop = $scrollParent === $notifications ? 0 : $notifications.offset().top;
|
||||||
|
const contentHeight = $notifications[0].scrollHeight;
|
||||||
|
|
||||||
|
if (this.moreResults && !this.loading && scrollTop + viewportHeight >= contentTop + contentHeight) {
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scrollParent.on('scroll', scrollHandler);
|
||||||
|
|
||||||
|
context.onunload = () => {
|
||||||
|
$scrollParent.off('scroll', scrollHandler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load notifications into the application's cache if they haven't already
|
* Load notifications into the application's cache if they haven't already
|
||||||
* been loaded.
|
* been loaded.
|
||||||
*/
|
*/
|
||||||
load() {
|
load() {
|
||||||
if (app.cache.notifications && !app.session.user.newNotificationsCount()) {
|
if (app.session.user.newNotificationsCount()) {
|
||||||
|
delete app.cache.notifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.cache.notifications) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.session.user.pushAttributes({newNotificationsCount: 0});
|
||||||
|
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the next page of notification results.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
loadMore() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
|
|
||||||
app.store.find('notifications')
|
const params = app.cache.notifications ? {page: {offset: app.cache.notifications.length * 10}} : null;
|
||||||
.then(notifications => {
|
|
||||||
app.session.user.pushAttributes({newNotificationsCount: 0});
|
return app.store.find('notifications', params)
|
||||||
app.cache.notifications = notifications.sort((a, b) => b.time() - a.time());
|
.then(this.parseResults.bind(this))
|
||||||
})
|
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -127,6 +170,21 @@ export default class NotificationList extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse results and append them to the notification list.
|
||||||
|
*
|
||||||
|
* @param {Notification[]} results
|
||||||
|
* @return {Notification[]}
|
||||||
|
*/
|
||||||
|
parseResults(results) {
|
||||||
|
app.cache.notifications = app.cache.notifications || [];
|
||||||
|
app.cache.notifications.push(results);
|
||||||
|
|
||||||
|
this.moreResults = !!results.payload.links.next;
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all of the notifications as read.
|
* Mark all of the notifications as read.
|
||||||
*/
|
*/
|
||||||
@@ -135,7 +193,9 @@ export default class NotificationList extends Component {
|
|||||||
|
|
||||||
app.session.user.pushAttributes({unreadNotificationsCount: 0});
|
app.session.user.pushAttributes({unreadNotificationsCount: 0});
|
||||||
|
|
||||||
app.cache.notifications.forEach(notification => notification.pushAttributes({isRead: true}));
|
app.cache.notifications.forEach(notifications => {
|
||||||
|
notifications.forEach(notification => notification.pushAttributes({isRead: true}))
|
||||||
|
});
|
||||||
|
|
||||||
app.request({
|
app.request({
|
||||||
url: app.forum.attribute('apiUrl') + '/notifications/read',
|
url: app.forum.attribute('apiUrl') + '/notifications/read',
|
||||||
|
@@ -126,7 +126,7 @@ class PostStream extends Component {
|
|||||||
|
|
||||||
this.visibleEnd = this.count();
|
this.visibleEnd = this.count();
|
||||||
|
|
||||||
this.loadRange(this.visibleStart, this.visibleEnd).then(() => m.redraw());
|
return this.loadRange(this.visibleStart, this.visibleEnd).then(() => m.redraw());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -82,9 +82,10 @@ export default class ReplyComposer extends ComposerBody {
|
|||||||
app.store.createRecord('posts').save(data).then(
|
app.store.createRecord('posts').save(data).then(
|
||||||
post => {
|
post => {
|
||||||
// If we're currently viewing the discussion which this reply was made
|
// If we're currently viewing the discussion which this reply was made
|
||||||
// in, then we can update the post stream.
|
// in, then we can update the post stream and scroll to the post.
|
||||||
if (app.viewingDiscussion(discussion)) {
|
if (app.viewingDiscussion(discussion)) {
|
||||||
app.current.stream.update();
|
app.current.stream.update().then(() => app.current.stream.goToNumber(post.number()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we'll create an alert message to inform the user that
|
// Otherwise, we'll create an alert message to inform the user that
|
||||||
// their reply has been posted, containing a button which will
|
// their reply has been posted, containing a button which will
|
||||||
|
@@ -82,7 +82,8 @@ export default class TextEditor extends Component {
|
|||||||
Button.component({
|
Button.component({
|
||||||
icon: 'eye',
|
icon: 'eye',
|
||||||
className: 'Button Button--icon',
|
className: 'Button Button--icon',
|
||||||
onclick: this.props.preview
|
onclick: this.props.preview,
|
||||||
|
title: app.translator.trans('core.forum.composer.preview_tooltip')
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,104 +0,0 @@
|
|||||||
import Component from 'flarum/Component';
|
|
||||||
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
|
||||||
import classList from 'flarum/utils/classList';
|
|
||||||
import extractText from 'flarum/utils/extractText';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `UserBio` component displays a user's bio, optionally letting the user
|
|
||||||
* edit it.
|
|
||||||
*/
|
|
||||||
export default class UserBio extends Component {
|
|
||||||
init() {
|
|
||||||
/**
|
|
||||||
* Whether or not the bio is currently being edited.
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
this.editing = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the bio is currently being saved.
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
view() {
|
|
||||||
const user = this.props.user;
|
|
||||||
let content;
|
|
||||||
|
|
||||||
if (this.editing) {
|
|
||||||
content = <textarea className="FormControl" placeholder={extractText(app.translator.trans('core.forum.user.bio_placeholder'))} rows="3" value={user.bio()}/>;
|
|
||||||
} else {
|
|
||||||
let subContent;
|
|
||||||
|
|
||||||
if (this.loading) {
|
|
||||||
subContent = <p className="UserBio-placeholder">{LoadingIndicator.component({size: 'tiny'})}</p>;
|
|
||||||
} else {
|
|
||||||
const bioHtml = user.bioHtml();
|
|
||||||
|
|
||||||
if (bioHtml) {
|
|
||||||
subContent = m.trust(bioHtml);
|
|
||||||
} else if (this.props.editable) {
|
|
||||||
subContent = <p className="UserBio-placeholder">{app.translator.trans('core.forum.user.bio_placeholder')}</p>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content = <div className="UserBio-content" onclick={this.edit.bind(this)}>{subContent}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'UserBio ' + classList({
|
|
||||||
editable: this.props.editable,
|
|
||||||
editing: this.editing
|
|
||||||
})}>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit the bio.
|
|
||||||
*/
|
|
||||||
edit() {
|
|
||||||
if (!this.props.editable) return;
|
|
||||||
|
|
||||||
this.editing = true;
|
|
||||||
m.redraw();
|
|
||||||
|
|
||||||
const bio = this;
|
|
||||||
const save = function(e) {
|
|
||||||
if (e.shiftKey) return;
|
|
||||||
e.preventDefault();
|
|
||||||
bio.save($(this).val());
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$('textarea').focus()
|
|
||||||
.bind('blur', save)
|
|
||||||
.bind('keydown', 'return', save);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the bio.
|
|
||||||
*
|
|
||||||
* @param {String} value
|
|
||||||
*/
|
|
||||||
save(value) {
|
|
||||||
const user = this.props.user;
|
|
||||||
|
|
||||||
if (user.bio() !== value) {
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
user.save({bio: value})
|
|
||||||
.catch(() => {})
|
|
||||||
.then(() => {
|
|
||||||
this.loading = false;
|
|
||||||
m.redraw();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.editing = false;
|
|
||||||
m.redraw();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -6,7 +6,6 @@ import avatar from 'flarum/helpers/avatar';
|
|||||||
import username from 'flarum/helpers/username';
|
import username from 'flarum/helpers/username';
|
||||||
import icon from 'flarum/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import Dropdown from 'flarum/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import UserBio from 'flarum/components/UserBio';
|
|
||||||
import AvatarEditor from 'flarum/components/AvatarEditor';
|
import AvatarEditor from 'flarum/components/AvatarEditor';
|
||||||
import listItems from 'flarum/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
@@ -82,13 +81,6 @@ export default class UserCard extends Component {
|
|||||||
const user = this.props.user;
|
const user = this.props.user;
|
||||||
const lastSeenTime = user.lastSeenTime();
|
const lastSeenTime = user.lastSeenTime();
|
||||||
|
|
||||||
items.add('bio',
|
|
||||||
UserBio.component({
|
|
||||||
user,
|
|
||||||
editable: this.props.editable
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (lastSeenTime) {
|
if (lastSeenTime) {
|
||||||
const online = user.isOnline();
|
const online = user.isOnline();
|
||||||
|
|
||||||
|
@@ -9,18 +9,27 @@ import username from 'flarum/helpers/username';
|
|||||||
* @implements SearchSource
|
* @implements SearchSource
|
||||||
*/
|
*/
|
||||||
export default class UsersSearchResults {
|
export default class UsersSearchResults {
|
||||||
|
constructor() {
|
||||||
|
this.results = {};
|
||||||
|
}
|
||||||
|
|
||||||
search(query) {
|
search(query) {
|
||||||
return app.store.find('users', {
|
return app.store.find('users', {
|
||||||
filter: {q: query},
|
filter: {q: query},
|
||||||
page: {limit: 5}
|
page: {limit: 5}
|
||||||
|
}).then(results => {
|
||||||
|
this.results[query] = results;
|
||||||
|
m.redraw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
view(query) {
|
view(query) {
|
||||||
query = query.toLowerCase();
|
query = query.toLowerCase();
|
||||||
|
|
||||||
const results = app.store.all('users')
|
const results = (this.results[query] || [])
|
||||||
.filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query));
|
.concat(app.store.all('users').filter(user => [user.username(), user.displayName()].some(value => value.toLowerCase().substr(0, query.length) === query)))
|
||||||
|
.filter((e, i, arr) => arr.lastIndexOf(e) === i)
|
||||||
|
.sort((a, b) => a.displayName().localeCompare(b.displayName()));
|
||||||
|
|
||||||
if (!results.length) return '';
|
if (!results.length) return '';
|
||||||
|
|
||||||
|
@@ -78,11 +78,7 @@ export default function boot(app) {
|
|||||||
.toggleClass('scrolled', top > offset);
|
.toggleClass('scrolled', top > offset);
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
// Initialize FastClick, which makes links and buttons much more responsive on
|
|
||||||
// touch devices.
|
|
||||||
$(() => {
|
$(() => {
|
||||||
FastClick.attach(document.body);
|
|
||||||
|
|
||||||
$('body').addClass('ontouchstart' in window ? 'touch' : 'no-touch');
|
$('body').addClass('ontouchstart' in window ? 'touch' : 'no-touch');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -162,7 +162,7 @@ export default {
|
|||||||
}
|
}
|
||||||
app.composer.show();
|
app.composer.show();
|
||||||
|
|
||||||
if (goToLast && app.viewingDiscussion(this)) {
|
if (goToLast && app.viewingDiscussion(this) && ! app.composer.isFullScreen()) {
|
||||||
app.current.stream.goToNumber('reply');
|
app.current.stream.goToNumber('reply');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -69,6 +69,10 @@ export default class Dropdown extends Component {
|
|||||||
$menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height()
|
$menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($menu.offset().top < 0) {
|
||||||
|
$menu.removeClass('Dropdown-menu--top');
|
||||||
|
}
|
||||||
|
|
||||||
$menu.toggleClass(
|
$menu.toggleClass(
|
||||||
'Dropdown-menu--right',
|
'Dropdown-menu--right',
|
||||||
isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width()
|
isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width()
|
||||||
|
@@ -16,8 +16,6 @@ Object.assign(User.prototype, {
|
|||||||
password: Model.attribute('password'),
|
password: Model.attribute('password'),
|
||||||
|
|
||||||
avatarUrl: Model.attribute('avatarUrl'),
|
avatarUrl: Model.attribute('avatarUrl'),
|
||||||
bio: Model.attribute('bio'),
|
|
||||||
bioHtml: computed('bio', bio => bio ? '<p>' + $('<div/>').text(bio).html().replace(/\n/g, '<br>').autoLink({rel: 'nofollow'}) + '</p>' : ''),
|
|
||||||
preferences: Model.attribute('preferences'),
|
preferences: Model.attribute('preferences'),
|
||||||
groups: Model.hasMany('groups'),
|
groups: Model.hasMany('groups'),
|
||||||
|
|
||||||
|
@@ -5,7 +5,12 @@ export default function patchMithril(global) {
|
|||||||
|
|
||||||
const m = function(comp, ...args) {
|
const m = function(comp, ...args) {
|
||||||
if (comp.prototype && comp.prototype instanceof Component) {
|
if (comp.prototype && comp.prototype instanceof Component) {
|
||||||
return comp.component(args[0], args.slice(1));
|
let children = args.slice(1);
|
||||||
|
if (children.length === 1 && Array.isArray(children[0])) {
|
||||||
|
children = children[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp.component(args[0], children);
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = mo.apply(this, arguments);
|
const node = mo.apply(this, arguments);
|
||||||
|
@@ -2,20 +2,46 @@
|
|||||||
background: @control-bg;
|
background: @control-bg;
|
||||||
color: @control-color;
|
color: @control-color;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.7;
|
|
||||||
|
|
||||||
@media @desktop-up {
|
@media @desktop-up {
|
||||||
.container {
|
.container {
|
||||||
max-width: 600px;
|
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
.Widget {
|
||||||
font-size: 26px;
|
background: @body-bg;
|
||||||
font-weight: 300;
|
color: @text-color;
|
||||||
margin-top: 0;
|
border-radius: @border-radius;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.StatusWidget {
|
||||||
|
color: @muted-color;
|
||||||
|
|
||||||
|
> ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 30px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&[class^="item-version-"] {
|
||||||
|
max-width: 100px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
&.item-help {
|
||||||
|
float: right;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,10 @@
|
|||||||
.AvatarEditor--noAvatar {
|
.AvatarEditor--noAvatar {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
&:hover .Dropdown-toggle, &.open .Dropdown-toggle, &.loading .Dropdown-toggle {
|
&:hover .Dropdown-toggle,
|
||||||
|
&.open .Dropdown-toggle,
|
||||||
|
&.loading .Dropdown-toggle,
|
||||||
|
&.dragover .Dropdown-toggle {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.LoadingIndicator {
|
.LoadingIndicator {
|
||||||
|
@@ -88,14 +88,11 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: fade(@body-bg, 90%);
|
background: fade(@body-bg, 90%);
|
||||||
opacity: 0;
|
display: none;
|
||||||
pointer-events: none;
|
|
||||||
border-radius: @border-radius @border-radius 0 0;
|
border-radius: @border-radius @border-radius 0 0;
|
||||||
.transition(opacity 0.2s);
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
opacity: 1;
|
display: block;
|
||||||
pointer-events: auto;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ComposerBody-editor {
|
.ComposerBody-editor {
|
||||||
@@ -119,7 +116,7 @@
|
|||||||
&:not(.minimized) {
|
&:not(.minimized) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100vh !important;
|
height: 350px !important;
|
||||||
padding-top: @header-height-phone;
|
padding-top: @header-height-phone;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
.NotificationList-content {
|
.NotificationList-content {
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& .Dropdown-toggle .Button-label {
|
& .Dropdown-toggle .Button-label {
|
||||||
|
@@ -90,37 +90,6 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
.item-bio {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.UserBio {
|
|
||||||
margin: -10px -10px 10px;
|
|
||||||
border: 1px dashed transparent;
|
|
||||||
border-radius: @border-radius;
|
|
||||||
|
|
||||||
&.editable:not(.editing) {
|
|
||||||
cursor: text;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&, textarea {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.UserBio-content {
|
|
||||||
padding: 10px 10px 1px;
|
|
||||||
}
|
|
||||||
.UserBio-placeholder {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
}
|
||||||
.UserCard-lastSeen {
|
.UserCard-lastSeen {
|
||||||
& .icon {
|
& .icon {
|
||||||
|
0
less/lib/Alert.less
Executable file → Normal file
0
less/lib/Alert.less
Executable file → Normal file
0
less/lib/AlertManager.less
Executable file → Normal file
0
less/lib/AlertManager.less
Executable file → Normal file
2
less/lib/App.less
Executable file → Normal file
2
less/lib/App.less
Executable file → Normal file
@@ -171,6 +171,8 @@
|
|||||||
.Header-logo {
|
.Header-logo {
|
||||||
max-height: 30px;
|
max-height: 30px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
// Prevent blurriness in Chrome
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On phones, the header is displayed inside of the drawer. We lay its
|
// On phones, the header is displayed inside of the drawer. We lay its
|
||||||
|
2
less/lib/Avatar.less
Executable file → Normal file
2
less/lib/Avatar.less
Executable file → Normal file
@@ -14,6 +14,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
// Prevent blurriness in Chrome
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
0
less/lib/Badge.less
Executable file → Normal file
0
less/lib/Badge.less
Executable file → Normal file
0
less/lib/Button.less
Executable file → Normal file
0
less/lib/Button.less
Executable file → Normal file
0
less/lib/Checkbox.less
Executable file → Normal file
0
less/lib/Checkbox.less
Executable file → Normal file
0
less/lib/Dropdown.less
Executable file → Normal file
0
less/lib/Dropdown.less
Executable file → Normal file
0
less/lib/Form.less
Executable file → Normal file
0
less/lib/Form.less
Executable file → Normal file
0
less/lib/FormControl.less
Executable file → Normal file
0
less/lib/FormControl.less
Executable file → Normal file
0
less/lib/LoadingIndicator.less
Executable file → Normal file
0
less/lib/LoadingIndicator.less
Executable file → Normal file
0
less/lib/Modal.less
Executable file → Normal file
0
less/lib/Modal.less
Executable file → Normal file
0
less/lib/Navigation.less
Executable file → Normal file
0
less/lib/Navigation.less
Executable file → Normal file
0
less/lib/Search.less
Executable file → Normal file
0
less/lib/Search.less
Executable file → Normal file
0
less/lib/Select.less
Executable file → Normal file
0
less/lib/Select.less
Executable file → Normal file
0
less/lib/Tooltip.less
Executable file → Normal file
0
less/lib/Tooltip.less
Executable file → Normal file
0
less/lib/lib.less
Executable file → Normal file
0
less/lib/lib.less
Executable file → Normal file
0
less/lib/mixins.less
Executable file → Normal file
0
less/lib/mixins.less
Executable file → Normal file
0
less/lib/mixins/border-radius.less
Executable file → Normal file
0
less/lib/mixins/border-radius.less
Executable file → Normal file
0
less/lib/mixins/clearfix.less
Executable file → Normal file
0
less/lib/mixins/clearfix.less
Executable file → Normal file
0
less/lib/mixins/vendor-prefixes.less
Executable file → Normal file
0
less/lib/mixins/vendor-prefixes.less
Executable file → Normal file
0
less/lib/normalize.less
vendored
Executable file → Normal file
0
less/lib/normalize.less
vendored
Executable file → Normal file
0
less/lib/print.less
Executable file → Normal file
0
less/lib/print.less
Executable file → Normal file
0
less/lib/scaffolding.less
Executable file → Normal file
0
less/lib/scaffolding.less
Executable file → Normal file
0
less/lib/sideNav.less
Executable file → Normal file
0
less/lib/sideNav.less
Executable file → Normal file
0
less/lib/variables.less
Executable file → Normal file
0
less/lib/variables.less
Executable file → Normal file
@@ -20,7 +20,7 @@ return [
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Store slugs for existing discussions
|
// Store slugs for existing discussions
|
||||||
$schema->getConnection()->table('discussions')->chunk(100, function ($discussions) use ($schema) {
|
$schema->getConnection()->table('discussions')->chunkById(100, function ($discussions) use ($schema) {
|
||||||
foreach ($discussions as $discussion) {
|
foreach ($discussions as $discussion) {
|
||||||
$schema->getConnection()->table('discussions')->where('id', $discussion->id)->update([
|
$schema->getConnection()->table('discussions')->where('id', $discussion->id)->update([
|
||||||
'slug' => Str::slug($discussion->title)
|
'slug' => Str::slug($discussion->title)
|
||||||
|
0
scripts/compile.sh
Executable file → Normal file
0
scripts/compile.sh
Executable file → Normal file
@@ -11,12 +11,23 @@
|
|||||||
|
|
||||||
namespace Flarum\Admin;
|
namespace Flarum\Admin;
|
||||||
|
|
||||||
use Flarum\Event\ExtensionWasDisabled;
|
use Flarum\Admin\Middleware\RequireAdministrateAbility;
|
||||||
use Flarum\Event\ExtensionWasEnabled;
|
use Flarum\Event\ConfigureMiddleware;
|
||||||
use Flarum\Event\SettingWasSet;
|
use Flarum\Extension\Event\Disabled;
|
||||||
|
use Flarum\Extension\Event\Enabled;
|
||||||
use Flarum\Foundation\AbstractServiceProvider;
|
use Flarum\Foundation\AbstractServiceProvider;
|
||||||
use Flarum\Http\Handler\RouteHandlerFactory;
|
use Flarum\Http\Middleware\AuthenticateWithSession;
|
||||||
|
use Flarum\Http\Middleware\DispatchRoute;
|
||||||
|
use Flarum\Http\Middleware\HandleErrors;
|
||||||
|
use Flarum\Http\Middleware\ParseJsonBody;
|
||||||
|
use Flarum\Http\Middleware\RememberFromCookie;
|
||||||
|
use Flarum\Http\Middleware\SetLocale;
|
||||||
|
use Flarum\Http\Middleware\StartSession;
|
||||||
use Flarum\Http\RouteCollection;
|
use Flarum\Http\RouteCollection;
|
||||||
|
use Flarum\Http\RouteHandlerFactory;
|
||||||
|
use Flarum\Http\UrlGenerator;
|
||||||
|
use Flarum\Settings\Event\Saved;
|
||||||
|
use Zend\Stratigility\MiddlewarePipe;
|
||||||
|
|
||||||
class AdminServiceProvider extends AbstractServiceProvider
|
class AdminServiceProvider extends AbstractServiceProvider
|
||||||
{
|
{
|
||||||
@@ -25,13 +36,35 @@ class AdminServiceProvider extends AbstractServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
$this->app->singleton(UrlGenerator::class, function () {
|
$this->app->extend(UrlGenerator::class, function (UrlGenerator $url) {
|
||||||
return new UrlGenerator($this->app, $this->app->make('flarum.admin.routes'));
|
return $url->addCollection('admin', $this->app->make('flarum.admin.routes'), 'admin');
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->singleton('flarum.admin.routes', function () {
|
$this->app->singleton('flarum.admin.routes', function () {
|
||||||
return new RouteCollection;
|
return new RouteCollection;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->app->singleton('flarum.admin.middleware', function ($app) {
|
||||||
|
$pipe = new MiddlewarePipe;
|
||||||
|
$pipe->raiseThrowables();
|
||||||
|
|
||||||
|
// All requests should first be piped through our global error handler
|
||||||
|
$debugMode = ! $app->isUpToDate() || $app->inDebugMode();
|
||||||
|
$pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode]));
|
||||||
|
|
||||||
|
$pipe->pipe($app->make(ParseJsonBody::class));
|
||||||
|
$pipe->pipe($app->make(StartSession::class));
|
||||||
|
$pipe->pipe($app->make(RememberFromCookie::class));
|
||||||
|
$pipe->pipe($app->make(AuthenticateWithSession::class));
|
||||||
|
$pipe->pipe($app->make(SetLocale::class));
|
||||||
|
$pipe->pipe($app->make(RequireAdministrateAbility::class));
|
||||||
|
|
||||||
|
event(new ConfigureMiddleware($pipe, 'admin'));
|
||||||
|
|
||||||
|
$pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.admin.routes')]));
|
||||||
|
|
||||||
|
return $pipe;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,9 +76,7 @@ class AdminServiceProvider extends AbstractServiceProvider
|
|||||||
|
|
||||||
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin');
|
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin');
|
||||||
|
|
||||||
$this->flushWebAppAssetsWhenThemeChanged();
|
$this->registerListeners();
|
||||||
|
|
||||||
$this->flushWebAppAssetsWhenExtensionsChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,30 +86,29 @@ class AdminServiceProvider extends AbstractServiceProvider
|
|||||||
*/
|
*/
|
||||||
protected function populateRoutes(RouteCollection $routes)
|
protected function populateRoutes(RouteCollection $routes)
|
||||||
{
|
{
|
||||||
$route = $this->app->make(RouteHandlerFactory::class);
|
$factory = $this->app->make(RouteHandlerFactory::class);
|
||||||
|
|
||||||
$routes->get(
|
$callback = include __DIR__.'/routes.php';
|
||||||
'/',
|
$callback($routes, $factory);
|
||||||
'index',
|
|
||||||
$route->toController(Controller\WebAppController::class)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function flushWebAppAssetsWhenThemeChanged()
|
protected function registerListeners()
|
||||||
{
|
{
|
||||||
$this->app->make('events')->listen(SettingWasSet::class, function (SettingWasSet $event) {
|
$dispatcher = $this->app->make('events');
|
||||||
|
|
||||||
|
// Flush web app assets when the theme is changed
|
||||||
|
$dispatcher->listen(Saved::class, function (Saved $event) {
|
||||||
if (preg_match('/^theme_|^custom_less$/i', $event->key)) {
|
if (preg_match('/^theme_|^custom_less$/i', $event->key)) {
|
||||||
$this->getWebAppAssets()->flushCss();
|
$this->getWebAppAssets()->flushCss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
protected function flushWebAppAssetsWhenExtensionsChanged()
|
// Flush web app assets when extensions are changed
|
||||||
{
|
$dispatcher->listen(Enabled::class, [$this, 'flushWebAppAssets']);
|
||||||
$events = $this->app->make('events');
|
$dispatcher->listen(Disabled::class, [$this, 'flushWebAppAssets']);
|
||||||
|
|
||||||
$events->listen(ExtensionWasEnabled::class, [$this, 'flushWebAppAssets']);
|
// Check the format of custom LESS code
|
||||||
$events->listen(ExtensionWasDisabled::class, [$this, 'flushWebAppAssets']);
|
$dispatcher->subscribe(CheckCustomLessFormat::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function flushWebAppAssets()
|
public function flushWebAppAssets()
|
||||||
@@ -87,10 +117,10 @@ class AdminServiceProvider extends AbstractServiceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Flarum\Http\WebApp\WebAppAssets
|
* @return \Flarum\Frontend\FrontendAssets
|
||||||
*/
|
*/
|
||||||
protected function getWebAppAssets()
|
protected function getWebAppAssets()
|
||||||
{
|
{
|
||||||
return $this->app->make(WebApp::class)->getAssets();
|
return $this->app->make(Frontend::class)->getAssets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
src/Admin/CheckCustomLessFormat.php
Normal file
43
src/Admin/CheckCustomLessFormat.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\Admin;
|
||||||
|
|
||||||
|
use Flarum\Foundation\ValidationException;
|
||||||
|
use Flarum\Settings\Event\Serializing;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Less_Exception_Parser;
|
||||||
|
use Less_Parser;
|
||||||
|
|
||||||
|
class CheckCustomLessFormat
|
||||||
|
{
|
||||||
|
public function subscribe(Dispatcher $events)
|
||||||
|
{
|
||||||
|
$events->listen(Serializing::class, [$this, 'check']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check(Serializing $event)
|
||||||
|
{
|
||||||
|
if ($event->key === 'custom_less') {
|
||||||
|
$parser = new Less_Parser();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check the custom less format before saving
|
||||||
|
// Variables names are not checked, we would have to set them and call getCss() to check them
|
||||||
|
$parser->parse($event->value);
|
||||||
|
} catch (Less_Exception_Parser $e) {
|
||||||
|
throw new ValidationException([
|
||||||
|
'custom_less' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -11,16 +11,17 @@
|
|||||||
|
|
||||||
namespace Flarum\Admin\Controller;
|
namespace Flarum\Admin\Controller;
|
||||||
|
|
||||||
use Flarum\Admin\WebApp;
|
use Flarum\Admin\Frontend;
|
||||||
use Flarum\Core\Permission;
|
|
||||||
use Flarum\Event\PrepareUnserializedSettings;
|
|
||||||
use Flarum\Extension\ExtensionManager;
|
use Flarum\Extension\ExtensionManager;
|
||||||
use Flarum\Http\Controller\AbstractWebAppController;
|
use Flarum\Frontend\AbstractFrontendController;
|
||||||
|
use Flarum\Group\Permission;
|
||||||
|
use Flarum\Settings\Event\Deserializing;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Illuminate\Contracts\Events\Dispatcher;
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
class WebAppController extends AbstractWebAppController
|
class FrontendController extends AbstractFrontendController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var SettingsRepositoryInterface
|
* @var SettingsRepositoryInterface
|
||||||
@@ -33,17 +34,24 @@ class WebAppController extends AbstractWebAppController
|
|||||||
protected $extensions;
|
protected $extensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param WebApp $webApp
|
* @var ConnectionInterface
|
||||||
|
*/
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Frontend $webApp
|
||||||
* @param Dispatcher $events
|
* @param Dispatcher $events
|
||||||
* @param SettingsRepositoryInterface $settings
|
* @param SettingsRepositoryInterface $settings
|
||||||
* @param ExtensionManager $extensions
|
* @param ExtensionManager $extensions
|
||||||
|
* @param ConnectionInterface $db
|
||||||
*/
|
*/
|
||||||
public function __construct(WebApp $webApp, Dispatcher $events, SettingsRepositoryInterface $settings, ExtensionManager $extensions)
|
public function __construct(Frontend $webApp, Dispatcher $events, SettingsRepositoryInterface $settings, ExtensionManager $extensions, ConnectionInterface $db)
|
||||||
{
|
{
|
||||||
$this->webApp = $webApp;
|
$this->webApp = $webApp;
|
||||||
$this->events = $events;
|
$this->events = $events;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
$this->extensions = $extensions;
|
$this->extensions = $extensions;
|
||||||
|
$this->db = $db;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,13 +64,16 @@ class WebAppController extends AbstractWebAppController
|
|||||||
$settings = $this->settings->all();
|
$settings = $this->settings->all();
|
||||||
|
|
||||||
$this->events->fire(
|
$this->events->fire(
|
||||||
new PrepareUnserializedSettings($settings)
|
new Deserializing($settings)
|
||||||
);
|
);
|
||||||
|
|
||||||
$view->setVariable('settings', $settings);
|
$view->setVariable('settings', $settings);
|
||||||
$view->setVariable('permissions', Permission::map());
|
$view->setVariable('permissions', Permission::map());
|
||||||
$view->setVariable('extensions', $this->extensions->getExtensions()->toArray());
|
$view->setVariable('extensions', $this->extensions->getExtensions()->toArray());
|
||||||
|
|
||||||
|
$view->setVariable('phpVersion', PHP_VERSION);
|
||||||
|
$view->setVariable('mysqlVersion', $this->db->selectOne('select version() as version')->version);
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Flarum\Admin;
|
namespace Flarum\Admin;
|
||||||
|
|
||||||
use Flarum\Http\WebApp\AbstractWebApp;
|
use Flarum\Frontend\AbstractFrontend;
|
||||||
|
|
||||||
class WebApp extends AbstractWebApp
|
class Frontend extends AbstractFrontend
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Admin\Middleware;
|
namespace Flarum\Admin\Middleware;
|
||||||
|
|
||||||
use Flarum\Core\Access\AssertPermissionTrait;
|
use Flarum\User\AssertPermissionTrait;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Zend\Stratigility\MiddlewareInterface;
|
use Zend\Stratigility\MiddlewareInterface;
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Flarum.
|
|
||||||
*
|
|
||||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Flarum\Admin;
|
|
||||||
|
|
||||||
use Flarum\Event\ConfigureMiddleware;
|
|
||||||
use Flarum\Foundation\Application;
|
|
||||||
use Flarum\Http\AbstractServer;
|
|
||||||
use Flarum\Http\Middleware\HandleErrors;
|
|
||||||
use Zend\Stratigility\MiddlewarePipe;
|
|
||||||
|
|
||||||
class Server extends AbstractServer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getMiddleware(Application $app)
|
|
||||||
{
|
|
||||||
$pipe = new MiddlewarePipe;
|
|
||||||
$pipe->raiseThrowables();
|
|
||||||
|
|
||||||
if ($app->isInstalled()) {
|
|
||||||
$path = parse_url($app->url('admin'), PHP_URL_PATH);
|
|
||||||
$errorDir = __DIR__.'/../../error';
|
|
||||||
|
|
||||||
// All requests should first be piped through our global error handler
|
|
||||||
$debugMode = ! $app->isUpToDate() || $app->inDebugMode();
|
|
||||||
$pipe->pipe($path, new HandleErrors($errorDir, $app->make('log'), $debugMode));
|
|
||||||
|
|
||||||
if ($app->isUpToDate()) {
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\ParseJsonBody'));
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\StartSession'));
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\RememberFromCookie'));
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\AuthenticateWithSession'));
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\SetLocale'));
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Admin\Middleware\RequireAdministrateAbility'));
|
|
||||||
|
|
||||||
event(new ConfigureMiddleware($pipe, $path, $this));
|
|
||||||
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.admin.routes')]));
|
|
||||||
} else {
|
|
||||||
$app->register('Flarum\Update\UpdateServiceProvider');
|
|
||||||
|
|
||||||
$pipe->pipe($path, $app->make('Flarum\Http\Middleware\DispatchRoute', ['routes' => $app->make('flarum.update.routes')]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pipe;
|
|
||||||
}
|
|
||||||
}
|
|
22
src/Admin/routes.php
Normal file
22
src/Admin/routes.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Flarum\Admin\Controller;
|
||||||
|
use Flarum\Http\RouteCollection;
|
||||||
|
use Flarum\Http\RouteHandlerFactory;
|
||||||
|
|
||||||
|
return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||||
|
$map->get(
|
||||||
|
'/',
|
||||||
|
'index',
|
||||||
|
$route->toController(Controller\FrontendController::class)
|
||||||
|
);
|
||||||
|
};
|
@@ -37,10 +37,8 @@ class ApiKey extends AbstractModel
|
|||||||
*/
|
*/
|
||||||
public static function generate()
|
public static function generate()
|
||||||
{
|
{
|
||||||
$key = new static;
|
return new static([
|
||||||
|
'id' => str_random(40)
|
||||||
$key->id = str_random(40);
|
]);
|
||||||
|
|
||||||
return $key;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,16 +12,29 @@
|
|||||||
namespace Flarum\Api;
|
namespace Flarum\Api;
|
||||||
|
|
||||||
use Flarum\Api\Controller\AbstractSerializeController;
|
use Flarum\Api\Controller\AbstractSerializeController;
|
||||||
|
use Flarum\Api\Middleware\FakeHttpMethods;
|
||||||
|
use Flarum\Api\Middleware\HandleErrors;
|
||||||
use Flarum\Api\Serializer\AbstractSerializer;
|
use Flarum\Api\Serializer\AbstractSerializer;
|
||||||
|
use Flarum\Api\Serializer\BasicDiscussionSerializer;
|
||||||
use Flarum\Api\Serializer\NotificationSerializer;
|
use Flarum\Api\Serializer\NotificationSerializer;
|
||||||
use Flarum\Event\ConfigureApiRoutes;
|
use Flarum\Event\ConfigureApiRoutes;
|
||||||
|
use Flarum\Event\ConfigureMiddleware;
|
||||||
use Flarum\Event\ConfigureNotificationTypes;
|
use Flarum\Event\ConfigureNotificationTypes;
|
||||||
use Flarum\Foundation\AbstractServiceProvider;
|
use Flarum\Foundation\AbstractServiceProvider;
|
||||||
use Flarum\Http\Handler\RouteHandlerFactory;
|
use Flarum\Http\Middleware\AuthenticateWithHeader;
|
||||||
|
use Flarum\Http\Middleware\AuthenticateWithSession;
|
||||||
|
use Flarum\Http\Middleware\DispatchRoute;
|
||||||
|
use Flarum\Http\Middleware\ParseJsonBody;
|
||||||
|
use Flarum\Http\Middleware\RememberFromCookie;
|
||||||
|
use Flarum\Http\Middleware\SetLocale;
|
||||||
|
use Flarum\Http\Middleware\StartSession;
|
||||||
use Flarum\Http\RouteCollection;
|
use Flarum\Http\RouteCollection;
|
||||||
|
use Flarum\Http\RouteHandlerFactory;
|
||||||
|
use Flarum\Http\UrlGenerator;
|
||||||
use Tobscure\JsonApi\ErrorHandler;
|
use Tobscure\JsonApi\ErrorHandler;
|
||||||
use Tobscure\JsonApi\Exception\Handler\FallbackExceptionHandler;
|
use Tobscure\JsonApi\Exception\Handler\FallbackExceptionHandler;
|
||||||
use Tobscure\JsonApi\Exception\Handler\InvalidParameterExceptionHandler;
|
use Tobscure\JsonApi\Exception\Handler\InvalidParameterExceptionHandler;
|
||||||
|
use Zend\Stratigility\MiddlewarePipe;
|
||||||
|
|
||||||
class ApiServiceProvider extends AbstractServiceProvider
|
class ApiServiceProvider extends AbstractServiceProvider
|
||||||
{
|
{
|
||||||
@@ -30,27 +43,48 @@ class ApiServiceProvider extends AbstractServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
$this->app->singleton(UrlGenerator::class, function () {
|
$this->app->extend(UrlGenerator::class, function (UrlGenerator $url) {
|
||||||
return new UrlGenerator($this->app, $this->app->make('flarum.api.routes'));
|
return $url->addCollection('api', $this->app->make('flarum.api.routes'), 'api');
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->singleton('flarum.api.routes', function () {
|
$this->app->singleton('flarum.api.routes', function () {
|
||||||
return new RouteCollection;
|
return new RouteCollection;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->app->singleton('flarum.api.middleware', function ($app) {
|
||||||
|
$pipe = new MiddlewarePipe;
|
||||||
|
$pipe->raiseThrowables();
|
||||||
|
|
||||||
|
$pipe->pipe($app->make(HandleErrors::class));
|
||||||
|
|
||||||
|
$pipe->pipe($app->make(ParseJsonBody::class));
|
||||||
|
$pipe->pipe($app->make(FakeHttpMethods::class));
|
||||||
|
$pipe->pipe($app->make(StartSession::class));
|
||||||
|
$pipe->pipe($app->make(RememberFromCookie::class));
|
||||||
|
$pipe->pipe($app->make(AuthenticateWithSession::class));
|
||||||
|
$pipe->pipe($app->make(AuthenticateWithHeader::class));
|
||||||
|
$pipe->pipe($app->make(SetLocale::class));
|
||||||
|
|
||||||
|
event(new ConfigureMiddleware($pipe, 'api'));
|
||||||
|
|
||||||
|
$pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.api.routes')]));
|
||||||
|
|
||||||
|
return $pipe;
|
||||||
|
});
|
||||||
|
|
||||||
$this->app->singleton(ErrorHandler::class, function () {
|
$this->app->singleton(ErrorHandler::class, function () {
|
||||||
$handler = new ErrorHandler;
|
$handler = new ErrorHandler;
|
||||||
|
|
||||||
$handler->registerHandler(new Handler\FloodingExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\FloodingExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\IlluminateValidationExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\IlluminateValidationExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\InvalidAccessTokenExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\InvalidAccessTokenExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\InvalidConfirmationTokenExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\InvalidConfirmationTokenExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\MethodNotAllowedExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\MethodNotAllowedExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\ModelNotFoundExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\ModelNotFoundExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\PermissionDeniedExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\PermissionDeniedExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\RouteNotFoundExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\RouteNotFoundExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\TokenMismatchExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\TokenMismatchExceptionHandler);
|
||||||
$handler->registerHandler(new Handler\ValidationExceptionHandler);
|
$handler->registerHandler(new ExceptionHandler\ValidationExceptionHandler);
|
||||||
$handler->registerHandler(new InvalidParameterExceptionHandler);
|
$handler->registerHandler(new InvalidParameterExceptionHandler);
|
||||||
$handler->registerHandler(new FallbackExceptionHandler($this->app->inDebugMode()));
|
$handler->registerHandler(new FallbackExceptionHandler($this->app->inDebugMode()));
|
||||||
|
|
||||||
@@ -81,7 +115,7 @@ class ApiServiceProvider extends AbstractServiceProvider
|
|||||||
{
|
{
|
||||||
$blueprints = [];
|
$blueprints = [];
|
||||||
$serializers = [
|
$serializers = [
|
||||||
'discussionRenamed' => 'Flarum\Api\Serializer\DiscussionBasicSerializer'
|
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->app->make('events')->fire(
|
$this->app->make('events')->fire(
|
||||||
@@ -100,298 +134,13 @@ class ApiServiceProvider extends AbstractServiceProvider
|
|||||||
*/
|
*/
|
||||||
protected function populateRoutes(RouteCollection $routes)
|
protected function populateRoutes(RouteCollection $routes)
|
||||||
{
|
{
|
||||||
$route = $this->app->make(RouteHandlerFactory::class);
|
$factory = $this->app->make(RouteHandlerFactory::class);
|
||||||
|
|
||||||
// Get forum information
|
$callback = include __DIR__.'/routes.php';
|
||||||
$routes->get(
|
$callback($routes, $factory);
|
||||||
'/forum',
|
|
||||||
'forum.show',
|
|
||||||
$route->toController(Controller\ShowForumController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Retrieve authentication token
|
|
||||||
$routes->post(
|
|
||||||
'/token',
|
|
||||||
'token',
|
|
||||||
$route->toController(Controller\TokenController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send forgot password email
|
|
||||||
$routes->post(
|
|
||||||
'/forgot',
|
|
||||||
'forgot',
|
|
||||||
$route->toController(Controller\ForgotPasswordController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Users
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List users
|
|
||||||
$routes->get(
|
|
||||||
'/users',
|
|
||||||
'users.index',
|
|
||||||
$route->toController(Controller\ListUsersController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register a user
|
|
||||||
$routes->post(
|
|
||||||
'/users',
|
|
||||||
'users.create',
|
|
||||||
$route->toController(Controller\CreateUserController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get a single user
|
|
||||||
$routes->get(
|
|
||||||
'/users/{id}',
|
|
||||||
'users.show',
|
|
||||||
$route->toController(Controller\ShowUserController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Edit a user
|
|
||||||
$routes->patch(
|
|
||||||
'/users/{id}',
|
|
||||||
'users.update',
|
|
||||||
$route->toController(Controller\UpdateUserController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete a user
|
|
||||||
$routes->delete(
|
|
||||||
'/users/{id}',
|
|
||||||
'users.delete',
|
|
||||||
$route->toController(Controller\DeleteUserController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Upload avatar
|
|
||||||
$routes->post(
|
|
||||||
'/users/{id}/avatar',
|
|
||||||
'users.avatar.upload',
|
|
||||||
$route->toController(Controller\UploadAvatarController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove avatar
|
|
||||||
$routes->delete(
|
|
||||||
'/users/{id}/avatar',
|
|
||||||
'users.avatar.delete',
|
|
||||||
$route->toController(Controller\DeleteAvatarController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// send confirmation email
|
|
||||||
$routes->post(
|
|
||||||
'/users/{id}/send-confirmation',
|
|
||||||
'users.confirmation.send',
|
|
||||||
$route->toController(Controller\SendConfirmationEmailController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Notifications
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List notifications for the current user
|
|
||||||
$routes->get(
|
|
||||||
'/notifications',
|
|
||||||
'notifications.index',
|
|
||||||
$route->toController(Controller\ListNotificationsController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mark all notifications as read
|
|
||||||
$routes->post(
|
|
||||||
'/notifications/read',
|
|
||||||
'notifications.readAll',
|
|
||||||
$route->toController(Controller\ReadAllNotificationsController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mark a single notification as read
|
|
||||||
$routes->patch(
|
|
||||||
'/notifications/{id}',
|
|
||||||
'notifications.update',
|
|
||||||
$route->toController(Controller\UpdateNotificationController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Discussions
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List discussions
|
|
||||||
$routes->get(
|
|
||||||
'/discussions',
|
|
||||||
'discussions.index',
|
|
||||||
$route->toController(Controller\ListDiscussionsController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a discussion
|
|
||||||
$routes->post(
|
|
||||||
'/discussions',
|
|
||||||
'discussions.create',
|
|
||||||
$route->toController(Controller\CreateDiscussionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show a single discussion
|
|
||||||
$routes->get(
|
|
||||||
'/discussions/{id}',
|
|
||||||
'discussions.show',
|
|
||||||
$route->toController(Controller\ShowDiscussionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Edit a discussion
|
|
||||||
$routes->patch(
|
|
||||||
'/discussions/{id}',
|
|
||||||
'discussions.update',
|
|
||||||
$route->toController(Controller\UpdateDiscussionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete a discussion
|
|
||||||
$routes->delete(
|
|
||||||
'/discussions/{id}',
|
|
||||||
'discussions.delete',
|
|
||||||
$route->toController(Controller\DeleteDiscussionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Posts
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List posts, usually for a discussion
|
|
||||||
$routes->get(
|
|
||||||
'/posts',
|
|
||||||
'posts.index',
|
|
||||||
$route->toController(Controller\ListPostsController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a post
|
|
||||||
$routes->post(
|
|
||||||
'/posts',
|
|
||||||
'posts.create',
|
|
||||||
$route->toController(Controller\CreatePostController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show a single or multiple posts by ID
|
|
||||||
$routes->get(
|
|
||||||
'/posts/{id}',
|
|
||||||
'posts.show',
|
|
||||||
$route->toController(Controller\ShowPostController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Edit a post
|
|
||||||
$routes->patch(
|
|
||||||
'/posts/{id}',
|
|
||||||
'posts.update',
|
|
||||||
$route->toController(Controller\UpdatePostController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete a post
|
|
||||||
$routes->delete(
|
|
||||||
'/posts/{id}',
|
|
||||||
'posts.delete',
|
|
||||||
$route->toController(Controller\DeletePostController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Groups
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List groups
|
|
||||||
$routes->get(
|
|
||||||
'/groups',
|
|
||||||
'groups.index',
|
|
||||||
$route->toController(Controller\ListGroupsController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a group
|
|
||||||
$routes->post(
|
|
||||||
'/groups',
|
|
||||||
'groups.create',
|
|
||||||
$route->toController(Controller\CreateGroupController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Edit a group
|
|
||||||
$routes->patch(
|
|
||||||
'/groups/{id}',
|
|
||||||
'groups.update',
|
|
||||||
$route->toController(Controller\UpdateGroupController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete a group
|
|
||||||
$routes->delete(
|
|
||||||
'/groups/{id}',
|
|
||||||
'groups.delete',
|
|
||||||
$route->toController(Controller\DeleteGroupController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Administration
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Toggle an extension
|
|
||||||
$routes->patch(
|
|
||||||
'/extensions/{name}',
|
|
||||||
'extensions.update',
|
|
||||||
$route->toController(Controller\UpdateExtensionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Uninstall an extension
|
|
||||||
$routes->delete(
|
|
||||||
'/extensions/{name}',
|
|
||||||
'extensions.delete',
|
|
||||||
$route->toController(Controller\UninstallExtensionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update settings
|
|
||||||
$routes->post(
|
|
||||||
'/settings',
|
|
||||||
'settings',
|
|
||||||
$route->toController(Controller\SetSettingsController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update a permission
|
|
||||||
$routes->post(
|
|
||||||
'/permission',
|
|
||||||
'permission',
|
|
||||||
$route->toController(Controller\SetPermissionController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Upload a logo
|
|
||||||
$routes->post(
|
|
||||||
'/logo',
|
|
||||||
'logo',
|
|
||||||
$route->toController(Controller\UploadLogoController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove the logo
|
|
||||||
$routes->delete(
|
|
||||||
'/logo',
|
|
||||||
'logo.delete',
|
|
||||||
$route->toController(Controller\DeleteLogoController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Upload a favicon
|
|
||||||
$routes->post(
|
|
||||||
'/favicon',
|
|
||||||
'favicon',
|
|
||||||
$route->toController(Controller\UploadFaviconController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove the favicon
|
|
||||||
$routes->delete(
|
|
||||||
'/favicon',
|
|
||||||
'favicon.delete',
|
|
||||||
$route->toController(Controller\DeleteFaviconController::class)
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->app->make('events')->fire(
|
$this->app->make('events')->fire(
|
||||||
new ConfigureApiRoutes($routes, $route)
|
new ConfigureApiRoutes($routes, $factory)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,9 +12,9 @@
|
|||||||
namespace Flarum\Api;
|
namespace Flarum\Api;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Flarum\Core\User;
|
|
||||||
use Flarum\Foundation\Application;
|
use Flarum\Foundation\Application;
|
||||||
use Flarum\Http\Controller\ControllerInterface;
|
use Flarum\Http\Controller\ControllerInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ namespace Flarum\Api\Controller;
|
|||||||
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
abstract class AbstractCreateController extends AbstractResourceController
|
abstract class AbstractCreateController extends AbstractShowController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
@@ -14,7 +14,7 @@ namespace Flarum\Api\Controller;
|
|||||||
use Tobscure\JsonApi\Collection;
|
use Tobscure\JsonApi\Collection;
|
||||||
use Tobscure\JsonApi\SerializerInterface;
|
use Tobscure\JsonApi\SerializerInterface;
|
||||||
|
|
||||||
abstract class AbstractCollectionController extends AbstractSerializeController
|
abstract class AbstractListController extends AbstractSerializeController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Api\Event\WillGetData;
|
||||||
|
use Flarum\Api\Event\WillSerializeData;
|
||||||
use Flarum\Api\JsonApiResponse;
|
use Flarum\Api\JsonApiResponse;
|
||||||
use Flarum\Event\ConfigureApiController;
|
|
||||||
use Flarum\Event\PrepareApiData;
|
|
||||||
use Flarum\Http\Controller\ControllerInterface;
|
use Flarum\Http\Controller\ControllerInterface;
|
||||||
use Illuminate\Contracts\Container\Container;
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Contracts\Events\Dispatcher;
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
@@ -91,13 +91,13 @@ abstract class AbstractSerializeController implements ControllerInterface
|
|||||||
$document = new Document;
|
$document = new Document;
|
||||||
|
|
||||||
static::$events->fire(
|
static::$events->fire(
|
||||||
new ConfigureApiController($this)
|
new WillGetData($this)
|
||||||
);
|
);
|
||||||
|
|
||||||
$data = $this->data($request, $document);
|
$data = $this->data($request, $document);
|
||||||
|
|
||||||
static::$events->fire(
|
static::$events->fire(
|
||||||
new PrepareApiData($this, $data, $request, $document)
|
new WillSerializeData($this, $data, $request, $document)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serializer = static::$container->make($this->serializer);
|
$serializer = static::$container->make($this->serializer);
|
||||||
|
@@ -14,7 +14,7 @@ namespace Flarum\Api\Controller;
|
|||||||
use Tobscure\JsonApi\Resource;
|
use Tobscure\JsonApi\Resource;
|
||||||
use Tobscure\JsonApi\SerializerInterface;
|
use Tobscure\JsonApi\SerializerInterface;
|
||||||
|
|
||||||
abstract class AbstractResourceController extends AbstractSerializeController
|
abstract class AbstractShowController extends AbstractSerializeController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
@@ -11,9 +11,10 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\ReadDiscussion;
|
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||||
use Flarum\Core\Command\StartDiscussion;
|
use Flarum\Discussion\Command\ReadDiscussion;
|
||||||
use Flarum\Core\Post\Floodgate;
|
use Flarum\Discussion\Command\StartDiscussion;
|
||||||
|
use Flarum\Post\Floodgate;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
@@ -23,7 +24,7 @@ class CreateDiscussionController extends AbstractCreateController
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\DiscussionSerializer';
|
public $serializer = DiscussionSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\CreateGroup;
|
use Flarum\Api\Serializer\GroupSerializer;
|
||||||
|
use Flarum\Group\Command\CreateGroup;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
@@ -21,7 +22,7 @@ class CreateGroupController extends AbstractCreateController
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\GroupSerializer';
|
public $serializer = GroupSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Dispatcher
|
* @var Dispatcher
|
||||||
|
@@ -11,9 +11,10 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\PostReply;
|
use Flarum\Api\Serializer\PostSerializer;
|
||||||
use Flarum\Core\Command\ReadDiscussion;
|
use Flarum\Discussion\Command\ReadDiscussion;
|
||||||
use Flarum\Core\Post\Floodgate;
|
use Flarum\Post\Command\PostReply;
|
||||||
|
use Flarum\Post\Floodgate;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
@@ -23,7 +24,7 @@ class CreatePostController extends AbstractCreateController
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\PostSerializer';
|
public $serializer = PostSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
@@ -41,13 +42,13 @@ class CreatePostController extends AbstractCreateController
|
|||||||
protected $bus;
|
protected $bus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Floodgate
|
* @var \Flarum\Post\Floodgate
|
||||||
*/
|
*/
|
||||||
protected $floodgate;
|
protected $floodgate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Dispatcher $bus
|
* @param Dispatcher $bus
|
||||||
* @param Floodgate $floodgate
|
* @param \Flarum\Post\Floodgate $floodgate
|
||||||
*/
|
*/
|
||||||
public function __construct(Dispatcher $bus, Floodgate $floodgate)
|
public function __construct(Dispatcher $bus, Floodgate $floodgate)
|
||||||
{
|
{
|
||||||
@@ -83,7 +84,7 @@ class CreatePostController extends AbstractCreateController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$discussion = $post->discussion;
|
$discussion = $post->discussion;
|
||||||
$discussion->posts = $discussion->postsVisibleTo($actor)->orderBy('time')->lists('id');
|
$discussion->posts = $discussion->postsVisibleTo($actor)->orderBy('time')->pluck('id');
|
||||||
|
|
||||||
return $post;
|
return $post;
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\RegisterUser;
|
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||||
|
use Flarum\User\Command\RegisterUser;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
@@ -21,7 +22,7 @@ class CreateUserController extends AbstractCreateController
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\CurrentUserSerializer';
|
public $serializer = CurrentUserSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Dispatcher
|
* @var Dispatcher
|
||||||
|
@@ -11,17 +11,18 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\DeleteAvatar;
|
use Flarum\Api\Serializer\UserSerializer;
|
||||||
|
use Flarum\User\Command\DeleteAvatar;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class DeleteAvatarController extends AbstractResourceController
|
class DeleteAvatarController extends AbstractShowController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\UserSerializer';
|
public $serializer = UserSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Dispatcher
|
* @var Dispatcher
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\DeleteDiscussion;
|
use Flarum\Discussion\Command\DeleteDiscussion;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Access\AssertPermissionTrait;
|
|
||||||
use Flarum\Foundation\Application;
|
use Flarum\Foundation\Application;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\AssertPermissionTrait;
|
||||||
use League\Flysystem\Adapter\Local;
|
use League\Flysystem\Adapter\Local;
|
||||||
use League\Flysystem\Filesystem;
|
use League\Flysystem\Filesystem;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\DeleteGroup;
|
use Flarum\Group\Command\DeleteGroup;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Access\AssertPermissionTrait;
|
|
||||||
use Flarum\Foundation\Application;
|
use Flarum\Foundation\Application;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\AssertPermissionTrait;
|
||||||
use League\Flysystem\Adapter\Local;
|
use League\Flysystem\Adapter\Local;
|
||||||
use League\Flysystem\Filesystem;
|
use League\Flysystem\Filesystem;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\DeletePost;
|
use Flarum\Post\Command\DeletePost;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\DeleteUser;
|
use Flarum\User\Command\DeleteUser;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\RequestPasswordReset;
|
|
||||||
use Flarum\Core\Repository\UserRepository;
|
|
||||||
use Flarum\Http\Controller\ControllerInterface;
|
use Flarum\Http\Controller\ControllerInterface;
|
||||||
|
use Flarum\User\Command\RequestPasswordReset;
|
||||||
|
use Flarum\User\UserRepository;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
@@ -21,7 +21,7 @@ use Zend\Diactoros\Response\EmptyResponse;
|
|||||||
class ForgotPasswordController implements ControllerInterface
|
class ForgotPasswordController implements ControllerInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \Flarum\Core\Repository\UserRepository
|
* @var \Flarum\User\UserRepository
|
||||||
*/
|
*/
|
||||||
protected $users;
|
protected $users;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class ForgotPasswordController implements ControllerInterface
|
|||||||
protected $bus;
|
protected $bus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Flarum\Core\Repository\UserRepository $users
|
* @param \Flarum\User\UserRepository $users
|
||||||
* @param Dispatcher $bus
|
* @param Dispatcher $bus
|
||||||
*/
|
*/
|
||||||
public function __construct(UserRepository $users, Dispatcher $bus)
|
public function __construct(UserRepository $users, Dispatcher $bus)
|
||||||
|
@@ -11,18 +11,19 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Api\UrlGenerator;
|
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||||
use Flarum\Core\Search\Discussion\DiscussionSearcher;
|
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||||
use Flarum\Core\Search\SearchCriteria;
|
use Flarum\Http\UrlGenerator;
|
||||||
|
use Flarum\Search\SearchCriteria;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class ListDiscussionsController extends AbstractCollectionController
|
class ListDiscussionsController extends AbstractListController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\DiscussionSerializer';
|
public $serializer = DiscussionSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
@@ -86,13 +87,25 @@ class ListDiscussionsController extends AbstractCollectionController
|
|||||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||||
|
|
||||||
$document->addPaginationLinks(
|
$document->addPaginationLinks(
|
||||||
$this->url->toRoute('discussions.index'),
|
$this->url->to('api')->route('discussions.index'),
|
||||||
$request->getQueryParams(),
|
$request->getQueryParams(),
|
||||||
$offset,
|
$offset,
|
||||||
$limit,
|
$limit,
|
||||||
$results->areMoreResults() ? null : 0
|
$results->areMoreResults() ? null : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
return $results->getResults();
|
$results = $results->getResults();
|
||||||
|
|
||||||
|
if ($relations = array_intersect($load, ['startPost', 'lastPost'])) {
|
||||||
|
foreach ($results as $discussion) {
|
||||||
|
foreach ($relations as $relation) {
|
||||||
|
if ($discussion->$relation) {
|
||||||
|
$discussion->$relation->discussion = $discussion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,16 +11,17 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Group;
|
use Flarum\Api\Serializer\GroupSerializer;
|
||||||
|
use Flarum\Group\Group;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class ListGroupsController extends AbstractCollectionController
|
class ListGroupsController extends AbstractListController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\GroupSerializer';
|
public $serializer = GroupSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
@@ -11,18 +11,20 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Discussion;
|
use Flarum\Api\Serializer\NotificationSerializer;
|
||||||
use Flarum\Core\Exception\PermissionDeniedException;
|
use Flarum\Discussion\Discussion;
|
||||||
use Flarum\Core\Repository\NotificationRepository;
|
use Flarum\Http\UrlGenerator;
|
||||||
|
use Flarum\Notification\NotificationRepository;
|
||||||
|
use Flarum\User\Exception\PermissionDeniedException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class ListNotificationsController extends AbstractCollectionController
|
class ListNotificationsController extends AbstractListController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\NotificationSerializer';
|
public $serializer = NotificationSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
@@ -39,16 +41,23 @@ class ListNotificationsController extends AbstractCollectionController
|
|||||||
public $limit = 10;
|
public $limit = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Flarum\Core\Repository\NotificationRepository
|
* @var NotificationRepository
|
||||||
*/
|
*/
|
||||||
protected $notifications;
|
protected $notifications;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Flarum\Core\Repository\NotificationRepository $notifications
|
* @var UrlGenerator
|
||||||
*/
|
*/
|
||||||
public function __construct(NotificationRepository $notifications)
|
protected $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param NotificationRepository $notifications
|
||||||
|
* @param UrlGenerator $url
|
||||||
|
*/
|
||||||
|
public function __construct(NotificationRepository $notifications, UrlGenerator $url)
|
||||||
{
|
{
|
||||||
$this->notifications = $notifications;
|
$this->notifications = $notifications;
|
||||||
|
$this->url = $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,10 +77,33 @@ class ListNotificationsController extends AbstractCollectionController
|
|||||||
$offset = $this->extractOffset($request);
|
$offset = $this->extractOffset($request);
|
||||||
$include = $this->extractInclude($request);
|
$include = $this->extractInclude($request);
|
||||||
|
|
||||||
$notifications = $this->notifications->findByUser($actor, $limit, $offset)
|
if (! in_array('subject', $include)) {
|
||||||
|
$include[] = 'subject';
|
||||||
|
}
|
||||||
|
|
||||||
|
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset)
|
||||||
->load(array_diff($include, ['subject.discussion']))
|
->load(array_diff($include, ['subject.discussion']))
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
|
$areMoreResults = false;
|
||||||
|
|
||||||
|
if (count($notifications) > $limit) {
|
||||||
|
array_pop($notifications);
|
||||||
|
$areMoreResults = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$document->addPaginationLinks(
|
||||||
|
$this->url->to('api')->route('notifications.index'),
|
||||||
|
$request->getQueryParams(),
|
||||||
|
$offset,
|
||||||
|
$limit,
|
||||||
|
$areMoreResults ? null : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
$notifications = array_filter($notifications, function ($notification) {
|
||||||
|
return ! $notification->subjectModel || $notification->subject;
|
||||||
|
});
|
||||||
|
|
||||||
if (in_array('subject.discussion', $include)) {
|
if (in_array('subject.discussion', $include)) {
|
||||||
$this->loadSubjectDiscussions($notifications);
|
$this->loadSubjectDiscussions($notifications);
|
||||||
}
|
}
|
||||||
@@ -80,7 +112,7 @@ class ListNotificationsController extends AbstractCollectionController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Flarum\Core\Notification[] $notifications
|
* @param \Flarum\Notification\Notification[] $notifications
|
||||||
*/
|
*/
|
||||||
private function loadSubjectDiscussions(array $notifications)
|
private function loadSubjectDiscussions(array $notifications)
|
||||||
{
|
{
|
||||||
|
@@ -11,19 +11,20 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Repository\PostRepository;
|
use Flarum\Api\Serializer\PostSerializer;
|
||||||
use Flarum\Event\ConfigurePostsQuery;
|
use Flarum\Event\ConfigurePostsQuery;
|
||||||
|
use Flarum\Post\PostRepository;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
use Tobscure\JsonApi\Exception\InvalidParameterException;
|
use Tobscure\JsonApi\Exception\InvalidParameterException;
|
||||||
|
|
||||||
class ListPostsController extends AbstractCollectionController
|
class ListPostsController extends AbstractListController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\PostSerializer';
|
public $serializer = PostSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
@@ -42,12 +43,12 @@ class ListPostsController extends AbstractCollectionController
|
|||||||
public $sortFields = ['time'];
|
public $sortFields = ['time'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Flarum\Core\Repository\PostRepository
|
* @var \Flarum\Post\PostRepository
|
||||||
*/
|
*/
|
||||||
protected $posts;
|
protected $posts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Flarum\Core\Repository\PostRepository $posts
|
* @param \Flarum\Post\PostRepository $posts
|
||||||
*/
|
*/
|
||||||
public function __construct(PostRepository $posts)
|
public function __construct(PostRepository $posts)
|
||||||
{
|
{
|
||||||
@@ -122,7 +123,7 @@ class ListPostsController extends AbstractCollectionController
|
|||||||
$query->orderBy($field, $order);
|
$query->orderBy($field, $order);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->lists('id')->all();
|
return $query->pluck('id')->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,19 +11,20 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Api\UrlGenerator;
|
use Flarum\Api\Serializer\UserSerializer;
|
||||||
use Flarum\Core\Exception\PermissionDeniedException;
|
use Flarum\Http\UrlGenerator;
|
||||||
use Flarum\Core\Search\SearchCriteria;
|
use Flarum\Search\SearchCriteria;
|
||||||
use Flarum\Core\Search\User\UserSearcher;
|
use Flarum\User\Exception\PermissionDeniedException;
|
||||||
|
use Flarum\User\Search\UserSearcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class ListUsersController extends AbstractCollectionController
|
class ListUsersController extends AbstractListController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public $serializer = 'Flarum\Api\Serializer\UserSerializer';
|
public $serializer = UserSerializer::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
@@ -84,7 +85,7 @@ class ListUsersController extends AbstractCollectionController
|
|||||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||||
|
|
||||||
$document->addPaginationLinks(
|
$document->addPaginationLinks(
|
||||||
$this->url->toRoute('users.index'),
|
$this->url->to('api')->route('users.index'),
|
||||||
$request->getQueryParams(),
|
$request->getQueryParams(),
|
||||||
$offset,
|
$offset,
|
||||||
$limit,
|
$limit,
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Command\ReadAllNotifications;
|
use Flarum\Notification\Command\ReadAllNotifications;
|
||||||
use Illuminate\Contracts\Bus\Dispatcher;
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
@@ -11,12 +11,12 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Access\AssertPermissionTrait;
|
|
||||||
use Flarum\Core\EmailToken;
|
|
||||||
use Flarum\Core\Exception\PermissionDeniedException;
|
|
||||||
use Flarum\Forum\UrlGenerator;
|
|
||||||
use Flarum\Http\Controller\ControllerInterface;
|
use Flarum\Http\Controller\ControllerInterface;
|
||||||
|
use Flarum\Http\UrlGenerator;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\AssertPermissionTrait;
|
||||||
|
use Flarum\User\EmailToken;
|
||||||
|
use Flarum\User\Exception\PermissionDeniedException;
|
||||||
use Illuminate\Contracts\Mail\Mailer;
|
use Illuminate\Contracts\Mail\Mailer;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
@@ -80,7 +80,7 @@ class SendConfirmationEmailController implements ControllerInterface
|
|||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'{username}' => $actor->username,
|
'{username}' => $actor->username,
|
||||||
'{url}' => $this->url->toRoute('confirmEmail', ['token' => $token->id]),
|
'{url}' => $this->url->to('forum')->route('confirmEmail', ['token' => $token->id]),
|
||||||
'{forum}' => $this->settings->get('forum_title')
|
'{forum}' => $this->settings->get('forum_title')
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Access\AssertPermissionTrait;
|
use Flarum\Group\Permission;
|
||||||
use Flarum\Core\Permission;
|
|
||||||
use Flarum\Http\Controller\ControllerInterface;
|
use Flarum\Http\Controller\ControllerInterface;
|
||||||
|
use Flarum\User\AssertPermissionTrait;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
|
|
||||||
|
@@ -11,11 +11,11 @@
|
|||||||
|
|
||||||
namespace Flarum\Api\Controller;
|
namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Core\Access\AssertPermissionTrait;
|
|
||||||
use Flarum\Event\PrepareSerializedSetting;
|
|
||||||
use Flarum\Event\SettingWasSet;
|
|
||||||
use Flarum\Http\Controller\ControllerInterface;
|
use Flarum\Http\Controller\ControllerInterface;
|
||||||
|
use Flarum\Settings\Event\Saved;
|
||||||
|
use Flarum\Settings\Event\Serializing;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\AssertPermissionTrait;
|
||||||
use Illuminate\Contracts\Events\Dispatcher;
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Zend\Diactoros\Response\EmptyResponse;
|
use Zend\Diactoros\Response\EmptyResponse;
|
||||||
@@ -53,11 +53,11 @@ class SetSettingsController implements ControllerInterface
|
|||||||
$settings = $request->getParsedBody();
|
$settings = $request->getParsedBody();
|
||||||
|
|
||||||
foreach ($settings as $k => $v) {
|
foreach ($settings as $k => $v) {
|
||||||
$this->dispatcher->fire(new PrepareSerializedSetting($k, $v));
|
$this->dispatcher->fire(new Serializing($k, $v));
|
||||||
|
|
||||||
$this->settings->set($k, $v);
|
$this->settings->set($k, $v);
|
||||||
|
|
||||||
$this->dispatcher->fire(new SettingWasSet($k, $v));
|
$this->dispatcher->fire(new Saved($k, $v));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EmptyResponse(204);
|
return new EmptyResponse(204);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user