1
0
mirror of https://github.com/flarum/core.git synced 2025-08-13 20:04:24 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
KyrneDev
9f6414d1c7 Add comment 2021-03-03 09:17:19 -05:00
KyrneDev
125297278b Debug removal 2021-03-03 09:17:19 -05:00
KyrneDev
727440e88b Prettier 2021-03-03 09:17:19 -05:00
KyrneDev
4464ab156f Convert to state 2021-03-03 09:17:19 -05:00
KyrneDev
b965a82bb1 format 2021-03-03 09:17:19 -05:00
KyrneDev
7b6ab61508 Scrubber 2021-03-03 09:17:19 -05:00
325 changed files with 3576 additions and 13449 deletions

View File

@@ -15,5 +15,5 @@ indent_size = 2
[*.{diff,md}]
trim_trailing_whitespace = false
[*.{php,xml,json}]
[*.{php,xml}]
indent_size = 4

View File

@@ -7,24 +7,10 @@ on:
jobs:
build:
name: JS / Build
runs-on: ubuntu-latest
steps:
- 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
- uses: actions/checkout@master
- uses: flarum/action-build@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,76 +0,0 @@
# 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

View File

@@ -1,7 +1,6 @@
name: Lint
on:
workflow_dispatch:
push:
paths:
- 'js/src/**'
@@ -11,18 +10,22 @@ on:
jobs:
prettier:
name: JS / Prettier
runs-on: ubuntu-latest
name: JS / Prettier
steps:
- name: Check out code
uses: actions/checkout@v2
- uses: actions/checkout@master
- name: Set up Node
uses: actions/setup-node@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: "14"
node-version: "12"
- name: Check JS formatting
run: npx prettier --check src
- name: Install JS dependencies
run: npm ci
working-directory: ./js
- name: Check JS code for formatting
run: node_modules/.bin/prettier --check src
working-directory: ./js

View File

@@ -1,45 +0,0 @@
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 }}

View File

