mirror of
https://github.com/flarum/core.git
synced 2025-08-14 04:14:06 +02:00
Compare commits
60 Commits
v0.1.0-bet
...
as/support
Author | SHA1 | Date | |
---|---|---|---|
|
e15e903cfe | ||
|
ee0299fa8c | ||
|
fd2e201a21 | ||
|
f05163eda5 | ||
|
b5dc653a19 | ||
|
9e3699ea47 | ||
|
b6f0b01307 | ||
|
548f1321f1 | ||
|
e376cf2079 | ||
|
286027ff27 | ||
|
e52b769ceb | ||
|
b1f166d82a | ||
|
63675c81d6 | ||
|
f76524a5de | ||
|
c006931798 | ||
|
a5ec39b5cf | ||
|
c75db75efe | ||
|
300dadff60 | ||
|
94d69fe15f | ||
|
da598db376 | ||
|
d31e0573f8 | ||
|
2968341f77 | ||
|
9839370701 | ||
|
40dc6d0feb | ||
|
945f6478b5 | ||
|
69a10c97be | ||
|
0074f0c984 | ||
|
19465fb522 | ||
|
0fe7723a7f | ||
|
fbe2813378 | ||
|
4b69a35260 | ||
|
5e8155e1cc | ||
|
0f0f2b6d4e | ||
|
3dae397c65 | ||
|
7025a7f5e0 | ||
|
12f6b1b375 | ||
|
2de57af7c8 | ||
|
1c4817a0b3 | ||
|
0eefbf0374 | ||
|
90c0bc410e | ||
|
d642fb531c | ||
|
706eaeda41 | ||
|
3cc18c1da2 | ||
|
8dd57ffed2 | ||
|
d29495203b | ||
|
783c563305 | ||
|
908d087e00 | ||
|
374189d48e | ||
|
fe8dda6fd0 | ||
|
cd9ee48af6 | ||
|
2e9078a7cf | ||
|
0cc12aed95 | ||
|
59fdd7628a | ||
|
298f6c39f2 | ||
|
233b97329c | ||
|
1b5b143930 | ||
|
0d139e6133 | ||
|
0e6a60bd5b | ||
|
6e4c75eba6 | ||
|
386f3d3db1 |
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -7,10 +7,24 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: JS / Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: flarum/action-build@master
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Restore npm cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('js/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
# Our action will install npm, cd into `./js`, run `npm run build`,
|
||||
# then commit and upload any changes
|
||||
- name: Build production JS
|
||||
uses: flarum/action-build@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
76
.github/workflows/codeql-analysis.yml
vendored
Normal file
76
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
# Run on:
|
||||
# - pushes to master, or
|
||||
# - PRs with a base of `master`
|
||||
# - which do not **only** consist of changes to .md or .less files
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.less'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.less'
|
||||
schedule:
|
||||
- cron: '0 0 * * 1,3,5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze / ${{ matrix.language }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
21
.github/workflows/lint.yml
vendored
21
.github/workflows/lint.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'js/src/**'
|
||||
@@ -10,22 +11,18 @@ on:
|
||||
|
||||
jobs:
|
||||
prettier:
|
||||
name: JS / Prettier
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: JS / Prettier
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "12"
|
||||
node-version: "14"
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: npm ci
|
||||
working-directory: ./js
|
||||
|
||||
- name: Check JS code for formatting
|
||||
run: node_modules/.bin/prettier --check src
|
||||
- name: Check JS formatting
|
||||
run: npx prettier --check src
|
||||
working-directory: ./js
|
||||
|
45
.github/workflows/pr_size_change.yml
vendored
Normal file
45
.github/workflows/pr_size_change.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Bundle size checker
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
paths:
|
||||
- "js/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "js/**"
|
||||
|
||||
jobs:
|
||||
bundlewatch:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: Bundlewatch
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
- name: Use npm v7
|
||||
run: sudo npm install -g npm@7.x.x
|
||||
|
||||
- name: Install JS dependencies
|
||||
# We need to use `npm install` here. If we don't, the workflow will fail.
|
||||
run: npm install
|
||||
working-directory: ./js
|
||||
|
||||
- name: Build production assets
|
||||
run: npm run build
|
||||
working-directory: ./js
|
||||
|
||||
- name: Check bundle size change
|
||||
run: node_modules/.bin/bundlewatch --config .bundlewatch.config.json
|
||||
working-directory: ./js
|
||||
env:
|
||||
BUNDLEWATCH_GITHUB_TOKEN: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}
|
||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@@ -43,10 +43,11 @@ jobs:
|
||||
name: 'PHP ${{ matrix.php }} / ${{ matrix.db }} ${{ matrix.prefixStr }}'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@0b9d33cd0782337377999751fc10ea079fdd7104 # pin@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
coverage: xdebug
|
||||
|
@@ -12,7 +12,3 @@ disabled:
|
||||
- phpdoc_order
|
||||
- phpdoc_separation
|
||||
- phpdoc_types
|
||||
|
||||
finder:
|
||||
exclude:
|
||||
- "stubs"
|
||||
|
@@ -25,10 +25,12 @@
|
||||
"components/font-awesome": "^5.14.0",
|
||||
"dflydev/fig-cookies": "^3.0.0",
|
||||
"doctrine/dbal": "^2.7",
|
||||
"dragonmantank/cron-expression": "^3.1.0",
|
||||
"franzl/whoops-middleware": "^2.0.0",
|
||||
"illuminate/bus": "^8.0",
|
||||
"illuminate/cache": "^8.0",
|
||||
"illuminate/config": "^8.0",
|
||||
"illuminate/console": "^8.0",
|
||||
"illuminate/container": "^8.0",
|
||||
"illuminate/contracts": "^8.0",
|
||||
"illuminate/database": "^8.0",
|
||||
@@ -74,8 +76,7 @@
|
||||
"Flarum\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/helpers.php",
|
||||
"src/TranslatorInterface.php"
|
||||
"src/helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
8
js/.bundlewatch.config.json
Normal file
8
js/.bundlewatch.config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"path": "./dist/*.js"
|
||||
}
|
||||
],
|
||||
"defaultCompression": "gzip"
|
||||
}
|
16
js/dist/admin.js
vendored
16
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
54
js/dist/forum.js
vendored
54
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
11239
js/package-lock.json
generated
11239
js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,32 +2,40 @@
|
||||
"private": true,
|
||||
"name": "@flarum/core",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@types/mithril": "^2.0.3",
|
||||
"bootstrap": "^3.4.1",
|
||||
"classnames": "^2.2.5",
|
||||
"clsx": "^1.1.1",
|
||||
"color-thief-browser": "^2.0.2",
|
||||
"dayjs": "^1.8.28",
|
||||
"expose-loader": "^0.7.5",
|
||||
"flarum-webpack-config": "0.1.0-beta.10",
|
||||
"jquery": "^3.5.1",
|
||||
"dayjs": "^1.10.4",
|
||||
"expose-loader": "^2.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"jquery.hotkeys": "^0.1.0",
|
||||
"lodash-es": "^4.17.14",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mithril": "^2.0.4",
|
||||
"punycode": "^2.1.1",
|
||||
"spin.js": "^3.1.0",
|
||||
"textarea-caret": "^3.1.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-merge": "^4.1.4"
|
||||
"textarea-caret": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"husky": "^4.2.5",
|
||||
"prettier": "2.0.2"
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@types/jquery": "^3.5.5",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mithril": "^2.0.7",
|
||||
"@types/punycode": "^2.1.0",
|
||||
"@types/textarea-caret": "^3.0.0",
|
||||
"bundlewatch": "^0.3.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"flarum-webpack-config": "0.1.0-beta.10",
|
||||
"husky": "^4.3.8",
|
||||
"prettier": "^2.2.1",
|
||||
"webpack": "^5.0.0",
|
||||
"webpack-bundle-analyzer": "^4.4.1",
|
||||
"webpack-cli": "^4.0.0",
|
||||
"webpack-merge": "^4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "webpack --mode development --watch",
|
||||
"build": "webpack --mode production",
|
||||
"analyze": "cross-env ANALYZER=true npm run build",
|
||||
"format": "prettier --write src",
|
||||
"format-check": "prettier --check src"
|
||||
},
|
||||
|
14
js/shims.d.ts
vendored
14
js/shims.d.ts
vendored
@@ -19,9 +19,21 @@ import Application from './src/common/Application';
|
||||
* to (and should not) bundle these themselves.
|
||||
*/
|
||||
declare global {
|
||||
const $: typeof _$;
|
||||
// $ is already defined by `@types/jquery`
|
||||
const m: Mithril.Static;
|
||||
const dayjs: typeof _dayjs;
|
||||
|
||||
// Extend JQuery with our custom functions, defined with $.fn
|
||||
interface JQuery {
|
||||
/**
|
||||
* Creates a tooltip on a jQuery element reference.
|
||||
*
|
||||
* Optionally accepts placement and delay options.
|
||||
*
|
||||
* Returns the same reference to allow for method chaining.
|
||||
*/
|
||||
tooltip: (tooltipOptions?: { placement?: 'top' | 'bottom' | 'left' | 'right'; delay?: number }) => JQuery;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
8
js/src/admin/app.ts
Normal file
8
js/src/admin/app.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Admin from './AdminApplication';
|
||||
|
||||
const app = new Admin();
|
||||
|
||||
// @ts-ignore
|
||||
window.app = app;
|
||||
|
||||
export default app;
|
@@ -126,16 +126,17 @@ export default class AdminNav extends Component {
|
||||
|
||||
categorizedExtensions[category].map((extension) => {
|
||||
const query = this.query().toUpperCase();
|
||||
const title = extension.extra['flarum-extension'].title;
|
||||
const title = extension.extra['flarum-extension'].title || '';
|
||||
const description = extension.description || '';
|
||||
|
||||
if (!query || title.toUpperCase().includes(query) || extension.description.toUpperCase().includes(query)) {
|
||||
if (!query || title.toUpperCase().includes(query) || description.toUpperCase().includes(query)) {
|
||||
items.add(
|
||||
`extension-${extension.id}`,
|
||||
<ExtensionLinkButton
|
||||
href={app.route('extension', { id: extension.id })}
|
||||
extensionId={extension.id}
|
||||
className="ExtensionNavButton"
|
||||
title={extension.description}
|
||||
title={description}
|
||||
>
|
||||
{title}
|
||||
</ExtensionLinkButton>,
|
||||
|
@@ -98,35 +98,41 @@ export default class AdminPage extends Page {
|
||||
return entry.call(this);
|
||||
}
|
||||
|
||||
const setting = entry.setting;
|
||||
const help = entry.help;
|
||||
delete entry.help;
|
||||
const { setting, help, ...componentAttrs } = entry;
|
||||
|
||||
delete componentAttrs.help;
|
||||
|
||||
const value = this.setting([setting])();
|
||||
if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {
|
||||
if (['bool', 'checkbox', 'switch', 'boolean'].includes(componentAttrs.type)) {
|
||||
return (
|
||||
<div className="Form-group">
|
||||
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...entry}>
|
||||
{entry.label}
|
||||
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...componentAttrs}>
|
||||
{componentAttrs.label}
|
||||
</Switch>
|
||||
<div className="helpText">{help}</div>
|
||||
</div>
|
||||
);
|
||||
} else if (['select', 'dropdown', 'selectdropdown'].includes(entry.type)) {
|
||||
} else if (['select', 'dropdown', 'selectdropdown'].includes(componentAttrs.type)) {
|
||||
return (
|
||||
<div className="Form-group">
|
||||
<label>{entry.label}</label>
|
||||
<label>{componentAttrs.label}</label>
|
||||
<div className="helpText">{help}</div>
|
||||
<Select value={value || entry.default} options={entry.options} buttonClassName="Button" onchange={this.settings[setting]} {...entry} />
|
||||
<Select
|
||||
value={value || componentAttrs.default}
|
||||
options={componentAttrs.options}
|
||||
buttonClassName="Button"
|
||||
onchange={this.settings[setting]}
|
||||
{...componentAttrs}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
entry.className = classList(['FormControl', entry.className]);
|
||||
componentAttrs.className = classList(['FormControl', componentAttrs.className]);
|
||||
return (
|
||||
<div className="Form-group">
|
||||
{entry.label ? <label>{entry.label}</label> : ''}
|
||||
{componentAttrs.label ? <label>{componentAttrs.label}</label> : ''}
|
||||
<div className="helpText">{help}</div>
|
||||
<input type={entry.type} bidi={this.setting(setting)} {...entry} />
|
||||
<input type={componentAttrs.type} bidi={this.setting(setting)} {...componentAttrs} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,9 +1,4 @@
|
||||
import AdminApplication from './AdminApplication';
|
||||
|
||||
const app = new AdminApplication();
|
||||
|
||||
// Backwards compatibility
|
||||
window.app = app;
|
||||
import app from './app';
|
||||
|
||||
export { app };
|
||||
|
||||
|
@@ -159,6 +159,8 @@ export default class Application {
|
||||
title = '';
|
||||
titleCount = 0;
|
||||
|
||||
initialRoute;
|
||||
|
||||
load(payload) {
|
||||
this.data = payload;
|
||||
this.translator.locale = payload.locale;
|
||||
@@ -174,6 +176,8 @@ export default class Application {
|
||||
this.session = new Session(this.store.getById('users', this.data.session.userId), this.data.session.csrfToken);
|
||||
|
||||
this.mount();
|
||||
|
||||
this.initialRoute = window.location.href;
|
||||
}
|
||||
|
||||
bootExtensions(extensions) {
|
||||
@@ -226,7 +230,8 @@ export default class Application {
|
||||
* @public
|
||||
*/
|
||||
preloadedApiDocument() {
|
||||
if (this.data.apiDocument) {
|
||||
// If the URL has changed, the preloaded Api document is invalid.
|
||||
if (this.data.apiDocument && window.location.href === this.initialRoute) {
|
||||
const results = this.store.pushPayload(this.data.apiDocument);
|
||||
|
||||
this.data.apiDocument = null;
|
||||
|
@@ -77,12 +77,12 @@ export default abstract class Component<T extends ComponentAttrs = ComponentAttr
|
||||
* containing all of the `li` elements inside the DOM element of this
|
||||
* component.
|
||||
*
|
||||
* @param {String} [selector] a jQuery-compatible selector string
|
||||
* @returns {jQuery} the jQuery object for the DOM node
|
||||
* @param [selector] a jQuery-compatible selector string
|
||||
* @returns the jQuery object for the DOM node
|
||||
* @final
|
||||
*/
|
||||
protected $(selector) {
|
||||
const $element = $(this.element);
|
||||
protected $(selector: string): JQuery {
|
||||
const $element = $(this.element) as JQuery<HTMLElement>;
|
||||
|
||||
return selector ? $element.find(selector) : $element;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ export default abstract class Component<T extends ComponentAttrs = ComponentAttr
|
||||
* @see https://mithril.js.org/hyperscript.html#mselector,-attributes,-children
|
||||
*/
|
||||
static component(attrs = {}, children = null): Mithril.Vnode {
|
||||
const componentAttrs = Object.assign({}, attrs);
|
||||
const componentAttrs = Object.assign({}, attrs) as Record<string, unknown>;
|
||||
|
||||
return m(this as any, componentAttrs, children);
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import listItems from '../helpers/listItems';
|
||||
* - `icon` The name of an icon to show in the dropdown toggle button.
|
||||
* - `caretIcon` The name of an icon to show on the right of the button.
|
||||
* - `label` The label of the dropdown toggle button. Defaults to 'Controls'.
|
||||
* - `accessibleToggleLabel` The label used to describe the dropdown toggle button to assistive readers. Defaults to 'Toggle dropdown menu'.
|
||||
* - `onhide`
|
||||
* - `onshow`
|
||||
*
|
||||
@@ -25,6 +26,7 @@ export default class Dropdown extends Component {
|
||||
attrs.menuClassName = attrs.menuClassName || '';
|
||||
attrs.label = attrs.label || '';
|
||||
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-caret-down';
|
||||
attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.lib.dropdown.toggle_dropdown_accessible_label');
|
||||
}
|
||||
|
||||
oninit(vnode) {
|
||||
@@ -92,7 +94,13 @@ export default class Dropdown extends Component {
|
||||
*/
|
||||
getButton(children) {
|
||||
return (
|
||||
<button className={'Dropdown-toggle ' + this.attrs.buttonClassName} data-toggle="dropdown" onclick={this.attrs.onclick}>
|
||||
<button
|
||||
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
|
||||
aria-haspopup="menu"
|
||||
aria-label={this.attrs.accessibleToggleLabel}
|
||||
data-toggle="dropdown"
|
||||
onclick={this.attrs.onclick}
|
||||
>
|
||||
{this.getButtonContent(children)}
|
||||
</button>
|
||||
);
|
||||
|
@@ -1,43 +0,0 @@
|
||||
import Component from '../Component';
|
||||
import { Spinner } from 'spin.js';
|
||||
|
||||
/**
|
||||
* The `LoadingIndicator` component displays a loading spinner with spin.js.
|
||||
*
|
||||
* ### Attrs
|
||||
*
|
||||
* - `size` The spin.js size preset to use. Defaults to 'small'.
|
||||
*
|
||||
* All other attrs will be assigned as attributes on the DOM element.
|
||||
*/
|
||||
export default class LoadingIndicator extends Component {
|
||||
view() {
|
||||
const attrs = Object.assign({}, this.attrs);
|
||||
|
||||
attrs.className = 'LoadingIndicator ' + (attrs.className || '');
|
||||
delete attrs.size;
|
||||
|
||||
return <div {...attrs}>{m.trust(' ')}</div>;
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
const options = { zIndex: 'auto', color: this.$().css('color') };
|
||||
|
||||
switch (this.attrs.size) {
|
||||
case 'large':
|
||||
Object.assign(options, { lines: 10, length: 8, width: 4, radius: 8 });
|
||||
break;
|
||||
|
||||
case 'tiny':
|
||||
Object.assign(options, { lines: 8, length: 2, width: 2, radius: 3 });
|
||||
break;
|
||||
|
||||
default:
|
||||
Object.assign(options, { lines: 8, length: 4, width: 3, radius: 5 });
|
||||
}
|
||||
|
||||
new Spinner(options).spin(this.element);
|
||||
}
|
||||
}
|
61
js/src/common/components/LoadingIndicator.tsx
Normal file
61
js/src/common/components/LoadingIndicator.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import Component, { ComponentAttrs } from '../Component';
|
||||
import classList from '../utils/classList';
|
||||
|
||||
export interface LoadingIndicatorAttrs extends ComponentAttrs {
|
||||
/**
|
||||
* Custom classes fro the loading indicator's container.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Custom classes for the loading indicator's container.
|
||||
*/
|
||||
containerClassName?: string;
|
||||
/**
|
||||
* Optional size to specify for the loading indicator.
|
||||
*/
|
||||
size?: 'large' | 'medium' | 'small';
|
||||
/**
|
||||
* Optional attributes to apply to the loading indicator's container.
|
||||
*/
|
||||
containerAttrs?: Partial<ComponentAttrs>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `LoadingIndicator` component displays a simple CSS-based loading spinner.
|
||||
*
|
||||
* To set a custom color, use the CSS `color` property.
|
||||
*
|
||||
* To increase spacing around the spinner, use the CSS `height` property on the
|
||||
* spinner's **container**.
|
||||
*
|
||||
* To apply a custom size to the loading indicator, set the `--size` and
|
||||
* `--thickness` custom properties on the loading indicator itself.
|
||||
*
|
||||
* If you really want to change how this looks as part of your custom theme,
|
||||
* you can override the `border-radius` and `border` then set either a
|
||||
* background image, or use `content: "\<glyph>"` (e.g. `content: "\f1ce"`)
|
||||
* and `font-family: 'Font Awesome 5 Free'` to set an FA icon if you'd rather.
|
||||
*
|
||||
* ### Attrs
|
||||
*
|
||||
* - `containerClassName` Class name(s) to apply to the indicator's parent
|
||||
* - `className` Class name(s) to apply to the indicator itself
|
||||
* - `size` Size of the loading indicator
|
||||
* - `containerAttrs` Optional attrs to be applied to the container DOM element
|
||||
*
|
||||
* All other attrs will be assigned as attributes on the DOM element.
|
||||
*/
|
||||
export default class LoadingIndicator extends Component<LoadingIndicatorAttrs> {
|
||||
view() {
|
||||
const { size, ...attrs } = this.attrs;
|
||||
|
||||
attrs.className = classList({ LoadingIndicator: true, [attrs.className || '']: true });
|
||||
attrs.containerClassName = classList({ 'LoadingIndicator-container': true, [attrs.containerClassName || '']: true });
|
||||
|
||||
return (
|
||||
<div {...attrs.containerAttrs} data-size={size} className={attrs.containerClassName}>
|
||||
<div {...attrs}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@@ -24,7 +24,12 @@ export default class SplitDropdown extends Dropdown {
|
||||
|
||||
return [
|
||||
Button.component(buttonAttrs, firstChild.children),
|
||||
<button className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName} data-toggle="dropdown">
|
||||
<button
|
||||
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
|
||||
aria-haspopup="menu"
|
||||
aria-label={this.attrs.accessibleToggleLabel}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
{icon(this.attrs.icon, { className: 'Button-icon' })}
|
||||
{icon('fas fa-caret-down', { className: 'Button-caret' })}
|
||||
</button>,
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import 'expose-loader?$!expose-loader?jQuery!jquery';
|
||||
import 'expose-loader?m!mithril';
|
||||
import 'expose-loader?dayjs!dayjs';
|
||||
// Expose jQuery, mithril and dayjs to the window browser object
|
||||
import 'expose-loader?exposes=$,jQuery!jquery';
|
||||
import 'expose-loader?exposes=m!mithril';
|
||||
import 'expose-loader?exposes=dayjs!dayjs';
|
||||
|
||||
import 'bootstrap/js/affix';
|
||||
import 'bootstrap/js/dropdown';
|
||||
import 'bootstrap/js/modal';
|
||||
|
@@ -31,7 +31,24 @@ export default class Drawer {
|
||||
* @public
|
||||
*/
|
||||
hide() {
|
||||
$('#app').removeClass('drawerOpen');
|
||||
/**
|
||||
* As part of hiding the drawer, this function also ensures that the drawer
|
||||
* correctly animates out, while ensuring it is not part of the navigation
|
||||
* tree while off-screen.
|
||||
*
|
||||
* More info: https://github.com/flarum/core/pull/2666#discussion_r595381014
|
||||
*/
|
||||
|
||||
const $app = $('#app');
|
||||
|
||||
if (!$app.hasClass('drawerOpen')) return;
|
||||
|
||||
const $drawer = $('#drawer');
|
||||
|
||||
// Used to prevent `visibility: hidden` from breaking the exit animation
|
||||
$drawer.css('visibility', 'visible').one('transitionend', () => $drawer.css('visibility', ''));
|
||||
|
||||
$app.removeClass('drawerOpen');
|
||||
|
||||
if (this.$backdrop) this.$backdrop.remove();
|
||||
}
|
||||
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* The `classList` utility creates a list of class names by joining an object's
|
||||
* keys, but only for values which are truthy.
|
||||
*
|
||||
* @example
|
||||
* classList({ foo: true, bar: false, qux: 'qaz' });
|
||||
* // "foo qux"
|
||||
*
|
||||
* @param {Object} classes
|
||||
* @return {String}
|
||||
*/
|
||||
export default function classList(classes) {
|
||||
let classNames;
|
||||
|
||||
if (classes instanceof Array) {
|
||||
classNames = classes.filter((name) => name);
|
||||
} else {
|
||||
classNames = [];
|
||||
|
||||
for (const i in classes) {
|
||||
if (classes[i]) classNames.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return classNames.join(' ');
|
||||
}
|
12
js/src/common/utils/classList.ts
Normal file
12
js/src/common/utils/classList.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* This util exposes `clsx` to core and extensions as a re-usable utility.
|
||||
*
|
||||
* For full documentation, see `clsx` on GitHub.
|
||||
*
|
||||
* @see https://github.com/lukeed/clsx
|
||||
*/
|
||||
const classList = clsx;
|
||||
|
||||
export default classList;
|
8
js/src/forum/app.ts
Normal file
8
js/src/forum/app.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Forum from './ForumApplication';
|
||||
|
||||
const app = new Forum();
|
||||
|
||||
// @ts-ignore
|
||||
window.app = app;
|
||||
|
||||
export default app;
|
@@ -36,8 +36,6 @@ import HeaderSecondary from './components/HeaderSecondary';
|
||||
import ComposerButton from './components/ComposerButton';
|
||||
import DiscussionList from './components/DiscussionList';
|
||||
import ReplyPlaceholder from './components/ReplyPlaceholder';
|
||||
import TextEditor from '../common/components/TextEditor'; // @deprecated beta 16, remove beta 17. Moved to common.
|
||||
import TextEditorButton from '../common/components/TextEditorButton'; // @deprecated beta 16, remove beta 17. Moved to common.
|
||||
import AvatarEditor from './components/AvatarEditor';
|
||||
import Post from './components/Post';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
@@ -87,7 +85,6 @@ export default Object.assign(compat, {
|
||||
'utils/UserControls': UserControls,
|
||||
'utils/Pane': Pane,
|
||||
'utils/BasicEditorDriver': BasicEditorDriver,
|
||||
'utils/SuperTextarea': BasicEditorDriver, // @deprecated beta 16, remove beta 17
|
||||
'states/ComposerState': ComposerState,
|
||||
'states/DiscussionListState': DiscussionListState,
|
||||
'states/GlobalSearchState': GlobalSearchState,
|
||||
@@ -116,8 +113,6 @@ export default Object.assign(compat, {
|
||||
'components/ComposerButton': ComposerButton,
|
||||
'components/DiscussionList': DiscussionList,
|
||||
'components/ReplyPlaceholder': ReplyPlaceholder,
|
||||
'components/TextEditor': TextEditor, // @deprecated beta 16, remove beta 17. Moved to common.
|
||||
'components/TextEditorButton': TextEditorButton, // @deprecated beta 16, remove beta 17. Moved to common.
|
||||
'components/AvatarEditor': AvatarEditor,
|
||||
'components/Post': Post,
|
||||
'components/SettingsPage': SettingsPage,
|
||||
|
@@ -87,6 +87,7 @@ export default class DiscussionListItem extends Component {
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
className: 'DiscussionListItem-controls',
|
||||
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
controls
|
||||
)
|
||||
|
@@ -189,6 +189,7 @@ export default class DiscussionPage extends Page {
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
className: 'App-primaryControl',
|
||||
buttonClassName: 'Button--primary',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.discussion_controls.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
DiscussionControls.controls(this.discussion, this).toArray()
|
||||
)
|
||||
|
@@ -57,6 +57,7 @@ export default class HeaderSecondary extends Component {
|
||||
SelectDropdown.component(
|
||||
{
|
||||
buttonClassName: 'Button Button--link',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.header.locale_dropdown_accessible_label'),
|
||||
},
|
||||
locales
|
||||
),
|
||||
|
@@ -172,6 +172,7 @@ export default class IndexPage extends Page {
|
||||
{
|
||||
buttonClassName: 'Button',
|
||||
className: 'App-titleControl',
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.index.toggle_sidenav_dropdown_accessible_label'),
|
||||
},
|
||||
this.navItems(this).toArray()
|
||||
)
|
||||
@@ -227,6 +228,7 @@ export default class IndexPage extends Page {
|
||||
{
|
||||
buttonClassName: 'Button',
|
||||
label: sortOptions[app.search.params().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0],
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.index_sort.toggle_dropdown_accessible_label'),
|
||||
},
|
||||
Object.keys(sortOptions).map((value) => {
|
||||
const label = sortOptions[value];
|
||||
|
@@ -9,6 +9,8 @@ export default class NotificationsDropdown extends Dropdown {
|
||||
attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right';
|
||||
attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip');
|
||||
attrs.icon = attrs.icon || 'fas fa-bell';
|
||||
// For best a11y support, both `title` and `aria-label` should be used
|
||||
attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.forum.notifications.toggle_dropdown_accessible_label');
|
||||
|
||||
super.initAttrs(attrs);
|
||||
}
|
||||
|
@@ -61,6 +61,7 @@ export default class Post extends Component {
|
||||
icon="fas fa-ellipsis-h"
|
||||
onshow={() => this.$('.Post-actions').addClass('open')}
|
||||
onhide={() => this.$('.Post-actions').removeClass('open')}
|
||||
accessibleToggleLabel={app.translator.trans('core.forum.post_controls.toggle_dropdown_accessible_label')}
|
||||
>
|
||||
{controls}
|
||||
</Dropdown>
|
||||
|
@@ -112,7 +112,6 @@ export default class PostStreamScrubber extends Component {
|
||||
|
||||
// Now we want to make the scrollbar handle draggable. Let's start by
|
||||
// preventing default browser events from messing things up.
|
||||
.css({ cursor: 'pointer', 'user-select': 'none' })
|
||||
.bind('dragstart mousedown touchstart', (e) => e.preventDefault());
|
||||
|
||||
// When the mouse is pressed on the scrollbar handle, we capture some
|
||||
@@ -124,7 +123,6 @@ export default class PostStreamScrubber extends Component {
|
||||
this.indexStart = 0;
|
||||
|
||||
this.$('.Scrubber-handle')
|
||||
.css('cursor', 'move')
|
||||
.bind('mousedown touchstart', this.onmousedown.bind(this))
|
||||
|
||||
// Exempt the scrollbar handle from the 'jump to' click event.
|
||||
|
@@ -49,7 +49,7 @@ export default class RenameDiscussionModal extends Modal {
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const title = this.newTitle;
|
||||
const title = this.newTitle();
|
||||
const currentTitle = this.currentTitle;
|
||||
|
||||
// If the title is different to what it was before, then save it. After the
|
||||
|
@@ -71,8 +71,11 @@ export default class Search extends Component {
|
||||
// Hide the search view if no sources were loaded
|
||||
if (!this.sources.length) return <div></div>;
|
||||
|
||||
const searchLabel = extractText(app.translator.trans('core.forum.header.search_placeholder'));
|
||||
|
||||
return (
|
||||
<div
|
||||
role="search"
|
||||
className={
|
||||
'Search ' +
|
||||
classList({
|
||||
@@ -85,9 +88,10 @@ export default class Search extends Component {
|
||||
>
|
||||
<div className="Search-input">
|
||||
<input
|
||||
aria-label={searchLabel}
|
||||
className="FormControl"
|
||||
type="search"
|
||||
placeholder={extractText(app.translator.trans('core.forum.header.search_placeholder'))}
|
||||
placeholder={searchLabel}
|
||||
value={this.state.getValue()}
|
||||
oninput={(e) => this.state.setValue(e.target.value)}
|
||||
onfocus={() => (this.hasFocus = true)}
|
||||
|
@@ -17,6 +17,8 @@ export default class SessionDropdown extends Dropdown {
|
||||
attrs.className = 'SessionDropdown';
|
||||
attrs.buttonClassName = 'Button Button--user Button--flat';
|
||||
attrs.menuClassName = 'Dropdown-menu--right';
|
||||
|
||||
attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label');
|
||||
}
|
||||
|
||||
view(vnode) {
|
||||
|
@@ -40,6 +40,7 @@ export default class UserCard extends Component {
|
||||
menuClassName: 'Dropdown-menu--right',
|
||||
buttonClassName: this.attrs.controlsButtonClassName,
|
||||
label: app.translator.trans('core.forum.user_controls.button'),
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.user_controls.toggle_dropdown_accessible_label'),
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
},
|
||||
controls
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import 'expose-loader?punycode!punycode';
|
||||
import 'expose-loader?ColorThief!color-thief-browser';
|
||||
// Expose punycode and ColorThief to the window browser object
|
||||
import 'expose-loader?exposes=punycode!punycode';
|
||||
import 'expose-loader?exposes=ColorThief!color-thief-browser';
|
||||
|
||||
import ForumApplication from './ForumApplication';
|
||||
|
||||
const app = new ForumApplication();
|
||||
|
||||
// Backwards compatibility
|
||||
window.app = app;
|
||||
import app from './app';
|
||||
|
||||
export { app };
|
||||
|
||||
|
@@ -1,15 +1,32 @@
|
||||
const config = require('flarum-webpack-config');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const merge = require('webpack-merge');
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
const useBundleAnalyzer = process.env.ANALYZER === 'true';
|
||||
|
||||
const plugins = [];
|
||||
|
||||
if (useBundleAnalyzer) {
|
||||
plugins.push(new BundleAnalyzerPlugin());
|
||||
}
|
||||
|
||||
module.exports = merge(config(), {
|
||||
output: {
|
||||
library: 'flarum.core'
|
||||
library: 'flarum.core',
|
||||
},
|
||||
|
||||
// temporary TS configuration
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||
},
|
||||
|
||||
plugins,
|
||||
optimization: {
|
||||
minimizer: [new TerserPlugin({
|
||||
extractComments: false,
|
||||
})],
|
||||
},
|
||||
});
|
||||
|
||||
module.exports['module'].rules[0].test = /\.(tsx?|js)$/;
|
||||
|
@@ -123,6 +123,9 @@
|
||||
// the left side of the screen. On other devices, the drawer has no specific
|
||||
// appearance.
|
||||
@media @phone {
|
||||
.App:not(.drawerOpen) .App-drawer {
|
||||
visibility: hidden;
|
||||
}
|
||||
.drawerOpen {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@@ -15,13 +15,9 @@
|
||||
float: left;
|
||||
margin-left: -65px;
|
||||
margin-top: -4px;
|
||||
|
||||
.LoadingIndicator {
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Checkbox--switch .Checkbox-display {
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
@@ -31,8 +27,28 @@
|
||||
background: @control-bg;
|
||||
.transition(background-color 0.2s);
|
||||
|
||||
.LoadingIndicator {
|
||||
--size: 22px !important;
|
||||
|
||||
&-container {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.on& {
|
||||
background: #58a400;
|
||||
|
||||
.LoadingIndicator-container {
|
||||
// Show loading indicator over the switch button
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.off& {
|
||||
.LoadingIndicator-container {
|
||||
// Show loading indicator over the switch button
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
|
@@ -2,13 +2,58 @@
|
||||
// Loading Indicators
|
||||
|
||||
.LoadingIndicator {
|
||||
position: relative;
|
||||
color: @muted-color;
|
||||
@spin-time: 750ms;
|
||||
--size: 24px;
|
||||
--thickness: 2px;
|
||||
|
||||
&-container[data-size="large"] & {
|
||||
--size: 32px;
|
||||
--thickness: 3px;
|
||||
}
|
||||
|
||||
&-container[data-size="tiny"] & {
|
||||
--size: 18px;
|
||||
}
|
||||
|
||||
// Use the value of `color` to maintain backwards compatibility
|
||||
border-color: currentColor;
|
||||
border-width: var(--thickness);
|
||||
border-style: solid;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
animation: spin @spin-time linear infinite;
|
||||
|
||||
// <div> container around the spinner
|
||||
// Used for positioning
|
||||
&-container {
|
||||
color: @muted-color;
|
||||
|
||||
// Center vertically and horizontally
|
||||
// Allows people to set `height` and it'll stay centered within the new height
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--block {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&--inline {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.LoadingIndicator--inline {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
}
|
||||
.LoadingIndicator--block {
|
||||
height: 100px;
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.header-background() {
|
||||
background: fade(@header-bg, 98%);
|
||||
background: @header-bg;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@@ -1,227 +1,151 @@
|
||||
// Vendor Prefixes
|
||||
//
|
||||
// All vendor mixins are deprecated as of v3.2.0 due to the introduction of
|
||||
// Autoprefixer in our Gruntfile. They will be removed in v4.
|
||||
|
||||
// - Animations
|
||||
// - Backface visibility
|
||||
// - Box shadow
|
||||
// - Box sizing
|
||||
// - Content columns
|
||||
// - Hyphens
|
||||
// - Placeholder text
|
||||
// - Transformations
|
||||
// - Transitions
|
||||
// - User Select
|
||||
|
||||
// These aim to ensure that Flarum remains compatible with most modern devices.
|
||||
// The vendor presets below are to try to remain compatible with iOS 9+ and other
|
||||
// major browsers (Chrome/Firefox/new Edge/Safari desktop).
|
||||
|
||||
// Animations
|
||||
// These remain for backwards compatibility with existing styles.
|
||||
.animation(@animation) {
|
||||
-webkit-animation: @animation;
|
||||
-o-animation: @animation;
|
||||
animation: @animation;
|
||||
animation: @animation;
|
||||
}
|
||||
.animation-name(@name) {
|
||||
-webkit-animation-name: @name;
|
||||
animation-name: @name;
|
||||
animation-name: @name;
|
||||
}
|
||||
.animation-duration(@duration) {
|
||||
-webkit-animation-duration: @duration;
|
||||
animation-duration: @duration;
|
||||
animation-duration: @duration;
|
||||
}
|
||||
.animation-timing-function(@timing-function) {
|
||||
-webkit-animation-timing-function: @timing-function;
|
||||
animation-timing-function: @timing-function;
|
||||
animation-timing-function: @timing-function;
|
||||
}
|
||||
.animation-delay(@delay) {
|
||||
-webkit-animation-delay: @delay;
|
||||
animation-delay: @delay;
|
||||
animation-delay: @delay;
|
||||
}
|
||||
.animation-iteration-count(@iteration-count) {
|
||||
-webkit-animation-iteration-count: @iteration-count;
|
||||
animation-iteration-count: @iteration-count;
|
||||
animation-iteration-count: @iteration-count;
|
||||
}
|
||||
.animation-direction(@direction) {
|
||||
-webkit-animation-direction: @direction;
|
||||
animation-direction: @direction;
|
||||
animation-direction: @direction;
|
||||
}
|
||||
.animation-fill-mode(@fill-mode) {
|
||||
-webkit-animation-fill-mode: @fill-mode;
|
||||
animation-fill-mode: @fill-mode;
|
||||
animation-fill-mode: @fill-mode;
|
||||
}
|
||||
|
||||
// Backface visibility
|
||||
// Prevent browsers from flickering when using CSS 3D transforms.
|
||||
// Default value is `visible`, but can be changed to `hidden`
|
||||
|
||||
.backface-visibility(@visibility){
|
||||
.backface-visibility(@visibility) {
|
||||
// Safari
|
||||
-webkit-backface-visibility: @visibility;
|
||||
-moz-backface-visibility: @visibility;
|
||||
backface-visibility: @visibility;
|
||||
backface-visibility: @visibility;
|
||||
}
|
||||
|
||||
// Drop shadows
|
||||
//
|
||||
// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
|
||||
// supported browsers that have box shadow capabilities now support it.
|
||||
|
||||
// These remain for backwards compatibility with existing styles.
|
||||
.box-shadow(@shadow) {
|
||||
-webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
|
||||
box-shadow: @shadow;
|
||||
box-shadow: @shadow;
|
||||
}
|
||||
|
||||
// Box sizing
|
||||
// These remain for backwards compatibility with existing styles.
|
||||
.box-sizing(@boxmodel) {
|
||||
-webkit-box-sizing: @boxmodel;
|
||||
-moz-box-sizing: @boxmodel;
|
||||
box-sizing: @boxmodel;
|
||||
box-sizing: @boxmodel;
|
||||
}
|
||||
|
||||
// CSS3 Content Columns
|
||||
.content-columns(@column-count; @column-gap: @grid-gutter-width) {
|
||||
// Safari
|
||||
-webkit-column-count: @column-count;
|
||||
-moz-column-count: @column-count;
|
||||
column-count: @column-count;
|
||||
column-count: @column-count;
|
||||
// Safari
|
||||
-webkit-column-gap: @column-gap;
|
||||
-moz-column-gap: @column-gap;
|
||||
column-gap: @column-gap;
|
||||
column-gap: @column-gap;
|
||||
}
|
||||
|
||||
// Optional hyphenation
|
||||
.hyphens(@mode: auto) {
|
||||
word-wrap: break-word;
|
||||
// Safari
|
||||
-webkit-hyphens: @mode;
|
||||
-moz-hyphens: @mode;
|
||||
-ms-hyphens: @mode; // IE10+
|
||||
-o-hyphens: @mode;
|
||||
hyphens: @mode;
|
||||
hyphens: @mode;
|
||||
}
|
||||
|
||||
// Placeholder text
|
||||
.placeholder(@color) {
|
||||
// Firefox
|
||||
&::-moz-placeholder {
|
||||
// Safari
|
||||
&::-webkit-input-placeholder,
|
||||
&::placeholder {
|
||||
color: @color;
|
||||
opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526
|
||||
}
|
||||
&:-ms-input-placeholder { color: @color; } // Internet Explorer 10+
|
||||
&::-webkit-input-placeholder { color: @color; } // Safari and Chrome
|
||||
}
|
||||
|
||||
// Transformations
|
||||
// These remain for backwards compatibility with existing styles.
|
||||
.scale(@ratio) {
|
||||
-webkit-transform: scale(@ratio);
|
||||
-ms-transform: scale(@ratio); // IE9 only
|
||||
-o-transform: scale(@ratio);
|
||||
transform: scale(@ratio);
|
||||
transform: scale(@ratio);
|
||||
}
|
||||
.scale(@ratioX; @ratioY) {
|
||||
-webkit-transform: scale(@ratioX, @ratioY);
|
||||
-ms-transform: scale(@ratioX, @ratioY); // IE9 only
|
||||
-o-transform: scale(@ratioX, @ratioY);
|
||||
transform: scale(@ratioX, @ratioY);
|
||||
transform: scale(@ratioX, @ratioY);
|
||||
}
|
||||
.scaleX(@ratio) {
|
||||
-webkit-transform: scaleX(@ratio);
|
||||
-ms-transform: scaleX(@ratio); // IE9 only
|
||||
-o-transform: scaleX(@ratio);
|
||||
transform: scaleX(@ratio);
|
||||
transform: scaleX(@ratio);
|
||||
}
|
||||
.scaleY(@ratio) {
|
||||
-webkit-transform: scaleY(@ratio);
|
||||
-ms-transform: scaleY(@ratio); // IE9 only
|
||||
-o-transform: scaleY(@ratio);
|
||||
transform: scaleY(@ratio);
|
||||
transform: scaleY(@ratio);
|
||||
}
|
||||
.skew(@x; @y) {
|
||||
-webkit-transform: skewX(@x) skewY(@y);
|
||||
-ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
|
||||
-o-transform: skewX(@x) skewY(@y);
|
||||
transform: skewX(@x) skewY(@y);
|
||||
transform: skewX(@x) skewY(@y);
|
||||
}
|
||||
.translate(@x; @y) {
|
||||
-webkit-transform: translate(@x, @y);
|
||||
-ms-transform: translate(@x, @y); // IE9 only
|
||||
-o-transform: translate(@x, @y);
|
||||
transform: translate(@x, @y);
|
||||
transform: translate(@x, @y);
|
||||
}
|
||||
.translate3d(@x; @y; @z) {
|
||||
-webkit-transform: translate3d(@x, @y, @z);
|
||||
transform: translate3d(@x, @y, @z);
|
||||
transform: translate3d(@x, @y, @z);
|
||||
}
|
||||
.rotate(@degrees) {
|
||||
-webkit-transform: rotate(@degrees);
|
||||
-ms-transform: rotate(@degrees); // IE9 only
|
||||
-o-transform: rotate(@degrees);
|
||||
transform: rotate(@degrees);
|
||||
transform: rotate(@degrees);
|
||||
}
|
||||
.rotateX(@degrees) {
|
||||
-webkit-transform: rotateX(@degrees);
|
||||
-ms-transform: rotateX(@degrees); // IE9 only
|
||||
-o-transform: rotateX(@degrees);
|
||||
transform: rotateX(@degrees);
|
||||
transform: rotateX(@degrees);
|
||||
}
|
||||
.rotateY(@degrees) {
|
||||
-webkit-transform: rotateY(@degrees);
|
||||
-ms-transform: rotateY(@degrees); // IE9 only
|
||||
-o-transform: rotateY(@degrees);
|
||||
transform: rotateY(@degrees);
|
||||
transform: rotateY(@degrees);
|
||||
}
|
||||
.perspective(@perspective) {
|
||||
-webkit-perspective: @perspective;
|
||||
-moz-perspective: @perspective;
|
||||
perspective: @perspective;
|
||||
perspective: @perspective;
|
||||
}
|
||||
.perspective-origin(@perspective) {
|
||||
-webkit-perspective-origin: @perspective;
|
||||
-moz-perspective-origin: @perspective;
|
||||
perspective-origin: @perspective;
|
||||
perspective-origin: @perspective;
|
||||
}
|
||||
.transform-origin(@origin) {
|
||||
-webkit-transform-origin: @origin;
|
||||
-moz-transform-origin: @origin;
|
||||
-ms-transform-origin: @origin; // IE9 only
|
||||
transform-origin: @origin;
|
||||
transform-origin: @origin;
|
||||
}
|
||||
|
||||
|
||||
// Transitions
|
||||
|
||||
// These remain for backwards compatibility with existing styles.
|
||||
.transition(@transition) {
|
||||
-webkit-transition: @transition;
|
||||
-o-transition: @transition;
|
||||
transition: @transition;
|
||||
transition: @transition;
|
||||
}
|
||||
.transition-property(@transition-property) {
|
||||
-webkit-transition-property: @transition-property;
|
||||
transition-property: @transition-property;
|
||||
transition-property: @transition-property;
|
||||
}
|
||||
.transition-delay(@transition-delay) {
|
||||
-webkit-transition-delay: @transition-delay;
|
||||
transition-delay: @transition-delay;
|
||||
transition-delay: @transition-delay;
|
||||
}
|
||||
.transition-duration(@transition-duration) {
|
||||
-webkit-transition-duration: @transition-duration;
|
||||
transition-duration: @transition-duration;
|
||||
transition-duration: @transition-duration;
|
||||
}
|
||||
.transition-timing-function(@timing-function) {
|
||||
-webkit-transition-timing-function: @timing-function;
|
||||
transition-timing-function: @timing-function;
|
||||
transition-timing-function: @timing-function;
|
||||
}
|
||||
.transition-transform(@transition) {
|
||||
-webkit-transition: -webkit-transform @transition;
|
||||
-moz-transition: -moz-transform @transition;
|
||||
-o-transition: -o-transform @transition;
|
||||
transition: transform @transition;
|
||||
transition: transform @transition;
|
||||
}
|
||||
|
||||
|
||||
// User select
|
||||
// For selecting text on the page
|
||||
|
||||
.user-select(@select) {
|
||||
// Safari + MS Edge
|
||||
-webkit-user-select: @select;
|
||||
-moz-user-select: @select;
|
||||
-ms-user-select: @select; // IE10+
|
||||
user-select: @select;
|
||||
user-select: @select;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
|
||||
.LoadingIndicator {
|
||||
.LoadingIndicator-container {
|
||||
height: 46px;
|
||||
}
|
||||
}
|
||||
|
@@ -10,9 +10,10 @@
|
||||
.DiscussionList-loadMore {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.DiscussionList-loadMore .LoadingIndicator {
|
||||
height: 46px;
|
||||
|
||||
.LoadingIndicator-container {
|
||||
height: 46px;
|
||||
}
|
||||
}
|
||||
|
||||
@media @phone {
|
||||
|
@@ -18,6 +18,8 @@
|
||||
height: 300px;
|
||||
min-height: 50px; // JavaScript sets a max-height
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
.user-select(none);
|
||||
}
|
||||
.Scrubber-before, .Scrubber-after {
|
||||
border-left: 1px solid @control-bg;
|
||||
@@ -42,6 +44,7 @@
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
cursor: move;
|
||||
}
|
||||
.Scrubber-bar {
|
||||
height: 100%;
|
||||
|
@@ -278,6 +278,7 @@ core:
|
||||
rename_button: => core.ref.rename
|
||||
reply_button: => core.ref.reply
|
||||
restore_button: => core.ref.restore
|
||||
toggle_dropdown_accessible_label: Toggle discussion actions dropdown menu
|
||||
|
||||
# These translations are used in the discussion list.
|
||||
discussion_list:
|
||||
@@ -316,10 +317,12 @@ core:
|
||||
header:
|
||||
admin_button: Administration
|
||||
back_to_index_tooltip: Back to Discussion List
|
||||
locale_dropdown_accessible_label: Change forum locale
|
||||
log_in_link: => core.ref.log_in
|
||||
log_out_button: => core.ref.log_out
|
||||
profile_button: Profile
|
||||
search_placeholder: Search Forum
|
||||
session_dropdown_accessible_label: Toggle session options dropdown menu
|
||||
settings_button: => core.ref.settings
|
||||
sign_up_link: => core.ref.sign_up
|
||||
|
||||
@@ -332,6 +335,7 @@ core:
|
||||
meta_title_text: => core.ref.all_discussions
|
||||
refresh_tooltip: Refresh
|
||||
start_discussion_button: => core.ref.start_a_discussion
|
||||
toggle_sidenav_dropdown_accessible_label: Toggle navigation dropdown menu
|
||||
|
||||
# These translations are used by the sorting control above the discussion list.
|
||||
index_sort:
|
||||
@@ -339,6 +343,7 @@ core:
|
||||
newest_button: Newest
|
||||
oldest_button: Oldest
|
||||
relevance_button: Relevance
|
||||
toggle_dropdown_accessible_label: Change discussion list sorting
|
||||
top_button: Top
|
||||
|
||||
# These translations are used in the Log In modal dialog.
|
||||
@@ -359,6 +364,7 @@ core:
|
||||
mark_all_as_read_tooltip: => core.ref.mark_all_as_read
|
||||
mark_as_read_tooltip: Mark as Read
|
||||
title: => core.ref.notifications
|
||||
toggle_dropdown_accessible_label: View notifications
|
||||
tooltip: => core.ref.notifications
|
||||
|
||||
# These translations are used by tooltips displayed for individual posts.
|
||||
@@ -375,6 +381,7 @@ core:
|
||||
edit_button: => core.ref.edit
|
||||
hide_confirmation: "Are you sure you want to delete this post?"
|
||||
restore_button: => core.ref.restore
|
||||
toggle_dropdown_accessible_label: Toggle post controls dropdown menu
|
||||
|
||||
# These translations are used in the scrubber to the right of the post stream.
|
||||
post_scrubber:
|
||||
@@ -448,6 +455,7 @@ core:
|
||||
delete_error_message: "Deletion of user <i>{username} ({email})</i> failed"
|
||||
delete_success_message: "User <i>{username} ({email})</i> was deleted"
|
||||
edit_button: => core.ref.edit
|
||||
toggle_dropdown_accessible_label: Toggle user controls dropdown menu
|
||||
|
||||
# These translations are used in the alert that is shown when a new user has not confirmed their email address.
|
||||
user_email_confirmation:
|
||||
@@ -462,6 +470,10 @@ core:
|
||||
badge:
|
||||
hidden_tooltip: Hidden
|
||||
|
||||
# These translations are used in the dropdown component.
|
||||
dropdown:
|
||||
toggle_dropdown_accessible_label: Toggle dropdown menu
|
||||
|
||||
# These translations are displayed as error messages.
|
||||
error:
|
||||
dependent_extensions_message: "Cannot disable {extension} until the following dependent extensions are disabled: {extensions}"
|
||||
|
@@ -30,8 +30,6 @@ return [
|
||||
$table->integer('hide_user_id')->unsigned()->nullable();
|
||||
|
||||
$table->unique(['discussion_id', 'number']);
|
||||
|
||||
$table->engine = 'MyISAM';
|
||||
});
|
||||
|
||||
$connection = $schema->getConnection();
|
||||
|
@@ -49,6 +49,7 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
|
||||
$this->container->singleton('flarum.admin.middleware', function () {
|
||||
return [
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
'flarum.admin.error_handler',
|
||||
HttpMiddleware\ParseJsonBody::class,
|
||||
HttpMiddleware\StartSession::class,
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Admin\Middleware;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
@@ -18,7 +19,7 @@ class RequireAdministrateAbility implements Middleware
|
||||
{
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
@@ -57,6 +57,7 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
|
||||
$this->container->singleton('flarum.api.middleware', function () {
|
||||
return [
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
'flarum.api.error_handler',
|
||||
HttpMiddleware\ParseJsonBody::class,
|
||||
Middleware\FakeHttpMethods::class,
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Api;
|
||||
use Exception;
|
||||
use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use InvalidArgumentException;
|
||||
@@ -56,7 +57,7 @@ class Client
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals(null, $queryParams, $body);
|
||||
|
||||
$request = $request->withAttribute('actor', $actor);
|
||||
$request = RequestUtil::withActor($request, $actor);
|
||||
|
||||
if (is_string($controller)) {
|
||||
$controller = $this->container->make($controller);
|
||||
|
@@ -11,6 +11,9 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\JsonApiResponse;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
@@ -84,6 +87,11 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
*/
|
||||
protected static $beforeSerializationCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $loadRelations = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -139,6 +147,47 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
*/
|
||||
abstract protected function createElement($data, SerializerInterface $serializer);
|
||||
|
||||
/**
|
||||
* Eager loads the required relationships.
|
||||
*
|
||||
* @param Collection $models
|
||||
* @param array $relations
|
||||
* @return void
|
||||
*/
|
||||
protected function loadRelations(Collection $models, array $relations): void
|
||||
{
|
||||
$addedRelations = [];
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$loadRelations[$class])) {
|
||||
$addedRelations = array_merge($addedRelations, static::$loadRelations[$class]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($addedRelations)) {
|
||||
usort($addedRelations, function ($a, $b) {
|
||||
return substr_count($a, '.') - substr_count($b, '.');
|
||||
});
|
||||
|
||||
foreach ($addedRelations as $relation) {
|
||||
if (strpos($relation, '.') !== false) {
|
||||
$parentRelation = Str::beforeLast($relation, '.');
|
||||
|
||||
if (! in_array($parentRelation, $relations, true)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$relations[] = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($relations)) {
|
||||
$relations = array_unique($relations);
|
||||
$models->loadMissing($relations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return array
|
||||
@@ -207,6 +256,11 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
return new Parameters($request->getQueryParams());
|
||||
}
|
||||
|
||||
protected function sortIsDefault(ServerRequestInterface $request): bool
|
||||
{
|
||||
return ! Arr::get($request->getQueryParams(), 'sort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the serializer that will serialize data for the endpoint.
|
||||
*
|
||||
@@ -348,4 +402,13 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
|
||||
static::$beforeSerializationCallbacks[$controllerClass][] = $callback;
|
||||
}
|
||||
|
||||
public static function setLoadRelations(string $controllerClass, array $relations)
|
||||
{
|
||||
if (! isset(static::$loadRelations[$controllerClass])) {
|
||||
static::$loadRelations[$controllerClass] = [];
|
||||
}
|
||||
|
||||
static::$loadRelations[$controllerClass] = array_merge(static::$loadRelations[$controllerClass], $relations);
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Foundation\Console\CacheClearCommand;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
@@ -35,7 +36,7 @@ class ClearCacheController extends AbstractDeleteController
|
||||
*/
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$this->command->run(
|
||||
new ArrayInput([]),
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Api\Controller;
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Command\ReadDiscussion;
|
||||
use Flarum\Discussion\Command\StartDiscussion;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -53,7 +54,7 @@ class CreateDiscussionController extends AbstractCreateController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$ipAddress = $request->getAttribute('ipAddress');
|
||||
|
||||
$discussion = $this->bus->dispatch(
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\GroupSerializer;
|
||||
use Flarum\Group\Command\CreateGroup;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -42,7 +43,7 @@ class CreateGroupController extends AbstractCreateController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new CreateGroup($request->getAttribute('actor'), Arr::get($request->getParsedBody(), 'data', []))
|
||||
new CreateGroup(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', []))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Discussion\Command\ReadDiscussion;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Post\Command\PostReply;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -52,7 +53,7 @@ class CreatePostController extends AbstractCreateController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$data = Arr::get($request->getParsedBody(), 'data', []);
|
||||
$discussionId = Arr::get($data, 'relationships.discussion.data.id');
|
||||
$ipAddress = $request->getAttribute('ipAddress');
|
||||
|
@@ -59,7 +59,6 @@ class CreateTokenController implements RequestHandlerInterface
|
||||
|
||||
$identification = Arr::get($body, 'identification');
|
||||
$password = Arr::get($body, 'password');
|
||||
$lifetime = Arr::get($body, 'lifetime', 3600);
|
||||
|
||||
$user = $this->users->findByIdentification($identification);
|
||||
|
||||
@@ -67,13 +66,7 @@ class CreateTokenController implements RequestHandlerInterface
|
||||
throw new NotAuthenticatedException;
|
||||
}
|
||||
|
||||
// Use of lifetime attribute is deprecated in beta 16, removed in beta 17
|
||||
// For backward compatibility with custom integrations, longer lifetimes will be interpreted as remember tokens
|
||||
if ($lifetime > 3600 || Arr::get($body, 'remember')) {
|
||||
if ($lifetime > 3600) {
|
||||
trigger_error('Use of parameter lifetime is deprecated in beta 16, will be removed in beta 17. Use remember parameter to start a remember session', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
if (Arr::get($body, 'remember')) {
|
||||
$token = RememberAccessToken::generate($user->id);
|
||||
} else {
|
||||
$token = SessionAccessToken::generate($user->id);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\RegisterUser;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -42,7 +43,7 @@ class CreateUserController extends AbstractCreateController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new RegisterUser($request->getAttribute('actor'), Arr::get($request->getParsedBody(), 'data', []))
|
||||
new RegisterUser(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', []))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\DeleteAvatar;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -42,7 +43,7 @@ class DeleteAvatarController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new DeleteAvatar(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
|
||||
new DeleteAvatar(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Discussion\Command\DeleteDiscussion;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -35,7 +36,7 @@ class DeleteDiscussionController extends AbstractDeleteController
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$input = $request->getParsedBody();
|
||||
|
||||
$this->bus->dispatch(
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
@@ -41,7 +42,7 @@ class DeleteFaviconController extends AbstractDeleteController
|
||||
*/
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$path = $this->settings->get('favicon_path');
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Group\Command\DeleteGroup;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -35,7 +36,7 @@ class DeleteGroupController extends AbstractDeleteController
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$this->bus->dispatch(
|
||||
new DeleteGroup(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
|
||||
new DeleteGroup(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
@@ -41,7 +42,7 @@ class DeleteLogoController extends AbstractDeleteController
|
||||
*/
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$path = $this->settings->get('logo_path');
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Post\Command\DeletePost;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -35,7 +36,7 @@ class DeletePostController extends AbstractDeleteController
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$this->bus->dispatch(
|
||||
new DeletePost(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
|
||||
new DeletePost(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\DeleteUser;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -35,7 +36,7 @@ class DeleteUserController extends AbstractDeleteController
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$this->bus->dispatch(
|
||||
new DeleteUser(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
|
||||
new DeleteUser(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Filter\DiscussionFilterer;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Query\QueryCriteria;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -85,15 +86,16 @@ class ListDiscussionsController extends AbstractListController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
$sortIsDefault = $this->sortIsDefault($request);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$include = array_merge($this->extractInclude($request), ['state']);
|
||||
|
||||
$criteria = new QueryCriteria($actor, $filters, $sort);
|
||||
$criteria = new QueryCriteria($actor, $filters, $sort, $sortIsDefault);
|
||||
if (array_key_exists('q', $filters)) {
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
} else {
|
||||
@@ -121,7 +123,9 @@ class ListDiscussionsController extends AbstractListController
|
||||
}
|
||||
}
|
||||
|
||||
$results = $results->getResults()->load($include);
|
||||
$results = $results->getResults();
|
||||
|
||||
$this->loadRelations($results, $include);
|
||||
|
||||
if ($relations = array_intersect($include, ['firstPost', 'lastPost', 'mostRelevantPost'])) {
|
||||
foreach ($results as $discussion) {
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\GroupSerializer;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -26,8 +27,12 @@ class ListGroupsController extends AbstractListController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
return Group::whereVisibleTo($actor)->get();
|
||||
$results = Group::whereVisibleTo($actor)->get();
|
||||
|
||||
$this->loadRelations($results, []);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\NotificationSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Notification\NotificationRepository;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -62,7 +63,7 @@ class ListNotificationsController extends AbstractListController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$actor->assertRegistered();
|
||||
|
||||
@@ -76,9 +77,11 @@ class ListNotificationsController extends AbstractListController
|
||||
$include[] = 'subject';
|
||||
}
|
||||
|
||||
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset)
|
||||
->load(array_diff($include, ['subject.discussion']))
|
||||
->all();
|
||||
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset);
|
||||
|
||||
$this->loadRelations($notifications, array_diff($include, ['subject.discussion']));
|
||||
|
||||
$notifications = $notifications->all();
|
||||
|
||||
$areMoreResults = false;
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Post\Filter\PostFilterer;
|
||||
use Flarum\Post\PostRepository;
|
||||
@@ -74,16 +75,17 @@ class ListPostsController extends AbstractListController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
$sortIsDefault = $this->sortIsDefault($request);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$results = $this->filterer->filter(new QueryCriteria($actor, $filters, $sort), $limit, $offset);
|
||||
$results = $this->filterer->filter(new QueryCriteria($actor, $filters, $sort, $sortIsDefault), $limit, $offset);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('posts.index'),
|
||||
@@ -93,7 +95,22 @@ class ListPostsController extends AbstractListController
|
||||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
return $results->getResults()->load($include);
|
||||
// Eager load discussion for use in the policies,
|
||||
// eager loading does not affect the JSON response,
|
||||
// the response only includes relations included in the request.
|
||||
if (! in_array('discussion', $include)) {
|
||||
$include[] = 'discussion';
|
||||
}
|
||||
|
||||
if (in_array('user', $include)) {
|
||||
$include[] = 'user.groups';
|
||||
}
|
||||
|
||||
$results = $results->getResults();
|
||||
|
||||
$this->loadRelations($results, $include);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +118,7 @@ class ListPostsController extends AbstractListController
|
||||
*/
|
||||
protected function extractOffset(ServerRequestInterface $request)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$queryParams = $request->getQueryParams();
|
||||
$sort = $this->extractSort($request);
|
||||
$limit = $this->extractLimit($request);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Query\QueryCriteria;
|
||||
use Flarum\User\Filter\UserFilterer;
|
||||
@@ -72,7 +73,7 @@ class ListUsersController extends AbstractListController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$actor->assertCan('viewUserList');
|
||||
|
||||
@@ -85,12 +86,13 @@ class ListUsersController extends AbstractListController
|
||||
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
$sortIsDefault = $this->sortIsDefault($request);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$criteria = new QueryCriteria($actor, $filters, $sort);
|
||||
$criteria = new QueryCriteria($actor, $filters, $sort, $sortIsDefault);
|
||||
if (array_key_exists('q', $filters)) {
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
} else {
|
||||
@@ -105,6 +107,10 @@ class ListUsersController extends AbstractListController
|
||||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
return $results->getResults()->load($include);
|
||||
$results = $results->getResults();
|
||||
|
||||
$this->loadRelations($results, $include);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Notification\Command\ReadAllNotifications;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -34,7 +35,7 @@ class ReadAllNotificationsController extends AbstractDeleteController
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$this->bus->dispatch(
|
||||
new ReadAllNotifications($request->getAttribute('actor'))
|
||||
new ReadAllNotifications(RequestUtil::getActor($request))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,10 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Mail\Job\SendRawEmailJob;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\EmailToken;
|
||||
use Flarum\User\AccountActivationMailerTrait;
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -24,6 +24,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class SendConfirmationEmailController implements RequestHandlerInterface
|
||||
{
|
||||
use AccountActivationMailerTrait;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
@@ -64,7 +66,7 @@ class SendConfirmationEmailController implements RequestHandlerInterface
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$actor->assertRegistered();
|
||||
|
||||
@@ -72,19 +74,10 @@ class SendConfirmationEmailController implements RequestHandlerInterface
|
||||
throw new PermissionDeniedException;
|
||||
}
|
||||
|
||||
$token = EmailToken::generate($actor->email, $actor->id);
|
||||
$token->save();
|
||||
$token = $this->generateToken($actor, $actor->email);
|
||||
$data = $this->getEmailData($actor, $token);
|
||||
|
||||
$data = [
|
||||
'{username}' => $actor->username,
|
||||
'{url}' => $this->url->to('forum')->route('confirmEmail', ['token' => $token->token]),
|
||||
'{forum}' => $this->settings->get('forum_title')
|
||||
];
|
||||
|
||||
$body = $this->translator->trans('core.email.activate_account.body', $data);
|
||||
$subject = $this->translator->trans('core.email.activate_account.subject');
|
||||
|
||||
$this->queue->push(new SendRawEmailJob($actor->email, $subject, $body));
|
||||
$this->sendConfirmationEmail($actor, $data);
|
||||
|
||||
return new EmptyResponse;
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Mail\Mailer;
|
||||
use Illuminate\Mail\Message;
|
||||
@@ -35,7 +36,7 @@ class SendTestMailController implements RequestHandlerInterface
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor->assertAdmin();
|
||||
|
||||
$body = $this->translator->trans('core.email.send_test.body', ['{username}' => $actor->username]);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Group\Permission;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -23,7 +24,7 @@ class SetPermissionController implements RequestHandlerInterface
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$body = $request->getParsedBody();
|
||||
$permission = Arr::get($body, 'permission');
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\Event;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
@@ -43,7 +44,7 @@ class SetSettingsController implements RequestHandlerInterface
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$settings = $request->getParsedBody();
|
||||
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Api\Controller;
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Flarum\User\User;
|
||||
@@ -82,7 +83,7 @@ class ShowDiscussionController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$discussionId = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
if (Arr::get($request->getQueryParams(), 'bySlug', false)) {
|
||||
@@ -111,7 +112,7 @@ class ShowDiscussionController extends AbstractShowController
|
||||
*/
|
||||
private function includePosts(Discussion $discussion, ServerRequestInterface $request, array $include)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->getPostsOffset($request, $discussion, $limit);
|
||||
|
||||
@@ -160,7 +161,7 @@ class ShowDiscussionController extends AbstractShowController
|
||||
private function getPostsOffset(ServerRequestInterface $request, Discussion $discussion, $limit)
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
if (($near = Arr::get($queryParams, 'page.near')) > 1) {
|
||||
$offset = $this->posts->getIndexForNumber($discussion->id, $near, $actor);
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\ForumSerializer;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -32,7 +33,7 @@ class ShowForumController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return [
|
||||
'groups' => Group::whereVisibleTo($request->getAttribute('actor'))->get()
|
||||
'groups' => Group::whereVisibleTo(RequestUtil::getActor($request))->get()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\MailSettingsSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Validation\Factory;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -27,7 +28,7 @@ class ShowMailSettingsController extends AbstractShowController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$drivers = array_map(function ($driver) {
|
||||
return self::$container->make($driver);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -51,6 +52,6 @@ class ShowPostController extends AbstractShowController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return $this->posts->findOrFail(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'));
|
||||
return $this->posts->findOrFail(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request));
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\User\User;
|
||||
use Flarum\User\UserRepository;
|
||||
@@ -56,7 +57,7 @@ class ShowUserController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
if (Arr::get($request->getQueryParams(), 'bySlug', false)) {
|
||||
$user = $this->slugManager->forResource(User::class)->fromSlug($id, $actor);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
@@ -30,7 +31,7 @@ class UninstallExtensionController extends AbstractDeleteController
|
||||
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$name = Arr::get($request->getQueryParams(), 'name');
|
||||
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Api\Controller;
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Command\EditDiscussion;
|
||||
use Flarum\Discussion\Command\ReadDiscussion;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -43,7 +44,7 @@ class UpdateDiscussionController extends AbstractShowController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$discussionId = Arr::get($request->getQueryParams(), 'id');
|
||||
$data = Arr::get($request->getParsedBody(), 'data', []);
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -36,7 +37,7 @@ class UpdateExtensionController implements RequestHandlerInterface
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$enabled = Arr::get($request->getParsedBody(), 'enabled');
|
||||
$name = Arr::get($request->getQueryParams(), 'name');
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\GroupSerializer;
|
||||
use Flarum\Group\Command\EditGroup;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -42,7 +43,7 @@ class UpdateGroupController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$data = Arr::get($request->getParsedBody(), 'data', []);
|
||||
|
||||
return $this->bus->dispatch(
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\NotificationSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Notification\Command\ReadNotification;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -42,7 +43,7 @@ class UpdateNotificationController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
return $this->bus->dispatch(
|
||||
new ReadNotification($id, $actor)
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Post\Command\EditPost;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -50,7 +51,7 @@ class UpdatePostController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$data = Arr::get($request->getParsedBody(), 'data', []);
|
||||
|
||||
return $this->bus->dispatch(
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\EditUser;
|
||||
use Flarum\User\Exception\NotAuthenticatedException;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
@@ -49,7 +50,7 @@ class UpdateUserController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$data = Arr::get($request->getParsedBody(), 'data', []);
|
||||
|
||||
if ($actor->id == $id) {
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\UploadAvatar;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -42,7 +43,7 @@ class UploadAvatarController extends AbstractShowController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = $request->getAttribute('actor');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$file = Arr::get($request->getUploadedFiles(), 'avatar');
|
||||
|
||||
return $this->bus->dispatch(
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -60,7 +61,7 @@ abstract class UploadImageController extends ShowForumController
|
||||
*/
|
||||
public function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$request->getAttribute('actor')->assertAdmin();
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$file = Arr::get($request->getUploadedFiles(), $this->filenamePrefix);
|
||||
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Api\Serializer;
|
||||
|
||||
use Closure;
|
||||
use DateTime;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -64,7 +65,7 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
|
||||
public function setRequest(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->actor = $request->getAttribute('actor');
|
||||
$this->actor = RequestUtil::getActor($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -9,12 +9,14 @@
|
||||
|
||||
namespace Flarum\Console;
|
||||
|
||||
use Flarum\Database\Console\GenerateMigrationCommand;
|
||||
use Flarum\Database\Console\MigrateCommand;
|
||||
use Flarum\Database\Console\ResetCommand;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Console\CacheClearCommand;
|
||||
use Flarum\Foundation\Console\InfoCommand;
|
||||
use Illuminate\Console\Scheduling\Schedule as LaravelSchedule;
|
||||
use Illuminate\Console\Scheduling\ScheduleListCommand;
|
||||
use Illuminate\Console\Scheduling\ScheduleRunCommand;
|
||||
|
||||
class ConsoleServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
@@ -23,14 +25,42 @@ class ConsoleServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// Used by Laravel to proxy artisan commands to its binary.
|
||||
// Flarum uses a similar binary, but it's called flarum.
|
||||
if (! defined('ARTISAN_BINARY')) {
|
||||
define('ARTISAN_BINARY', 'flarum');
|
||||
}
|
||||
|
||||
$this->container->singleton(LaravelSchedule::class, function () {
|
||||
return $this->container->make(Schedule::class);
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.console.commands', function () {
|
||||
return [
|
||||
CacheClearCommand::class,
|
||||
GenerateMigrationCommand::class,
|
||||
InfoCommand::class,
|
||||
MigrateCommand::class,
|
||||
ResetCommand::class,
|
||||
ScheduleListCommand::class,
|
||||
ScheduleRunCommand::class
|
||||
];
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.console.scheduled', function () {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$schedule = $this->container->make(LaravelSchedule::class);
|
||||
|
||||
foreach ($this->container->make('flarum.console.scheduled') as $scheduled) {
|
||||
$event = $schedule->command($scheduled['command'], $scheduled['args']);
|
||||
$scheduled['callback']($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
src/Console/Schedule.php
Normal file
37
src/Console/Schedule.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Console;
|
||||
|
||||
use Flarum\Foundation\Config;
|
||||
use Illuminate\Console\Scheduling\Schedule as LaravelSchedule;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class Schedule extends LaravelSchedule
|
||||
{
|
||||
public function dueEvents($container)
|
||||
{
|
||||
return (new Collection($this->events))->filter->isDue(new class($container) {
|
||||
public function __construct($container)
|
||||
{
|
||||
$this->config = $container->make(Config::class);
|
||||
}
|
||||
|
||||
public function isDownForMaintenance()
|
||||
{
|
||||
return $this->config->inMaintenanceMode();
|
||||
}
|
||||
|
||||
public function environment()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Database\Console;
|
||||
|
||||
use Flarum\Console\AbstractCommand;
|
||||
use Flarum\Database\MigrationCreator;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class GenerateMigrationCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* @var MigrationCreator
|
||||
*/
|
||||
protected $creator;
|
||||
|
||||
/**
|
||||
* @param MigrationCreator $creator
|
||||
*/
|
||||
public function __construct(MigrationCreator $creator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->creator = $creator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('generate:migration')
|
||||
->setDescription('Generate a migration')
|
||||
->addArgument(
|
||||
'name',
|
||||
InputArgument::REQUIRED,
|
||||
'The name of the migration.'
|
||||
)
|
||||
->addOption(
|
||||
'extension',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The extension to generate the migration for.'
|
||||
)
|
||||
->addOption(
|
||||
'create',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The table to be created.'
|
||||
)
|
||||
->addOption(
|
||||
'table',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The table to migrate.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function fire()
|
||||
{
|
||||
$name = $this->input->getArgument('name');
|
||||
|
||||
$extension = $this->input->getOption('extension');
|
||||
|
||||
$table = $this->input->getOption('table');
|
||||
|
||||
$create = $this->input->getOption('create');
|
||||
|
||||
if (! $table && is_string($create)) {
|
||||
$table = $create;
|
||||
}
|
||||
|
||||
$this->writeMigration($name, $extension, $table, $create);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration file to disk.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $extension
|
||||
* @param string $table
|
||||
* @param bool $create
|
||||
* @return string
|
||||
*/
|
||||
protected function writeMigration($name, $extension, $table, $create)
|
||||
{
|
||||
$path = $this->creator->create($name, $extension, $table, $create);
|
||||
|
||||
$file = pathinfo($path, PATHINFO_FILENAME);
|
||||
|
||||
$this->info("Created migration: $file");
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user