@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
php: [7.3, 7.4, '8.0']
php: ['7.2', '7.3', '7.4', '8.0']
service: ['mysql:5.7', mariadb]
prefix: ['', flarum_]
@@ -21,6 +21,12 @@ jobs:
prefixStr: (prefix)
exclude:
- php: 7.2
service: 'mysql:5.7'
prefix: flarum_
- php: 7.2
service: mariadb
prefix: flarum_
- php: 7.3
service: 'mysql:5.7'
prefix: flarum_
@@ -43,11 +49,10 @@ jobs:
name: 'PHP ${{ matrix.php }} / ${{ matrix.db }} ${{ matrix.prefixStr }}'
steps:
- name: Check out code
uses: actions/checkout@v2
- uses: actions/checkout@master
- name: Setup PHP
uses: shivammathur/setup-php@0b9d33cd0782337377999751fc10ea079fdd7104 # pin@v2
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
@@ -75,5 +80,3 @@ jobs:
- name: Run Composer tests
run: composer test
env:
COMPOSER_PROCESS_TIMEOUT: 600

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@ composer.phar
node_modules
.DS_Store
Thumbs.db
tests/.phpunit.result.cache
/tests/integration/tmp
.vagrant
.idea/*

View File

@@ -12,3 +12,7 @@ disabled:
- phpdoc_order
- phpdoc_separation
- phpdoc_types
finder:
exclude:
- "stubs"

View File

@@ -1,96 +1,5 @@
# Changelog
## [0.1.0-beta.16](https://github.com/flarum/core/compare/v0.1.0-beta.15...v0.1.0-beta.16)
### Added
- Allow event subscribers (https://github.com/flarum/core/pull/2535)
- Allow Settings extender to have a default value (https://github.com/flarum/core/pull/2495)
- Allow hooking into the sending of notifications before being send (https://github.com/flarum/core/pull/2533)
- PHP 8 support (https://github.com/flarum/core/pull/2507)
- Search extender (https://github.com/flarum/core/pull/2483)
- User badges to post preview (https://github.com/flarum/core/pull/2555)
- Optional extension dependencies allow a booting order (https://github.com/flarum/core/pull/2579)
- Auth extender (https://github.com/flarum/core/pull/2176)
- `X-Powered-By` header added to allow indexers easier data aggregation of Flarum adoption (https://github.com/flarum/core/pull/2618)
### Changed
- Run integration tests in transaction (https://github.com/flarum/core/pull/2304)
- Allow policies to return a boolean for simplified allow/deny (https://github.com/flarum/core/pull/2534)
- Converted highlight helper to typescript (https://github.com/flarum/core/pull/2532)
- Add accessibility attributes to Mark as Read button (https://github.com/flarum/core/pull/2564)
- Dismiss errors on change email modal upon a new request ([00913d5](https://github.com/flarum/core/commit/00913d5b0be2172cfce1f16aaf64a24f3d2e6d4b))
- Disabled extensions now are marked with a red circle instead of a red dot (https://github.com/flarum/core/pull/2562)
- Extension dependency errors now show the extension title instead of the ID (https://github.com/flarum/core/pull/2563)
- Change `mutate` method on ApiSerializer extender to `attributes` (https://github.com/flarum/core/pull/2578)
- Moved locale files to the core from the language pack (https://github.com/flarum/core/pull/2408)
- AdminPage extensibility and generic improvements (https://github.com/flarum/core/pull/2593)
- Remove entry of authors, link to https://flarum.org/team (https://github.com/flarum/core/pull/2625)
- Search and filtering are split (https://github.com/flarum/core/pull/2454)
- Move IP identification into a middleware (https://github.com/flarum/core/pull/2624)
- Editor Driver abstraction introduced (https://github.com/flarum/core/pull/2594)
- Allow overriding routes (https://github.com/flarum/core/pull/2577)
- Split user edit permissions into permissions for editing of user credentials, username, groups and suspending (https://github.com/flarum/core/pull/2620)
- Reduced number of admin extension categories (https://github.com/flarum/core/pull/2604)
- Move search related classes to a dedicated Query namespace (https://github.com/flarum/core/pull/2645)
- Rewrite common helpers into typescript (https://github.com/flarum/core/pull/2541)
- `TextEditor` is moved to the common namespace for use in the admin frontend (https://github.com/flarum/core/pull/2649)
- Update Laravel/Illuminate components to 8 (https://github.com/flarum/core/pull/2576)
- Eager load relations in discussion listing to improve performance (https://github.com/flarum/core/pull/2639)
- Adopt flarum/testing package (https://github.com/flarum/core/pull/2545)
- Replace `user` gambit with `author` gambit ([612a57c](https://github.com/flarum/core/commit/612a57c4664415a3ea120103483645c32acc6f12))
- Posts page of on user profile loads posts using username instead of id ([30017ee](https://github.com/flarum/core/commit/30017eef09ae9e78640c4e2cacd4909fffa8d775))
### Fixed
- Transform css breaks iOS scroll functionality (https://github.com/flarum/core/pull/2527)
- Composer header is hidden on mobile devices (https://github.com/flarum/core/pull/2279)
- Cannot delete a post or discussion of a deleted user (https://github.com/flarum/core/pull/2521)
- DiscussionListPane jumps around not keeping the scroll position (https://github.com/flarum/core/pull/2402)
- Infinite scroll on notifications dropdown broken (https://github.com/flarum/core/pull/2524)
- The show language selector switch remains toggled on ([9347b12](https://github.com/flarum/core/commit/9347b12b47bf4ab97ffb7ca92673604b237c1012))
- Model Visibility extender throws exception on extensions that aren't installed or enabled (https://github.com/flarum/core/pull/2580)
- Extensions are marked as enabled when enabling fails to unmet extension dependencies (https://github.com/flarum/core/pull/2558)
- Routes to admin extension pages without a valid ID break the admin page (https://github.com/flarum/core/pull/2584)
- Disabled fieldset use an incorrect CSS property `disallowed` (https://github.com/flarum/core/pull/2585)
- Scrolling to a post that is already loaded the Load More button shows and does not trigger (https://github.com/flarum/core/pull/2388)
- Opening discussions on some mobile devices require a double tap (https://github.com/flarum/core/pull/2607)
- iOS devices show erratic behavior in the post stream while updating (https://github.com/flarum/core/pull/2548)
- Small mobile screens partially hides the composer when the keyboard is open (https://github.com/flarum/core/pull/2631)
- Clearing cache does not clear the template cache in storage/views (https://github.com/flarum/core/pull/2648)
- Boot errors show critical information (https://github.com/flarum/core/pull/2633)
- List user endpoint discloses last online even if user choose against it (https://github.com/flarum/core/pull/2634)
- Group gambit disclosed hidden groups (https://github.com/flarum/core/pull/2657)
- Search results on small windows not fully visible (https://github.com/flarum/core/pull/2650)
- Composer goes off screen on Safari when starting to type (https://github.com/flarum/core/pull/2660)
- A search that has no results shows the search results dropdown ([b88a7cb](https://github.com/flarum/core/commit/b88a7cb33b56e318f11670e9e2d563aef94db039))
- The composer modal moves around when typing on Safari ([a64c398](https://github.com/flarum/core/commit/a64c39835aba43e831209609f4a9638ae589aa41))
### Removed
- Deprecated CSRF wildcard path match
- Deprecated policy and visibility scoping events
- Deprecated post types event
- Deprecated validation events
- Deprecated notification events
- Deprecated floodgate
- Deprecated user preferences event
- Deprecated formatting events
- Deprecated api events
- Deprecated bootstrap.php support
- PHP 7.2 support (https://github.com/flarum/core/pull/2507)
- Bidi attribute in the rendered HTML (https://github.com/flarum/core/pull/2602)
- `AccessToken::find`, use `AccessToken::findValid` instead (https://github.com/flarum/core/pull/2651)
### Deprecated
- `GetModelIsPrivate` event (https://github.com/flarum/core/pull/2587)
- `CheckingPassword` event (https://github.com/flarum/core/pull/2176)
- `event()` helper (https://github.com/flarum/core/pull/2608)
- `AccessToken::generate` argument `$lifetime` (https://github.com/flarum/core/pull/2651)
- `Rememberer::remember` argument `$token` should receive an instance of `RememberAccessToken` with `AccessToken` being deprecated (https://github.com/flarum/core/pull/2651)
- `Rememberer::rememberUser` (https://github.com/flarum/core/pull/2651)
- `SessionAuthenticator::logIn` argument `$userId`, should be replaced with `AccessToken` (https://github.com/flarum/core/pull/2651)
- `TextEditor` has been moved to `common` (https://github.com/flarum/core/pull/2649)
- `UserFilter` ([91e8b56](https://github.com/flarum/core/commit/91e8b569618957c86757ef89bac666e9102db5ae))
## [0.1.0-beta.15](https://github.com/flarum/core/compare/v0.1.0-beta.14.1...v0.1.0-beta.15)
### Added
@@ -301,7 +210,7 @@
- SES mail support (#2011)
- Backward compatibility layer for `Flarum\Mail\DriverInterface`, new methods from beta.12 are now required
- `Flarum\Util\Str` helper class
- `Flarum\Event\ConfigureMiddleware` event
- `Flarum\Event\ConfigureMiddleware` event
### Deprecated
- `Flarum\Event\AbstractConfigureRoutes` event class

View File

@@ -1,10 +1,7 @@
{
"name": "flarum/core",
"description": "Delightfully simple forum software.",
"keywords": [
"forum",
"discussion"
],
"keywords": ["forum", "discussion"],
"homepage": "https://flarum.org/",
"license": "MIT",
"authors": [
@@ -20,29 +17,27 @@
"docs": "https://flarum.org/docs/"
},
"require": {
"php": ">=7.3",
"php": ">=7.2",
"axy/sourcemap": "^0.1.4",
"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",
"illuminate/events": "^8.0",
"illuminate/filesystem": "^8.0",
"illuminate/hashing": "^8.0",
"illuminate/mail": "^8.0",
"illuminate/queue": "^8.0",
"illuminate/session": "^8.0",
"illuminate/support": "^8.0",
"illuminate/validation": "^8.0",
"illuminate/view": "^8.0",
"illuminate/bus": "^6.0",
"illuminate/cache": "^6.0",
"illuminate/config": "^6.0",
"illuminate/container": "^6.0",
"illuminate/contracts": "^6.0",
"illuminate/database": "^6.0",
"illuminate/events": "^6.0",
"illuminate/filesystem": "^6.0",
"illuminate/hashing": "^6.0",
"illuminate/mail": "^6.0",
"illuminate/queue": "^6.0",
"illuminate/session": "^6.0",
"illuminate/support": "^6.0",
"illuminate/validation": "^6.0",
"illuminate/view": "^6.0",
"intervention/image": "^2.5.0",
"laminas/laminas-diactoros": "^2.4.1",
"laminas/laminas-httphandlerrunner": "^1.2.0",
@@ -59,17 +54,18 @@
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"s9e/text-formatter": "^2.3.6",
"symfony/config": "^5.2.2",
"symfony/console": "^5.2.2",
"symfony/event-dispatcher": "^5.2.2",
"symfony/config": "^4.3.4",
"symfony/console": "^4.3.4",
"symfony/event-dispatcher": "^4.3.4",
"symfony/mime": "^5.2.0",
"symfony/translation": "^5.1.5",
"symfony/yaml": "^5.2.2",
"symfony/translation": "^4.3.4",
"symfony/yaml": "^4.3.4",
"tobscure/json-api": "^0.3.0",
"wikimedia/less.php": "^3.0"
},
"require-dev": {
"flarum/testing": "^0.1.0-beta.16"
"mockery/mockery": "^1.3.3",
"phpunit/phpunit": "^8.0"
},
"autoload": {
"psr-4": {

View File

@@ -1,8 +0,0 @@
{
"files": [
{
"path": "./dist/*.js"
}
],
"defaultCompression": "gzip"
}

16
js/dist/admin.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

18
js/dist/forum.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10518
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,40 +2,32 @@
"private": true,
"name": "@flarum/core",
"dependencies": {
"@babel/preset-typescript": "^7.10.1",
"@types/mithril": "^2.0.3",
"bootstrap": "^3.4.1",
"clsx": "^1.1.1",
"classnames": "^2.2.5",
"color-thief-browser": "^2.0.2",
"dayjs": "^1.10.4",
"expose-loader": "^1.0.3",
"jquery": "^3.6.0",
"dayjs": "^1.8.28",
"expose-loader": "^0.7.5",
"flarum-webpack-config": "0.1.0-beta.10",
"jquery": "^3.5.1",
"jquery.hotkeys": "^0.1.0",
"lodash-es": "^4.17.21",
"lodash-es": "^4.17.14",
"mithril": "^2.0.4",
"punycode": "^2.1.1",
"spin.js": "^3.1.0",
"textarea-caret": "^3.1.0"
"textarea-caret": "^3.1.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-merge": "^4.1.4"
},
"devDependencies": {
"@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": "^4.46.0",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.2"
"husky": "^4.2.5",
"prettier": "2.0.2"
},
"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
View File

@@ -19,21 +19,9 @@ import Application from './src/common/Application';
* to (and should not) bundle these themselves.
*/
declare global {
// $ is already defined by `@types/jquery`
const $: typeof _$;
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;
}
}
/**

View File

@@ -1,8 +0,0 @@
import Admin from './AdminApplication';
const app = new Admin();
// @ts-ignore
window.app = app;
export default app;

View File

@@ -126,17 +126,16 @@ export default class AdminNav extends Component {
categorizedExtensions[category].map((extension) => {
const query = this.query().toUpperCase();
const title = extension.extra['flarum-extension'].title || '';
const description = extension.description || '';
const title = extension.extra['flarum-extension'].title;
if (!query || title.toUpperCase().includes(query) || description.toUpperCase().includes(query)) {
if (!query || title.toUpperCase().includes(query) || extension.description.toUpperCase().includes(query)) {
items.add(
`extension-${extension.id}`,
<ExtensionLinkButton
href={app.route('extension', { id: extension.id })}
extensionId={extension.id}
className="ExtensionNavButton"
title={description}
title={extension.description}
>
{title}
</ExtensionLinkButton>,

View File

@@ -98,41 +98,35 @@ export default class AdminPage extends Page {
return entry.call(this);
}
const { setting, help, ...componentAttrs } = entry;
delete componentAttrs.help;
const setting = entry.setting;
const help = entry.help;
delete entry.help;
const value = this.setting([setting])();
if (['bool', 'checkbox', 'switch', 'boolean'].includes(componentAttrs.type)) {
if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {
return (
<div className="Form-group">
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...componentAttrs}>
{componentAttrs.label}
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...entry}>
{entry.label}
</Switch>
<div className="helpText">{help}</div>
</div>
);
} else if (['select', 'dropdown', 'selectdropdown'].includes(componentAttrs.type)) {
} else if (['select', 'dropdown', 'selectdropdown'].includes(entry.type)) {
return (
<div className="Form-group">
<label>{componentAttrs.label}</label>
<label>{entry.label}</label>
<div className="helpText">{help}</div>
<Select
value={value || componentAttrs.default}
options={componentAttrs.options}
buttonClassName="Button"
onchange={this.settings[setting]}
{...componentAttrs}
/>
<Select value={value || entry.default} options={entry.options} buttonClassName="Button" onchange={this.settings[setting]} {...entry} />
</div>
);
} else {
componentAttrs.className = classList(['FormControl', componentAttrs.className]);
entry.className = classList(['FormControl', entry.className]);
return (
<div className="Form-group">
{componentAttrs.label ? <label>{componentAttrs.label}</label> : ''}
{entry.label ? <label>{entry.label}</label> : ''}
<div className="helpText">{help}</div>
<input type={componentAttrs.type} bidi={this.setting(setting)} {...componentAttrs} />
<input type={entry.type} bidi={this.setting(setting)} {...entry} />
</div>
);
}

View File

@@ -1,4 +1,9 @@
import app from './app';
import AdminApplication from './AdminApplication';
const app = new AdminApplication();
// Backwards compatibility
window.app = app;
export { app };

View File

@@ -24,7 +24,6 @@ import { flattenDeep } from 'lodash-es';
import PageState from './states/PageState';
import ModalManagerState from './states/ModalManagerState';
import AlertManagerState from './states/AlertManagerState';
import ErrorPage from './components/ErrorPage';
/**
* The `App` class provides a container for an application, as well as various
@@ -160,8 +159,6 @@ export default class Application {
title = '';
titleCount = 0;
initialRoute;
load(payload) {
this.data = payload;
this.translator.locale = payload.locale;
@@ -177,8 +174,6 @@ 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) {
@@ -200,9 +195,6 @@ export default class Application {
this.drawer = new Drawer();
// Needed so 404s don't get their URLs changed to the homepage.
// Won't affect extension routes since its added last.
this.routes.error = { path: '/:4xx...', component: ErrorPage };
m.route(document.getElementById('content'), basePath + '/', mapRoutes(this.routes, basePath));
// Add a class to the body which indicates that the page has been scrolled
@@ -234,8 +226,7 @@ export default class Application {
* @public
*/
preloadedApiDocument() {
// If the URL has changed, the preloaded Api document is invalid.
if (this.data.apiDocument && window.location.href === this.initialRoute) {
if (this.data.apiDocument) {
const results = this.store.pushPayload(this.data.apiDocument);
this.data.apiDocument = null;

View File

@@ -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 [selector] a jQuery-compatible selector string
* @returns the jQuery object for the DOM node
* @param {String} [selector] a jQuery-compatible selector string
* @returns {jQuery} the jQuery object for the DOM node
* @final
*/
protected $(selector: string): JQuery {
const $element = $(this.element) as JQuery<HTMLElement>;
protected $(selector) {
const $element = $(this.element);
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) as Record<string, unknown>;
const componentAttrs = Object.assign({}, attrs);
return m(this as any, componentAttrs, children);
}

View File

@@ -1,7 +1,6 @@
import * as extend from './extend';
import Session from './Session';
import Store from './Store';
import BasicEditorDriver from './utils/BasicEditorDriver';
import evented from './utils/evented';
import liveHumanTimes from './utils/liveHumanTimes';
import ItemList from './utils/ItemList';
@@ -57,8 +56,6 @@ import ModalManager from './components/ModalManager';
import Button from './components/Button';
import Modal from './components/Modal';
import GroupBadge from './components/GroupBadge';
import TextEditor from './components/TextEditor';
import TextEditorButton from './components/TextEditorButton';
import Model from './Model';
import Application from './Application';
import fullTime from './helpers/fullTime';
@@ -77,7 +74,6 @@ export default {
extend: extend,
Session: Session,
Store: Store,
'utils/BasicEditorDriver': BasicEditorDriver,
'utils/evented': evented,
'utils/liveHumanTimes': liveHumanTimes,
'utils/ItemList': ItemList,
@@ -134,8 +130,6 @@ export default {
'components/Button': Button,
'components/Modal': Modal,
'components/GroupBadge': GroupBadge,
'components/TextEditor': TextEditor,
'components/TextEditorButton': TextEditorButton,
Model: Model,
Application: Application,
'helpers/fullTime': fullTime,

View File

@@ -13,7 +13,6 @@ 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`
*
@@ -26,7 +25,6 @@ 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) {
@@ -94,13 +92,7 @@ export default class Dropdown extends Component {
*/
getButton(children) {
return (
<button
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}
data-toggle="dropdown"
onclick={this.attrs.onclick}
>
<button className={'Dropdown-toggle ' + this.attrs.buttonClassName} data-toggle="dropdown" onclick={this.attrs.onclick}>
{this.getButtonContent(children)}
</button>
);

View File

@@ -1,40 +0,0 @@
import Link from './Link';
import Page from './Page';
export default class ErrorPage extends Page {
oninit(vnode) {
super.oninit(vnode);
this.title = app.translator.trans(`core.forum.error.${this.attrs.errorCode}_title`);
}
oncreate(vnode) {
super.oncreate(vnode);
app.setTitle(this.title);
}
view() {
const links = {
404: '/',
};
const link = links[this.attrs.errorCode];
return (
<div className="ErrorPage">
<div className="container">
<h2>{this.title}</h2>
<p>{app.translator.trans(`core.forum.error.${this.attrs.errorCode}_text`)}</p>
<p>
{link && (
<Link href={link}>
{app.translator.trans(`core.forum.error.${this.attrs.errorCode}_link_text`, { forum: app.forum.attribute('title') })}
</Link>
)}
</p>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,43 @@
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('&nbsp;')}</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);
}
}

View File

@@ -1,61 +0,0 @@
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>
);
}
}

View File

@@ -24,12 +24,7 @@ export default class SplitDropdown extends Dropdown {
return [
Button.component(buttonAttrs, firstChild.children),
<button
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}
data-toggle="dropdown"
>
<button className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName} data-toggle="dropdown">
{icon(this.attrs.icon, { className: 'Button-icon' })}
{icon('fas fa-caret-down', { className: 'Button-caret' })}
</button>,

View File

@@ -1,28 +1,26 @@
import * as Mithril from 'mithril';
import User from '../models/User';
/**
* The `avatar` helper displays a user's avatar.
*
* @param user
* @param attrs Attributes to apply to the avatar element
* @param {User} user
* @param {Object} attrs Attributes to apply to the avatar element
* @return {Object}
*/
export default function avatar(user: User, attrs: Object = {}): Mithril.Vnode {
export default function avatar(user, attrs = {}) {
attrs.className = 'Avatar ' + (attrs.className || '');
let content: string = '';
let content = '';
// If the `title` attribute is set to null or false, we don't want to give the
// avatar a title. On the other hand, if it hasn't been given at all, we can
// safely default it to the user's username.
const hasTitle: boolean | string = attrs.title === 'undefined' || attrs.title;
const hasTitle = attrs.title === 'undefined' || attrs.title;
if (!hasTitle) delete attrs.title;
// If a user has been passed, then we will set up an avatar using their
// uploaded image, or the first letter of their username if they haven't
// uploaded one.
if (user) {
const username: string = user.displayName() || '?';
const avatarUrl: string = user.avatarUrl();
const username = user.displayName() || '?';
const avatarUrl = user.avatarUrl();
if (hasTitle) attrs.title = attrs.title || username;

View File

@@ -1,16 +1,15 @@
import * as Mithril from 'mithril';
import Separator from '../components/Separator';
import classList from '../utils/classList';
function isSeparator(item): boolean {
function isSeparator(item) {
return item.tag === Separator;
}
function withoutUnnecessarySeparators(items: Array<Mithril.Vnode>): Array<Mithril.Vnode> {
function withoutUnnecessarySeparators(items) {
const newItems = [];
let prevItem;
items.filter(Boolean).forEach((item: Mithril.Vnode, i: number) => {
items.filter(Boolean).forEach((item, i) => {
if (!isSeparator(item) || (prevItem && !isSeparator(prevItem) && i !== items.length - 1)) {
prevItem = item;
newItems.push(item);
@@ -23,11 +22,14 @@ function withoutUnnecessarySeparators(items: Array<Mithril.Vnode>): Array<Mithri
/**
* The `listItems` helper wraps a collection of components in <li> tags,
* stripping out any unnecessary `Separator` components.
*
* @param {*} items
* @return {Array}
*/
export default function listItems(items: Mithril.Vnode | Array<Mithril.Vnode>): Array<Mithril.Vnode> {
export default function listItems(items) {
if (!(items instanceof Array)) items = [items];
return withoutUnnecessarySeparators(items).map((item: Mithril.Vnode) => {
return withoutUnnecessarySeparators(items).map((item) => {
const isListItem = item.tag && item.tag.isListItem;
const active = item.tag && item.tag.isActive && item.tag.isActive(item.attrs);
const className = (item.attrs && item.attrs.itemClassName) || item.itemClassName;
@@ -38,7 +40,7 @@ export default function listItems(items: Mithril.Vnode | Array<Mithril.Vnode>):
item.key = item.attrs.key;
}
const node: Mithril.Vnode = isListItem ? (
const node = isListItem ? (
item
) : (
<li

View File

@@ -1,11 +1,12 @@
import * as Mithril from 'mithril';
import User from '../models/User';
import icon from './icon';
/**
* The `useronline` helper displays a green circle if the user is online
*
* @param {User} user
* @return {Object}
*/
export default function userOnline(user: User): Mithril.Vnode {
export default function userOnline(user) {
if (user.lastSeenAt() && user.isOnline()) {
return <span className="UserOnline">{icon('fas fa-circle')}</span>;
}

View File

@@ -1,11 +1,11 @@
import * as Mithril from 'mithril';
import User from '../models/User';
/**
* The `username` helper displays a user's username in a <span class="username">
* tag. If the user doesn't exist, the username will be displayed as [deleted].
*
* @param {User} user
* @return {Object}
*/
export default function username(user: User): Mithril.Vnode {
export default function username(user) {
const name = (user && user.displayName()) || app.translator.trans('core.lib.username.deleted_text');
return <span className="username">{name}</span>;

View File

@@ -1,8 +1,6 @@
// Expose jQuery, mithril and dayjs to the window browser object
import 'expose-loader?exposes[]=$&exposes[]=jQuery!jquery';
import 'expose-loader?exposes=m!mithril';
import 'expose-loader?exposes=dayjs!dayjs';
import 'expose-loader?$!expose-loader?jQuery!jquery';
import 'expose-loader?m!mithril';
import 'expose-loader?dayjs!dayjs';
import 'bootstrap/js/affix';
import 'bootstrap/js/dropdown';
import 'bootstrap/js/modal';

View File

@@ -1,5 +1,4 @@
import Mithril from 'mithril';
import ErrorPage from '../components/ErrorPage';
/**
* Generates a route resolver for a given component.
@@ -33,12 +32,6 @@ export default class DefaultResolver {
}
onmatch(args, requestedPath, route) {
const errorCode = app.data.errorCode;
if (errorCode) {
delete app.data.errorCode;
return { view: () => ErrorPage.component({ errorCode }) };
}
return this.component;
}

View File

@@ -31,24 +31,7 @@ export default class Drawer {
* @public
*/
hide() {
/**
* 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');
$('#app').removeClass('drawerOpen');
if (this.$backdrop) this.$backdrop.remove();
}

View File

@@ -0,0 +1,26 @@
/**
* 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(' ');
}

View File

@@ -1,12 +0,0 @@
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;

View File

@@ -16,7 +16,6 @@ import NotificationListState from './states/NotificationListState';
import GlobalSearchState from './states/GlobalSearchState';
import DiscussionListState from './states/DiscussionListState';
import ComposerState from './states/ComposerState';
import isSafariMobile from './utils/isSafariMobile';
export default class ForumApplication extends Application {
/**
@@ -139,12 +138,6 @@ export default class ForumApplication extends Application {
m.redraw();
}
});
if (isSafariMobile()) {
$(() => {
$('.App').addClass('mobile-safari');
});
}
}
/**

View File

@@ -1,8 +0,0 @@
import Forum from './ForumApplication';
const app = new Forum();
// @ts-ignore
window.app = app;
export default app;

View File

@@ -36,6 +36,8 @@ import HeaderSecondary from './components/HeaderSecondary';
import ComposerButton from './components/ComposerButton';
import DiscussionList from './components/DiscussionList';
import ReplyPlaceholder from './components/ReplyPlaceholder';
import TextEditor from './components/TextEditor';
import TextEditorButton from './components/TextEditorButton';
import AvatarEditor from './components/AvatarEditor';
import Post from './components/Post';
import SettingsPage from './components/SettingsPage';
@@ -71,7 +73,7 @@ import DiscussionListItem from './components/DiscussionListItem';
import LoadingPost from './components/LoadingPost';
import PostsUserPage from './components/PostsUserPage';
import DiscussionPageResolver from './resolvers/DiscussionPageResolver';
import BasicEditorDriver from '../common/utils/BasicEditorDriver';
import BasicEditorDriver from './utils/BasicEditorDriver';
import routes from './routes';
import ForumApplication from './ForumApplication';
@@ -85,6 +87,7 @@ 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,
@@ -113,6 +116,8 @@ export default Object.assign(compat, {
'components/ComposerButton': ComposerButton,
'components/DiscussionList': DiscussionList,
'components/ReplyPlaceholder': ReplyPlaceholder,
'components/TextEditor': TextEditor,
'components/TextEditorButton': TextEditorButton,
'components/AvatarEditor': AvatarEditor,
'components/Post': Post,
'components/SettingsPage': SettingsPage,

View File

@@ -265,17 +265,7 @@ export default class Composer extends Component {
this.animateHeightChange().then(() => this.focus());
if (app.screen() === 'phone') {
// On safari fixed position doesn't properly work on mobile,
// So we use absolute and set the top value.
// https://github.com/flarum/core/issues/2652
// Due to another safari bug, `scrollTop` is unreliable when
// at the very bottom of the page AND opening the composer.
// So we fallback to a calculated version of scrollTop.
// https://github.com/flarum/core/issues/2683
const scrollElement = document.documentElement;
const topOfViewport = Math.min(scrollElement.scrollTop, scrollElement.scrollHeight - scrollElement.clientHeight);
this.$().css('top', $('.App').is('.mobile-safari') ? topOfViewport : 0);
this.$().css('top', 0);
this.showBackdrop();
}
}

View File

@@ -1,7 +1,7 @@
import Component from '../../common/Component';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import ConfirmDocumentUnload from '../../common/components/ConfirmDocumentUnload';
import TextEditor from '../../common/components/TextEditor';
import TextEditor from './TextEditor';
import avatar from '../../common/helpers/avatar';
import listItems from '../../common/helpers/listItems';
import ItemList from '../../common/utils/ItemList';

View File

@@ -87,7 +87,6 @@ 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
)

View File

@@ -189,7 +189,6 @@ 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()
)

View File

@@ -237,8 +237,7 @@ export default class EditUserModal extends Modal {
}
/**
* @internal
* @protected
* @internal @protected
*/
userIsAdmin(user) {
return user.groups().some((g) => g.id() === Group.ADMINISTRATOR_ID);

View File

@@ -57,7 +57,6 @@ export default class HeaderSecondary extends Component {
SelectDropdown.component(
{
buttonClassName: 'Button Button--link',
accessibleToggleLabel: app.translator.trans('core.forum.header.locale_dropdown_accessible_label'),
},
locales
),

View File

@@ -172,7 +172,6 @@ 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()
)
@@ -228,7 +227,6 @@ 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];

View File

@@ -9,8 +9,6 @@ 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);
}

View File

@@ -61,7 +61,6 @@ 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>

View File

@@ -1,4 +1,5 @@
import Component from '../../common/Component';
import Button from '../../common/components/Button';
import icon from '../../common/helpers/icon';
import formatNumber from '../../common/utils/formatNumber';
import ScrollListener from '../../common/utils/ScrollListener';
@@ -19,6 +20,8 @@ export default class PostStreamScrubber extends Component {
this.stream = this.attrs.stream;
this.handlers = {};
this.pendingMoveIndex = null;
this.scrollListener = new ScrollListener(this.updateScrubberValues.bind(this, { fromScroll: true, forceHeightChange: true }));
}
@@ -31,8 +34,17 @@ export default class PostStreamScrubber extends Component {
count: <span className="Scrubber-count">{formatNumber(count)}</span>,
});
const index = this.stream.index;
const previousIndex = this.stream.previousIndex;
// We want to make sure the back button isn't crammed in.
// If the previous post index is less than 5% from the last/first post,
// or if the previous post index is less than 25% from the current post, we will
// hide the button. Additionally, this hides the button on very short screens.
const showBackButton = previousIndex > count / 20 && previousIndex < count - count / 20 && 100 * Math.abs((index - previousIndex) / count) > 25;
const unreadCount = this.stream.discussion.unreadCount();
const unreadPercent = count ? Math.min(count - this.stream.index, unreadCount) / count : 0;
const unreadPercent = count ? Math.min(count - index, unreadCount) / count : 0;
function styleUnread(vnode) {
const $element = $(vnode.dom);
@@ -65,6 +77,18 @@ export default class PostStreamScrubber extends Component {
</a>
<div className="Scrubber-scrollbar">
{showBackButton ? (
<a
style={'top: ' + this.percentPerPost().index * this.stream.previousIndex + '%'}
className="Scrubber-back"
onclick={this.returnToLastPosition.bind(this)}
title={app.translator.trans('core.forum.post_scrubber.back_title')}
>
{icon('fas fa-chevron-left')}
</a>
) : (
''
)}
<div className="Scrubber-before" />
<div className="Scrubber-handle">
<div className="Scrubber-bar" />
@@ -89,6 +113,16 @@ export default class PostStreamScrubber extends Component {
);
}
returnToLastPosition(e) {
// Don't fire the scrubber click event as well
e.stopPropagation();
this.stream.goToIndex(Math.floor(this.stream.previousIndex));
this.updateScrubberValues({ animate: true });
this.$().removeClass('open');
}
onupdate() {
if (this.stream.forceUpdateScrubber) {
this.stream.forceUpdateScrubber = false;
@@ -112,6 +146,7 @@ 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
@@ -123,6 +158,7 @@ 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.
@@ -155,7 +191,7 @@ export default class PostStreamScrubber extends Component {
* @param {Boolean} animate
*/
updateScrubberValues(options = {}) {
const index = this.stream.index;
const index = this.pendingMoveIndex || this.stream.index;
const count = this.stream.count();
const visible = this.stream.visible || 1;
const percentPerPost = this.percentPerPost();
@@ -246,7 +282,7 @@ export default class PostStreamScrubber extends Component {
const deltaIndex = deltaPercent / this.percentPerPost().index || 0;
const newIndex = Math.min(this.indexStart + deltaIndex, this.stream.count() - 1);
this.stream.index = Math.max(0, newIndex);
this.pendingMoveIndex = Math.max(0, newIndex);
this.updateScrubberValues();
}
@@ -263,15 +299,17 @@ export default class PostStreamScrubber extends Component {
// If the index we've landed on is in a gap, then tell the stream-
// content that we want to load those posts.
const intIndex = Math.floor(this.stream.index);
const intIndex = Math.floor(this.pendingMoveIndex);
this.stream.goToIndex(intIndex);
this.pendingMoveIndex = null;
}
onclick(e) {
// Calculate the index which we want to jump to based on the click position.
// 1. Get the offset of the click from the top of the scrollbar, as a
// percentage of the scrollbar's height.
// percentage of the scrollbar's height. Save current location for the
// back button.
const $scrollbar = this.$('.Scrubber-scrollbar');
const offsetPixels = (e.pageY || e.originalEvent.touches[0].pageY) - $scrollbar.offset().top + $('body').scrollTop();
let offsetPercent = (offsetPixels / $scrollbar.outerHeight()) * 100;

View File

@@ -121,7 +121,7 @@ export default class PostsUserPage extends UserPage {
loadResults(offset) {
return app.store.find('posts', {
filter: {
author: this.user.username(),
user: this.user.id(),
type: 'comment',
},
page: { offset, limit: this.loadLimit },

View File

@@ -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

View File

@@ -71,11 +71,8 @@ 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({
@@ -88,10 +85,9 @@ export default class Search extends Component {
>
<div className="Search-input">
<input
aria-label={searchLabel}
className="FormControl"
type="search"
placeholder={searchLabel}
placeholder={extractText(app.translator.trans('core.forum.header.search_placeholder'))}
value={this.state.getValue()}
oninput={(e) => this.state.setValue(e.target.value)}
onfocus={() => (this.hasFocus = true)}
@@ -117,16 +113,6 @@ export default class Search extends Component {
onupdate() {
// Highlight the item that is currently selected.
this.setIndex(this.getCurrentNumericIndex());
// If there are no sources, the search view is not shown.
if (!this.sources.length) return;
// Since extensions might add elements above the search box on mobile,
// we need to calculate and set the max height dynamically.
const resultsElementMargin = 14;
const maxHeight =
window.innerHeight - this.element.querySelector('.Search-input>.FormControl').getBoundingClientRect().bottom - resultsElementMargin;
this.element.querySelector('.Search-results').style['max-height'] = `${maxHeight}px`;
}
oncreate(vnode) {

View File

@@ -17,8 +17,6 @@ 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) {

View File

@@ -1,7 +1,7 @@
import Component from '../Component';
import ItemList from '../utils/ItemList';
import listItems from '../helpers/listItems';
import Button from './Button';
import Component from '../../common/Component';
import ItemList from '../../common/utils/ItemList';
import listItems from '../../common/helpers/listItems';
import Button from '../../common/components/Button';
import BasicEditorDriver from '../utils/BasicEditorDriver';

View File

@@ -1,4 +1,4 @@
import Button from './Button';
import Button from '../../common/components/Button';
/**
* The `TextEditorButton` component displays a button suitable for the text

View File

@@ -40,7 +40,6 @@ 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

View File

@@ -1,8 +1,12 @@
// Expose punycode and ColorThief to the window browser object
import 'expose-loader?exposes=punycode!punycode';
import 'expose-loader?exposes=ColorThief!color-thief-browser';
import 'expose-loader?punycode!punycode';
import 'expose-loader?ColorThief!color-thief-browser';
import app from './app';
import ForumApplication from './ForumApplication';
const app = new ForumApplication();
// Backwards compatibility
window.app = app;
export { app };

View File

@@ -1,7 +1,7 @@
import subclassOf from '../../common/utils/subclassOf';
import Stream from '../../common/utils/Stream';
import ReplyComposer from '../components/ReplyComposer';
import EditorDriverInterface from '../../common/utils/EditorDriverInterface';
import EditorDriverInterface from '../utils/EditorDriverInterface';
class ComposerState {
constructor() {

View File

@@ -22,6 +22,7 @@ class PostStreamState {
this.pagesLoading = 0;
this.index = 0;
this.previousIndex = 0;
this.number = 1;
/**
@@ -137,6 +138,7 @@ class PostStreamState {
this.needsScroll = true;
this.targetPost = { index };
this.animateScroll = !noAnimation;
this.previousIndex = index === this.previousIndex ? index : this.index;
this.index = index;
m.redraw();

View File

@@ -1,13 +0,0 @@
/**
* @see https://stackoverflow.com/a/31732310
*/
export default function isSafariMobile(): boolean {
return (
'ontouchstart' in window &&
navigator.vendor &&
navigator.vendor.includes('Apple') &&
navigator.userAgent &&
!navigator.userAgent.includes('CriOS') &&
!navigator.userAgent.includes('FxiOS')
);
}

View File

@@ -1,26 +1,15 @@
const config = require('flarum-webpack-config');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const merge = require('webpack-merge');
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,
});
module.exports['module'].rules[0].test = /\.(tsx?|js)$/;

View File

@@ -123,9 +123,6 @@
// 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;
}

View File

@@ -15,9 +15,13 @@
float: left;
margin-left: -65px;
margin-top: -4px;
.LoadingIndicator {
display: inline-block;
margin-left: 20px;
}
}
}
.Checkbox--switch .Checkbox-display {
width: 50px;
height: 28px;
@@ -27,28 +31,8 @@
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 {

View File

@@ -2,58 +2,13 @@
// Loading Indicators
.LoadingIndicator {
@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;
}
}
position: relative;
color: @muted-color;
}
@keyframes spin {
from {
transform: rotate(0);
}
to {
transform: rotate(1turn);
}
.LoadingIndicator--inline {
display: inline-block;
width: 25px;
}
.LoadingIndicator--block {
height: 100px;
}

View File

@@ -15,6 +15,7 @@
}
}
.Search-results {
max-height: 70vh;
overflow: auto;
left: auto;
right: 0;

View File

@@ -1,49 +0,0 @@
.TextEditor .TextEditor-editor {
border-radius: 0;
padding: 0 0 10px;
border: 0;
resize: none;
color: @text-color;
font-size: 14px;
line-height: 1.7;
&, &:focus, &[disabled] {
background: none;
border: 0;
}
@media @phone {
font-size: 16px; // minimum font-size required to prevent page zoom on focus in iOS 10
}
}
.TextEditor-controls {
margin: 0;
padding: 10px 0;
list-style-type: none;
overflow-x: auto;
white-space: nowrap;
li {
display: inline-block;
margin-right: 10px;
}
}
.TextEditor-toolbar {
.Button--icon {
width: 28px;
}
}
@media @tablet-up {
.TextEditor-controls {
margin: 0 -20px 0 -105px;
padding: 10px 20px;
border-top: 1px solid @control-bg;
.fullScreen & {
margin: 0;
border-top: 0;
padding: 20px 0;
}
}
}

View File

@@ -25,6 +25,5 @@
@import "Placeholder";
@import "Search";
@import "Select";
@import "TextEditor";
@import "Tooltip";
@import "ValidationError";

View File

@@ -1,5 +1,5 @@
.header-background() {
background: @header-bg;
background: fade(@header-bg, 98%);
position: fixed;
top: 0;
left: 0;

View File

@@ -1,151 +1,227 @@
// 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) {
animation: @animation;
-webkit-animation: @animation;
-o-animation: @animation;
animation: @animation;
}
.animation-name(@name) {
animation-name: @name;
-webkit-animation-name: @name;
animation-name: @name;
}
.animation-duration(@duration) {
animation-duration: @duration;
-webkit-animation-duration: @duration;
animation-duration: @duration;
}
.animation-timing-function(@timing-function) {
animation-timing-function: @timing-function;
-webkit-animation-timing-function: @timing-function;
animation-timing-function: @timing-function;
}
.animation-delay(@delay) {
animation-delay: @delay;
-webkit-animation-delay: @delay;
animation-delay: @delay;
}
.animation-iteration-count(@iteration-count) {
animation-iteration-count: @iteration-count;
-webkit-animation-iteration-count: @iteration-count;
animation-iteration-count: @iteration-count;
}
.animation-direction(@direction) {
animation-direction: @direction;
-webkit-animation-direction: @direction;
animation-direction: @direction;
}
.animation-fill-mode(@fill-mode) {
animation-fill-mode: @fill-mode;
-webkit-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) {
// Safari
.backface-visibility(@visibility){
-webkit-backface-visibility: @visibility;
backface-visibility: @visibility;
-moz-backface-visibility: @visibility;
backface-visibility: @visibility;
}
// Drop shadows
// These remain for backwards compatibility with existing styles.
//
// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
// supported browsers that have box shadow capabilities now support it.
.box-shadow(@shadow) {
box-shadow: @shadow;
-webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
box-shadow: @shadow;
}
// Box sizing
// These remain for backwards compatibility with existing styles.
.box-sizing(@boxmodel) {
box-sizing: @boxmodel;
-webkit-box-sizing: @boxmodel;
-moz-box-sizing: @boxmodel;
box-sizing: @boxmodel;
}
// CSS3 Content Columns
.content-columns(@column-count; @column-gap: @grid-gutter-width) {
// Safari
-webkit-column-count: @column-count;
column-count: @column-count;
// Safari
-moz-column-count: @column-count;
column-count: @column-count;
-webkit-column-gap: @column-gap;
column-gap: @column-gap;
-moz-column-gap: @column-gap;
column-gap: @column-gap;
}
// Optional hyphenation
.hyphens(@mode: auto) {
word-wrap: break-word;
// Safari
-webkit-hyphens: @mode;
hyphens: @mode;
-moz-hyphens: @mode;
-ms-hyphens: @mode; // IE10+
-o-hyphens: @mode;
hyphens: @mode;
}
// Placeholder text
.placeholder(@color) {
// Safari
&::-webkit-input-placeholder,
&::placeholder {
// Firefox
&::-moz-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) {
transform: scale(@ratio);
-webkit-transform: scale(@ratio);
-ms-transform: scale(@ratio); // IE9 only
-o-transform: scale(@ratio);
transform: scale(@ratio);
}
.scale(@ratioX; @ratioY) {
transform: scale(@ratioX, @ratioY);
-webkit-transform: scale(@ratioX, @ratioY);
-ms-transform: scale(@ratioX, @ratioY); // IE9 only
-o-transform: scale(@ratioX, @ratioY);
transform: scale(@ratioX, @ratioY);
}
.scaleX(@ratio) {
transform: scaleX(@ratio);
-webkit-transform: scaleX(@ratio);
-ms-transform: scaleX(@ratio); // IE9 only
-o-transform: scaleX(@ratio);
transform: scaleX(@ratio);
}
.scaleY(@ratio) {
transform: scaleY(@ratio);
-webkit-transform: scaleY(@ratio);
-ms-transform: scaleY(@ratio); // IE9 only
-o-transform: scaleY(@ratio);
transform: scaleY(@ratio);
}
.skew(@x; @y) {
transform: skewX(@x) skewY(@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);
}
.translate(@x; @y) {
transform: translate(@x, @y);
-webkit-transform: translate(@x, @y);
-ms-transform: translate(@x, @y); // IE9 only
-o-transform: translate(@x, @y);
transform: translate(@x, @y);
}
.translate3d(@x; @y; @z) {
transform: translate3d(@x, @y, @z);
-webkit-transform: translate3d(@x, @y, @z);
transform: translate3d(@x, @y, @z);
}
.rotate(@degrees) {
transform: rotate(@degrees);
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees); // IE9 only
-o-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.rotateX(@degrees) {
transform: rotateX(@degrees);
-webkit-transform: rotateX(@degrees);
-ms-transform: rotateX(@degrees); // IE9 only
-o-transform: rotateX(@degrees);
transform: rotateX(@degrees);
}
.rotateY(@degrees) {
transform: rotateY(@degrees);
-webkit-transform: rotateY(@degrees);
-ms-transform: rotateY(@degrees); // IE9 only
-o-transform: rotateY(@degrees);
transform: rotateY(@degrees);
}
.perspective(@perspective) {
perspective: @perspective;
-webkit-perspective: @perspective;
-moz-perspective: @perspective;
perspective: @perspective;
}
.perspective-origin(@perspective) {
perspective-origin: @perspective;
-webkit-perspective-origin: @perspective;
-moz-perspective-origin: @perspective;
perspective-origin: @perspective;
}
.transform-origin(@origin) {
transform-origin: @origin;
-webkit-transform-origin: @origin;
-moz-transform-origin: @origin;
-ms-transform-origin: @origin; // IE9 only
transform-origin: @origin;
}
// Transitions
// These remain for backwards compatibility with existing styles.
.transition(@transition) {
transition: @transition;
-webkit-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
.transition-property(@transition-property) {
transition-property: @transition-property;
-webkit-transition-property: @transition-property;
transition-property: @transition-property;
}
.transition-delay(@transition-delay) {
transition-delay: @transition-delay;
-webkit-transition-delay: @transition-delay;
transition-delay: @transition-delay;
}
.transition-duration(@transition-duration) {
transition-duration: @transition-duration;
-webkit-transition-duration: @transition-duration;
transition-duration: @transition-duration;
}
.transition-timing-function(@timing-function) {
transition-timing-function: @timing-function;
-webkit-transition-timing-function: @timing-function;
transition-timing-function: @timing-function;
}
.transition-transform(@transition) {
transition: transform @transition;
-webkit-transition: -webkit-transform @transition;
-moz-transition: -moz-transform @transition;
-o-transition: -o-transform @transition;
transition: transform @transition;
}
// User select
// For selecting text on the page
.user-select(@select) {
// Safari + MS Edge
-webkit-user-select: @select;
user-select: @select;
-moz-user-select: @select;
-ms-user-select: @select; // IE10+
user-select: @select;
}

View File

@@ -2,7 +2,7 @@
text-align: center;
margin-top: 10px;
.LoadingIndicator-container {
.LoadingIndicator {
height: 46px;
}
}

View File

@@ -120,12 +120,6 @@
max-height: 100%;
padding-top: @header-height-phone;
// Fixes a bug where fixed position doesn't properly work in Safari mobile
// https://github.com/flarum/core/issues/2652
.mobile-safari & {
position: absolute;
}
&:before {
content: " ";
.header-background();
@@ -325,3 +319,56 @@
left: @pane-width;
}
}
// ------------------------------------
// Text Editor
.TextEditor .TextEditor-editor {
border-radius: 0;
padding: 0 0 10px;
border: 0;
resize: none;
color: @text-color;
font-size: 14px;
line-height: 1.7;
&, &:focus, &[disabled] {
background: none;
border: 0;
}
@media @phone {
font-size: 16px; // minimum font-size required to prevent page zoom on focus in iOS 10
}
}
.TextEditor-controls {
margin: 0;
padding: 10px 0;
list-style-type: none;
overflow-x: auto;
white-space: nowrap;
li {
display: inline-block;
margin-right: 10px;
}
}
.TextEditor-toolbar {
.Button--icon {
width: 28px;
}
}
@media @tablet-up {
.TextEditor-controls {
margin: 0 -20px 0 -105px;
padding: 10px 20px;
border-top: 1px solid @control-bg;
.fullScreen & {
margin: 0;
border-top: 0;
padding: 20px 0;
}
}
}

View File

@@ -10,10 +10,9 @@
.DiscussionList-loadMore {
text-align: center;
margin-top: 10px;
.LoadingIndicator-container {
height: 46px;
}
}
.DiscussionList-loadMore .LoadingIndicator {
height: 46px;
}
@media @phone {

View File

@@ -18,12 +18,16 @@
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;
}
.Scrubber-back {
position: absolute;
left: 12.5px;
z-index: 1;
color: @muted-color;
}
.Scrubber-unread {
position: absolute;
border-left: 1px solid lighten(@muted-color, 10%);
@@ -44,7 +48,6 @@
background: transparent;
width: 100%;
padding: 5px 0;
cursor: move;
}
.Scrubber-bar {
height: 100%;

View File

@@ -278,7 +278,6 @@ 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:
@@ -303,15 +302,6 @@ core:
username_heading: => core.ref.username
username_label: => core.ref.username
error:
401_text: You do not have permission to access this page. Try again after logging in.
403_text: You do not have permission to access this page.
404_text: The page you requested could not be found.
401_title: Not Authenticated
403_title: Permission Denied
404_title: Not Found
404_link_text: "Return to {forum}"
# These translations are used in the Forgot Password modal dialog.
forgot_password:
dismiss_button: => core.ref.okay
@@ -326,12 +316,10 @@ 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
@@ -344,7 +332,6 @@ 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:
@@ -352,7 +339,6 @@ 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.
@@ -373,7 +359,6 @@ 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.
@@ -390,7 +375,6 @@ 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:
@@ -464,7 +448,6 @@ 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:
@@ -479,10 +462,6 @@ 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}"
@@ -526,6 +505,10 @@ core:
csrf_token_mismatch: You have been inactive for too long.
csrf_token_mismatch_return_link: Go back, to try again
invalid_confirmation_token: This confirmation link has already been used or is invalid.
not_authenticated: You do not have permission to access this page. Try again after logging in.
not_found: The page you requested could not be found.
not_found_return_link: "Return to {forum}"
permission_denied: You do not have permission to access this page.
unknown: An error occurred while trying to load this page.
# Translations in this namespace are displayed by the basic HTML discussion index.

View File

@@ -30,6 +30,8 @@ return [
$table->integer('hide_user_id')->unsigned()->nullable();
$table->unique(['discussion_id', 'number']);
$table->engine = 'MyISAM';
});
$connection = $schema->getConnection();

View File

@@ -1,43 +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.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->string('type', 100)->index();
});
// Since all active sessions will stop working on update due to switching from user_id to access_token
// We can do things simple here by terminating all tokens that have the previously default lifetime
$schema->getConnection()->table('access_tokens')
->where('lifetime_seconds', 3600)
->delete();
// We will then assume that all remaining tokens are remember tokens
// This will include tokens that previously had a custom lifetime
$schema->getConnection()->table('access_tokens')
->update([
'type' => 'session_remember',
]);
$schema->table('access_tokens', function (Blueprint $table) {
$table->dropColumn('lifetime_seconds');
});
},
'down' => function (Builder $schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->dropColumn('type');
$table->integer('lifetime_seconds');
});
}
];

View File

@@ -1,35 +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.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('access_tokens', function (Blueprint $table) {
// Replace primary key with unique index so we can create a new primary
$table->dropPrimary('token');
$table->unique('token');
});
// This needs to be done in a second statement because of the order Laravel runs operations in
$schema->table('access_tokens', function (Blueprint $table) {
// Introduce new increment-based ID
$table->increments('id')->first();
});
},
'down' => function (Builder $schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->dropColumn('id');
$table->dropIndex('token');
$table->primary('token');
});
}
];

View File

@@ -1,21 +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.
*/
use Flarum\Database\Migration;
return Migration::addColumns('access_tokens', [
'title' => ['string', 'length' => 150, 'nullable' => true],
// Accommodates both IPv4 and IPv6 as strings
'last_ip_address' => ['string', 'length' => 45, 'nullable' => true],
// Technically, there's no limit to a user agent length
// Most are around 150 in length, and the general recommendation seems to be below 200
// We're going to use the longest string possible to be safe
// There will still be exceptions, we'll just truncate them
'last_user_agent' => ['string', 'length' => 255, 'nullable' => true],
]);

View File

@@ -12,7 +12,6 @@ namespace Flarum\Admin;
use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\ErrorHandling\FrontendFormatter;
use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\ErrorHandling\ViewFormatter;
@@ -37,20 +36,19 @@ class AdminServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url) {
return $url->addCollection('admin', $this->container->make('flarum.admin.routes'), 'admin');
$this->app->extend(UrlGenerator::class, function (UrlGenerator $url) {
return $url->addCollection('admin', $this->app->make('flarum.admin.routes'), 'admin');
});
$this->container->singleton('flarum.admin.routes', function () {
$this->app->singleton('flarum.admin.routes', function () {
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->container->singleton('flarum.admin.middleware', function () {
$this->app->singleton('flarum.admin.middleware', function () {
return [
HttpMiddleware\InjectActorReference::class,
'flarum.admin.error_handler',
HttpMiddleware\ParseJsonBody::class,
HttpMiddleware\StartSession::class,
@@ -63,24 +61,23 @@ class AdminServiceProvider extends AbstractServiceProvider
];
});
$this->container->bind('flarum.admin.error_handler', function () {
$this->app->bind('flarum.admin.error_handler', function () {
return new HttpMiddleware\HandleErrors(
$this->container->make(Registry::class),
$this->container['flarum.config']->inDebugMode() ? $this->container->make(WhoopsFormatter::class) : $this->container->make(ViewFormatter::class),
$this->container->make(FrontendFormatter::class),
$this->container->tagged(Reporter::class)
$this->app->make(Registry::class),
$this->app['flarum.config']->inDebugMode() ? $this->app->make(WhoopsFormatter::class) : $this->app->make(ViewFormatter::class),
$this->app->tagged(Reporter::class)
);
});
$this->container->bind('flarum.admin.route_resolver', function () {
return new HttpMiddleware\ResolveRoute($this->container->make('flarum.admin.routes'));
$this->app->bind('flarum.admin.route_resolver', function () {
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.admin.routes'));
});
$this->container->singleton('flarum.admin.handler', function () {
$this->app->singleton('flarum.admin.handler', function () {
$pipe = new MiddlewarePipe;
foreach ($this->container->make('flarum.admin.middleware') as $middleware) {
$pipe->pipe($this->container->make($middleware));
foreach ($this->app->make('flarum.admin.middleware') as $middleware) {
$pipe->pipe($this->app->make($middleware));
}
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
@@ -88,9 +85,9 @@ class AdminServiceProvider extends AbstractServiceProvider
return $pipe;
});
$this->container->bind('flarum.assets.admin', function () {
$this->app->bind('flarum.assets.admin', function () {
/** @var \Flarum\Frontend\Assets $assets */
$assets = $this->container->make('flarum.assets.factory')('admin');
$assets = $this->app->make('flarum.assets.factory')('admin');
$assets->js(function (SourceCollector $sources) {
$sources->addFile(__DIR__.'/../../js/dist/admin.js');
@@ -100,17 +97,17 @@ class AdminServiceProvider extends AbstractServiceProvider
$sources->addFile(__DIR__.'/../../less/admin.less');
});
$this->container->make(AddTranslations::class)->forFrontend('admin')->to($assets);
$this->container->make(AddLocaleAssets::class)->to($assets);
$this->app->make(AddTranslations::class)->forFrontend('admin')->to($assets);
$this->app->make(AddLocaleAssets::class)->to($assets);
return $assets;
});
$this->container->bind('flarum.frontend.admin', function () {
$this->app->bind('flarum.frontend.admin', function () {
/** @var \Flarum\Frontend\Frontend $frontend */
$frontend = $this->container->make('flarum.frontend.factory')('admin');
$frontend = $this->app->make('flarum.frontend.factory')('admin');
$frontend->content($this->container->make(Content\AdminPayload::class));
$frontend->content($this->app->make(Content\AdminPayload::class));
return $frontend;
});
@@ -123,14 +120,14 @@ class AdminServiceProvider extends AbstractServiceProvider
{
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin');
$events = $this->container->make('events');
$events = $this->app->make('events');
$events->listen(
[Enabled::class, Disabled::class, ClearingCache::class],
function () {
$recompile = new RecompileFrontendAssets(
$this->container->make('flarum.assets.admin'),
$this->container->make(LocaleManager::class)
$this->app->make('flarum.assets.admin'),
$this->app->make(LocaleManager::class)
);
$recompile->flush();
}
@@ -140,8 +137,8 @@ class AdminServiceProvider extends AbstractServiceProvider
Saved::class,
function (Saved $event) {
$recompile = new RecompileFrontendAssets(
$this->container->make('flarum.assets.admin'),
$this->container->make(LocaleManager::class)
$this->app->make('flarum.assets.admin'),
$this->app->make(LocaleManager::class)
);
$recompile->whenSettingsSaved($event);
}
@@ -153,7 +150,7 @@ class AdminServiceProvider extends AbstractServiceProvider
*/
protected function populateRoutes(RouteCollection $routes)
{
$factory = $this->container->make(RouteHandlerFactory::class);
$factory = $this->app->make(RouteHandlerFactory::class);
$callback = include __DIR__.'/routes.php';
$callback($routes, $factory);

View File

@@ -9,7 +9,6 @@
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;
@@ -19,7 +18,7 @@ class RequireAdministrateAbility implements Middleware
{
public function process(Request $request, Handler $handler): Response
{
RequestUtil::getActor($request)->assertAdmin();
$request->getAttribute('actor')->assertAdmin();
return $handler->handle($request);
}

View File

@@ -30,18 +30,18 @@ class ApiServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url) {
return $url->addCollection('api', $this->container->make('flarum.api.routes'), 'api');
$this->app->extend(UrlGenerator::class, function (UrlGenerator $url) {
return $url->addCollection('api', $this->app->make('flarum.api.routes'), 'api');
});
$this->container->singleton('flarum.api.routes', function () {
$this->app->singleton('flarum.api.routes', function () {
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->container->singleton('flarum.api.throttlers', function () {
$this->app->singleton('flarum.api.throttlers', function () {
return [
'bypassThrottlingAttribute' => function ($request) {
if ($request->getAttribute('bypassThrottling')) {
@@ -51,13 +51,12 @@ class ApiServiceProvider extends AbstractServiceProvider
];
});
$this->container->bind(Middleware\ThrottleApi::class, function ($container) {
return new Middleware\ThrottleApi($container->make('flarum.api.throttlers'));
$this->app->bind(Middleware\ThrottleApi::class, function ($app) {
return new Middleware\ThrottleApi($app->make('flarum.api.throttlers'));
});
$this->container->singleton('flarum.api.middleware', function () {
$this->app->singleton('flarum.api.middleware', function () {
return [
HttpMiddleware\InjectActorReference::class,
'flarum.api.error_handler',
HttpMiddleware\ParseJsonBody::class,
Middleware\FakeHttpMethods::class,
@@ -72,26 +71,23 @@ class ApiServiceProvider extends AbstractServiceProvider
];
});
$this->container->bind('flarum.api.error_handler', function () {
$jsonFormatter = new JsonApiFormatter($this->container['flarum.config']->inDebugMode());
$this->app->bind('flarum.api.error_handler', function () {
return new HttpMiddleware\HandleErrors(
$this->container->make(Registry::class),
$jsonFormatter,
$jsonFormatter, // This won't be used.
$this->container->tagged(Reporter::class)
$this->app->make(Registry::class),
new JsonApiFormatter($this->app['flarum.config']->inDebugMode()),
$this->app->tagged(Reporter::class)
);
});
$this->container->bind('flarum.api.route_resolver', function () {
return new HttpMiddleware\ResolveRoute($this->container->make('flarum.api.routes'));
$this->app->bind('flarum.api.route_resolver', function () {
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.api.routes'));
});
$this->container->singleton('flarum.api.handler', function () {
$this->app->singleton('flarum.api.handler', function () {
$pipe = new MiddlewarePipe;
foreach ($this->container->make('flarum.api.middleware') as $middleware) {
$pipe->pipe($this->container->make($middleware));
foreach ($this->app->make('flarum.api.middleware') as $middleware) {
$pipe->pipe($this->app->make($middleware));
}
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
@@ -99,7 +95,7 @@ class ApiServiceProvider extends AbstractServiceProvider
return $pipe;
});
$this->container->singleton('flarum.api.notification_serializers', function () {
$this->app->singleton('flarum.api.notification_serializers', function () {
return [
'discussionRenamed' => BasicDiscussionSerializer::class
];
@@ -113,9 +109,9 @@ class ApiServiceProvider extends AbstractServiceProvider
{
$this->setNotificationSerializers();
AbstractSerializeController::setContainer($this->container);
AbstractSerializeController::setContainer($this->app);
AbstractSerializer::setContainer($this->container);
AbstractSerializer::setContainer($this->app);
}
/**
@@ -123,7 +119,7 @@ class ApiServiceProvider extends AbstractServiceProvider
*/
protected function setNotificationSerializers()
{
$serializers = $this->container->make('flarum.api.notification_serializers');
$serializers = $this->app->make('flarum.api.notification_serializers');
foreach ($serializers as $type => $serializer) {
NotificationSerializer::setSubjectSerializer($type, $serializer);
@@ -137,7 +133,7 @@ class ApiServiceProvider extends AbstractServiceProvider
*/
protected function populateRoutes(RouteCollection $routes)
{
$factory = $this->container->make(RouteHandlerFactory::class);
$factory = $this->app->make(RouteHandlerFactory::class);
$callback = include __DIR__.'/routes.php';
$callback($routes, $factory);

View File

@@ -12,7 +12,6 @@ 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;
@@ -57,7 +56,7 @@ class Client
{
$request = ServerRequestFactory::fromGlobals(null, $queryParams, $body);
$request = RequestUtil::withActor($request, $actor);
$request = $request->withAttribute('actor', $actor);
if (is_string($controller)) {
$controller = $this->container->make($controller);

View File

@@ -11,9 +11,6 @@ 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;
@@ -87,11 +84,6 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
*/
protected static $beforeSerializationCallbacks = [];
/**
* @var array
*/
protected static $loadRelations = [];
/**
* {@inheritdoc}
*/
@@ -147,47 +139,6 @@ 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
@@ -256,11 +207,6 @@ 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.
*
@@ -402,13 +348,4 @@ 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);
}
}

View File

@@ -10,7 +10,6 @@
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;
@@ -36,7 +35,7 @@ class ClearCacheController extends AbstractDeleteController
*/
protected function delete(ServerRequestInterface $request)
{
RequestUtil::getActor($request)->assertAdmin();
$request->getAttribute('actor')->assertAdmin();
$this->command->run(
new ArrayInput([]),

View File

@@ -12,7 +12,6 @@ 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;
@@ -54,7 +53,7 @@ class CreateDiscussionController extends AbstractCreateController
*/
protected function data(ServerRequestInterface $request, Document $document)
{
$actor = RequestUtil::getActor($request);
$actor = $request->getAttribute('actor');
$ipAddress = $request->getAttribute('ipAddress');
$discussion = $this->bus->dispatch(

View File

@@ -11,7 +11,6 @@ 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;
@@ -43,7 +42,7 @@ class CreateGroupController extends AbstractCreateController
protected function data(ServerRequestInterface $request, Document $document)
{
return $this->bus->dispatch(
new CreateGroup(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', []))
new CreateGroup($request->getAttribute('actor'), Arr::get($request->getParsedBody(), 'data', []))
);
}
}

View File

@@ -11,7 +11,6 @@ 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;
@@ -53,7 +52,7 @@ class CreatePostController extends AbstractCreateController
*/
protected function data(ServerRequestInterface $request, Document $document)
{
$actor = RequestUtil::getActor($request);
$actor = $request->getAttribute('actor');
$data = Arr::get($request->getParsedBody(), 'data', []);
$discussionId = Arr::get($data, 'relationships.discussion.data.id');
$ipAddress = $request->getAttribute('ipAddress');

View File

@@ -9,8 +9,7 @@
namespace Flarum\Api\Controller;
use Flarum\Http\RememberAccessToken;
use Flarum\Http\SessionAccessToken;
use Flarum\Http\AccessToken;
use Flarum\User\Exception\NotAuthenticatedException;
use Flarum\User\UserRepository;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
@@ -59,6 +58,7 @@ 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);
@@ -66,14 +66,8 @@ class CreateTokenController implements RequestHandlerInterface
throw new NotAuthenticatedException;
}
if (Arr::get($body, 'remember')) {
$token = RememberAccessToken::generate($user->id);
} else {
$token = SessionAccessToken::generate($user->id);
}
// We do a first update here to log the IP/agent of the token creator, even if the token is never used afterwards
$token->touch($request);
$token = AccessToken::generate($user->id, $lifetime);
$token->save();
return new JsonResponse([
'token' => $token->token,

View File

@@ -10,7 +10,6 @@
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;
@@ -43,7 +42,7 @@ class CreateUserController extends AbstractCreateController
protected function data(ServerRequestInterface $request, Document $document)
{
return $this->bus->dispatch(
new RegisterUser(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', []))
new RegisterUser($request->getAttribute('actor'), Arr::get($request->getParsedBody(), 'data', []))
);
}
}

View File

@@ -10,7 +10,6 @@
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;
@@ -43,7 +42,7 @@ class DeleteAvatarController extends AbstractShowController
protected function data(ServerRequestInterface $request, Document $document)
{
return $this->bus->dispatch(
new DeleteAvatar(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
new DeleteAvatar(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
);
}
}

View File

@@ -10,7 +10,6 @@
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;
@@ -36,7 +35,7 @@ class DeleteDiscussionController extends AbstractDeleteController
protected function delete(ServerRequestInterface $request)
{
$id = Arr::get($request->getQueryParams(), 'id');
$actor = RequestUtil::getActor($request);
$actor = $request->getAttribute('actor');
$input = $request->getParsedBody();
$this->bus->dispatch(

View File

@@ -9,7 +9,6 @@
namespace Flarum\Api\Controller;
use Flarum\Http\RequestUtil;
use Flarum\Settings\SettingsRepositoryInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use League\Flysystem\FilesystemInterface;
@@ -42,7 +41,7 @@ class DeleteFaviconController extends AbstractDeleteController
*/
protected function delete(ServerRequestInterface $request)
{
RequestUtil::getActor($request)->assertAdmin();
$request->getAttribute('actor')->assertAdmin();
$path = $this->settings->get('favicon_path');

View File

@@ -10,7 +10,6 @@
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;
@@ -36,7 +35,7 @@ class DeleteGroupController extends AbstractDeleteController
protected function delete(ServerRequestInterface $request)
{
$this->bus->dispatch(
new DeleteGroup(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
new DeleteGroup(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
);
}
}

View File

@@ -9,7 +9,6 @@
namespace Flarum\Api\Controller;
use Flarum\Http\RequestUtil;
use Flarum\Settings\SettingsRepositoryInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use League\Flysystem\FilesystemInterface;
@@ -42,7 +41,7 @@ class DeleteLogoController extends AbstractDeleteController
*/
protected function delete(ServerRequestInterface $request)
{
RequestUtil::getActor($request)->assertAdmin();
$request->getAttribute('actor')->assertAdmin();
$path = $this->settings->get('logo_path');

View File

@@ -9,7 +9,6 @@
namespace Flarum\Api\Controller;
use Flarum\Http\RequestUtil;
use Flarum\Post\Command\DeletePost;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Support\Arr;
@@ -36,7 +35,7 @@ class DeletePostController extends AbstractDeleteController
protected function delete(ServerRequestInterface $request)
{
$this->bus->dispatch(
new DeletePost(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
new DeletePost(Arr::get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
);
}
}

Some files were not shown because too many files have changed in this diff Show More