1
0
mirror of https://github.com/flarum/core.git synced 2025-08-18 14:22:02 +02:00

Compare commits

..

17 Commits

Author SHA1 Message Date
luceos
68cacd5404 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-11-30 23:15:36 +00:00
Matthew Kilgore
345a8e5773 Update UrlGenerator 2020-11-30 18:15:19 -05:00
luceos
1cbaef579c Apply fixes from StyleCI
[ci skip] [skip ci]
2020-11-30 23:03:42 +00:00
Matthew Kilgore
8fb65e9de7 Use Repositories instead of models 2020-11-30 18:03:25 -05:00
Matthew Kilgore
92a5b98b77 Fix some review issues 2020-11-26 20:45:09 -05:00
Matthew Kilgore
1eb316bf44 Make min search length a constant 2020-11-26 08:09:34 -05:00
luceos
47e8990813 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-11-26 08:09:34 -05:00
Matthew Kilgore
dbce6705fd Made frontend discussion slug independent 2020-11-26 08:09:34 -05:00
Matthew Kilgore
9de6ebbe4f Working Admin Page 2020-11-26 08:09:34 -05:00
luceos
f63ca2be3c Apply fixes from StyleCI
[ci skip] [skip ci]
2020-11-26 08:09:34 -05:00
Matthew Kilgore
6a1017d4b1 Slug Extender Test works 2020-11-26 08:09:34 -05:00
luceos
696dc8ef6b Apply fixes from StyleCI
[ci skip] [skip ci]
2020-11-26 08:09:34 -05:00
Matthew Kilgore
a8fa57ad30 WIP Extender 2020-11-26 08:09:34 -05:00
Alexander Skvortsov
9ff02f8822 Format 2020-11-26 08:09:34 -05:00
Alexander Skvortsov
dde718e27f Fix user ShowTests 2020-11-26 08:09:34 -05:00
luceos
a155e41432 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-11-26 08:09:34 -05:00
Matthew Kilgore
4574fa6290 Initial Driver Support (No Extender) 2020-11-26 08:09:34 -05:00
531 changed files with 11231 additions and 22127 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,41 +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: Install JS dependencies
run: npm ci
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]
service: ['mysql:5.7', mariadb]
prefix: ['', flarum_]
@@ -21,16 +21,16 @@ jobs:
prefixStr: (prefix)
exclude:
- php: 7.3
- php: 7.2
service: 'mysql:5.7'
prefix: flarum_
- php: 7.3
- php: 7.2
service: mariadb
prefix: flarum_
- php: 8.0
- php: 7.3
service: 'mysql:5.7'
prefix: flarum_
- php: 8.0
- php: 7.3
service: mariadb
prefix: flarum_
@@ -43,25 +43,15 @@ 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
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
tools: phpunit, composer:v2
- name: Select PHP version
run: sudo update-alternatives --set php $(which php${{ matrix.php }})
# The authentication alter is necessary because newer mysql versions use the `caching_sha2_password` driver,
# which isn't supported prior to PHP7.4
# When we drop support for PHP7.3, we should remove this from the setup.
- name: Create MySQL Database
run: |
sudo systemctl start mysql
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306
mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" --port 13306
- name: Install Composer dependencies
run: composer install
@@ -75,5 +65,3 @@ jobs:
- name: Run Composer tests
run: composer test
env:
COMPOSER_PROCESS_TIMEOUT: 600

2
.gitignore vendored
View File

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

View File

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

View File

@@ -1,164 +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
- Slug drivers support (https://github.com/flarum/core/pull/2456).
- Notification type extender (https://github.com/flarum/core/pull/2424).
- Validation extender (https://github.com/flarum/core/pull/2102).
- Post extender (https://github.com/flarum/core/pull/2101).
- Notification channel extender (https://github.com/flarum/core/pull/2432).
- Service provider extender (https://github.com/flarum/core/pull/2437).
- API serializer extender (https://github.com/flarum/core/pull/2438).
- User preferences extender (https://github.com/flarum/core/pull/2463).
- Settings extender (https://github.com/flarum/core/pull/2452).
- ApiController extender (https://github.com/flarum/core/pull/2451).
- Model visibility extender (https://github.com/flarum/core/pull/2460).
- Policy extender (https://github.com/flarum/core/pull/2461).
### Changed
- Time helpers converted to Typescript (https://github.com/flarum/core/pull/2391).
- Improved the formatter extender (https://github.com/flarum/core/pull/2098).
- Improve wording on installer when facing file permission issues (https://github.com/flarum/core/pull/2435).
- Background color of checkbox toggles improved for better usability (https://github.com/flarum/core/pull/2443).
- Route resolving refactored (https://github.com/flarum/core/pull/2425).
- Administration panel UX refactored (https://github.com/flarum/core/pull/2409).
- Floodgate moved to middleware and extender added (https://github.com/flarum/core/pull/2170).
- DRY up image uploading logic (https://github.com/flarum/core/pull/2477).
- Process isolation on testing (https://github.com/flarum/core/commit/984f751c718c89501cc09857bc271efa2c7eea8c).
- Forum and admin javascript exports namespaced (https://github.com/flarum/core/pull/2488).
### Fixed
- Web updater does not take into account subfolder installations (https://github.com/flarum/core/pull/2426).
- Callables handling in extenders failed (https://github.com/flarum/core/pull/2423).
- Scrolling on mobile from PostSteam changes didn't work correctly (https://github.com/flarum/core/pull/2385).
- Side pane covers part of the discussion page due to `app.discussions` being empty (https://github.com/flarum/core/commit/102e76b084bf47fdfb4c73f95e1fbb322537f7aa).
- Change email modal keeps showing the previous error message even on success (https://github.com/flarum/core/pull/2467).
- Comment count not updated when discussions are deleted (https://github.com/flarum/core/pull/2472).
- `goToIndex` in PostStream does not trigger an xhr to retrieve new data (https://github.com/flarum/core/commit/09e2736cbcc267594b660beabbd001d9030f9880).
- On refresh the post number is reduced by one (https://github.com/flarum/core/pull/2476).
- Queue worker would instantiate a new Queue factory, not the bound one (https://github.com/flarum/core/pull/2481).
- Header accidentally has a border bottom (https://github.com/flarum/core/pull/2489).
- Namespace mentioned in docblock is incorrect (https://github.com/flarum/core/pull/2494).
- Scrolling inside longer discussions (especially Firefox) skips posts (https://github.com/flarum/core/commit/210a6b3e253d7917bd1eacd3ed8d2f95073ae99d).
- Uploading avatars that are jpg/jpeg fails with a validation error (https://github.com/flarum/core/pull/2497).
### Removed
- MomentJS alias (https://github.com/flarum/core/pull/2428).
- Deprecated user events `GetDisplayName` and `PrepareUserGroups` (https://github.com/flarum/core/pull/2428).
- AssertPermissionTrait (https://github.com/flarum/core/pull/2428).
- Path related helpers and methods in Application (https://github.com/flarum/core/pull/2428).
- Backward compatibility layers from the frontend rewrite (https://github.com/flarum/core/pull/2428).
### Deprecated
- `CheckingForFlooding` (https://github.com/flarum/core/commit/8e25bcb68f86cc992c46dfa70368419fe9f936ac).
## [0.1.0-beta.14.1](https://github.com/flarum/core/compare/v0.1.0-beta.14...v0.1.0-beta.14.1)
### Fixed
- SuperTextarea component is not exported.
- Symfony dependencies do not match those depended on by Laravel (https://github.com/flarum/core/pull/2407).
- Scripts from textformatter aren't executed (https://github.com/flarum/core/pull/2415)
- Sub path installations have no page title.
- Losing focus of Composer area when coming from fullscreen.
## [0.1.0-beta.14](https://github.com/flarum/core/compare/v0.1.0-beta.13...v0.1.0-beta.14)
### Added
@@ -301,7 +142,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,17 +1,36 @@
{
"name": "flarum/core",
"description": "Delightfully simple forum software.",
"keywords": [
"forum",
"discussion"
],
"keywords": ["forum", "discussion"],
"homepage": "https://flarum.org/",
"license": "MIT",
"authors": [
{
"name": "Flarum",
"email": "info@flarum.org",
"homepage": "https://flarum.org/team"
"name": "Franz Liedke",
"email": "franz@develophp.org"
},
{
"name": "Daniël Klabbers",
"email": "daniel@klabbers.email",
"homepage": "https://luceos.com"
},
{
"name": "David Sevilla Martin",
"email": "me+flarum@datitisev.me",
"homepage": "https://datitisev.me"
},
{
"name": "Clark Winkelmann",
"email": "clark.winkelmann@gmail.com",
"homepage": "https://clarkwinkelmann.com"
},
{
"name": "Matthew Kilgore",
"email": "matthew@kilgore.dev"
},
{
"name": "Alexander (Sasha) Skvortsov",
"email": "askvortsov@flarum.org"
}
],
"support": {
@@ -20,38 +39,36 @@
"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",
"dflydev/fig-cookies": "^2.0.1",
"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",
"franzl/whoops-middleware": "^0.4.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",
"laminas/laminas-stratigility": "^3.2.2",
"laminas/laminas-diactoros": "^1.8.4",
"laminas/laminas-httphandlerrunner": "^1.0",
"laminas/laminas-stratigility": "^3.0",
"league/flysystem": "^1.0.11",
"matthiasmullie/minify": "^1.3",
"middlewares/base-path": "^2.0.1",
"middlewares/base-path-router": "^2.0.1",
"middlewares/request-handler": "^2.0.1",
"middlewares/base-path": "^1.1",
"middlewares/base-path-router": "^0.2.1",
"middlewares/request-handler": "^1.2",
"monolog/monolog": "^1.16.0",
"nesbot/carbon": "^2.0",
"nikic/fast-route": "^0.6",
@@ -59,17 +76,17 @@
"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/mime": "^5.2.0",
"symfony/translation": "^5.1.5",
"symfony/yaml": "^5.2.2",
"symfony/config": "^4.3.4",
"symfony/console": "^4.3.4",
"symfony/event-dispatcher": "^4.3.4",
"symfony/translation": "^4.3.4",
"symfony/yaml": "^4.3.4",
"tobscure/json-api": "^0.3.0",
"wikimedia/less.php": "^3.0"
},
"require-dev": {
"flarum/testing": "^0.1.0-beta.16"
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.0"
},
"autoload": {
"psr-4": {

View File

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

View File

@@ -1,40 +0,0 @@
// Hi early reviewer! I'm a temporary file and
// will be moved to the Flarum webpack config soon!
const loaderUtils = require('loader-utils');
// Custom loader logic
module.exports = function (source) {
// Get the type of the module to be exported
const location = loaderUtils.interpolateName(this, '[folder]/', {
context: this.rootContext || this.context,
});
// Get the name of module to be exported
const moduleName = loaderUtils.interpolateName(this, '[name]', {
context: this.rootContext || this.context,
});
// Don't export low level files
if (/.*\/(admin|forum)$/.test(location) || /(index|app|compat|FlarumRegistry)$/.test(moduleName)) {
return source;
}
let addition = "";
// Find the export names
const matches = [...source.matchAll(/export\s+?(?:default\s?|function|abstract\s?|class)+?\s([^(\s<;]*)/gm)];
matches.map(match => {
let name = match[1]
if (!name || name === 'interface') {
return;
}
// Add code at the end of the file to add the file to registry
addition += `\nwindow.flreg.add('${location}${name}', ${name})`
});
return source + addition;
}

View File

@@ -8,4 +8,4 @@
*/
export * from './src/common';
export * from './src/admin';
export * from './src/admin';

26
js/dist/admin.js vendored Normal file

File diff suppressed because one or more lines are too long

1
js/dist/admin.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(()=>{var e,r={},o={};function t(e){var n=o[e];if(void 0!==n)return n.exports;var l=o[e]={exports:{}};return r[e].call(l.exports,l,l.exports,t),l.exports}t.m=r,e=[],t.O=(r,o,n,l)=>{if(!o){var a=1/0;for(f=0;f<e.length;f++){for(var[o,n,l]=e[f],i=!0,u=0;u<o.length;u++)(!1&l||a>=l)&&Object.keys(t.O).every((e=>t.O[e](o[u])))?o.splice(u--,1):(i=!1,l<a&&(a=l));i&&(e.splice(f--,1),r=n())}return r}l=l||0;for(var f=e.length;f>0&&e[f-1][2]>l;f--)e[f]=e[f-1];e[f]=[o,n,l]},t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e={93:0};t.O.j=r=>0===e[r];var r=(r,o)=>{var n,l,[a,i,u]=o,f=0;for(n in i)t.o(i,n)&&(t.m[n]=i[n]);for(u&&u(t),r&&r(o);f<a.length;f++)l=a[f],t.o(e,l)&&e[l]&&e[l][0](),e[a[f]]=0;t.O()},o=self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[];o.forEach(r.bind(null,0)),o.push=r.bind(null,o.push.bind(o))})(),t.O()})();
//# sourceMappingURL=runtime.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

53
js/dist/forum.js vendored Normal file

File diff suppressed because one or more lines are too long

1
js/dist/forum.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[372],{4146:(a,s,t)=>{t.r(s),t.d(s,{default:()=>n});var e=t(1788),o=t(5556),r=t(2587),i=t(1024),n=function(a){function s(){return a.apply(this,arguments)||this}(0,e.Z)(s,a);var t=s.prototype;return t.oninit=function(s){a.prototype.oninit.call(this,s),this.success=!1,this.email=(0,i.Z)(app.session.user.email()),this.password=(0,i.Z)("")},t.className=function(){return"ChangeEmailModal Modal--small"},t.title=function(){return app.translator.trans("core.forum.change_email.title")},t.content=function(){return this.success?m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.change_email.confirmation_message",{email:m("strong",null,this.email())})),m("div",{className:"Form-group"},m(r.Z,{className:"Button Button--primary Button--block",onclick:this.hide.bind(this)},app.translator.trans("core.forum.change_email.dismiss_button"))))):m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("div",{className:"Form-group"},m("input",{type:"email",name:"email",className:"FormControl",placeholder:app.session.user.email(),bidi:this.email,disabled:this.loading})),m("div",{className:"Form-group"},m("input",{type:"password",name:"password",className:"FormControl",placeholder:app.translator.trans("core.forum.change_email.confirm_password_placeholder"),bidi:this.password,disabled:this.loading})),m("div",{className:"Form-group"},r.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.change_email.submit_button")))))},t.onsubmit=function(a){var s=this;a.preventDefault(),this.email()!==app.session.user.email()?(this.loading=!0,this.alertAttrs=null,app.session.user.save({email:this.email()},{errorHandler:this.onerror.bind(this),meta:{password:this.password()}}).then((function(){s.success=!0})).catch((function(){})).then(this.loaded.bind(this))):this.hide()},t.onerror=function(s){401===s.status&&(s.alert.content=app.translator.trans("core.forum.change_email.incorrect_password_message")),a.prototype.onerror.call(this,s)},s}(o.Z);window.flreg.add("components/ChangeEmailModal",n)}}]);
//# sourceMappingURL=ChangeEmailModal.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[503],{22:(t,a,e)=>{e.r(a),e.d(a,{default:()=>s});var o=e(1788),r=e(5556),n=e(2587),s=function(t){function a(){return t.apply(this,arguments)||this}(0,o.Z)(a,t);var e=a.prototype;return e.className=function(){return"ChangePasswordModal Modal--small"},e.title=function(){return app.translator.trans("core.forum.change_password.title")},e.content=function(){return m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.change_password.text")),m("div",{className:"Form-group"},n.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.change_password.send_button")))))},e.onsubmit=function(t){t.preventDefault(),this.loading=!0,app.request({method:"POST",url:app.forum.attribute("apiUrl")+"/forgot",body:{email:app.session.user.email()}}).then(this.hide.bind(this),this.loaded.bind(this))},a}(r.Z);window.flreg.add("components/ChangePasswordModal",s)}}]);
//# sourceMappingURL=ChangePasswordModal.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/ChangePasswordModal.js"],"names":["ChangePasswordModal","className","title","app","translator","trans","content","Button","type","loading","this","onsubmit","e","preventDefault","request","method","url","forum","attribute","body","email","session","user","then","hide","bind","loaded","Modal","window","flreg","add"],"mappings":"4JAOqBA,E,sGACnBC,UAAA,WACE,MAAO,oC,EAGTC,MAAA,WACE,OAAOC,IAAIC,WAAWC,MAAM,qC,EAG9BC,QAAA,WACE,OACE,SAAKL,UAAU,cACb,SAAKA,UAAU,uBACb,OAAGA,UAAU,YAAYE,IAAIC,WAAWC,MAAM,oCAC9C,SAAKJ,UAAU,cACZM,cACC,CACEN,UAAW,uCACXO,KAAM,SACNC,QAASC,KAAKD,SAEhBN,IAAIC,WAAWC,MAAM,+C,EAQjCM,SAAA,SAASC,GACPA,EAAEC,iBAEFH,KAAKD,SAAU,EAEfN,IACGW,QAAQ,CACPC,OAAQ,OACRC,IAAKb,IAAIc,MAAMC,UAAU,UAAY,UACrCC,KAAM,CAAEC,MAAOjB,IAAIkB,QAAQC,KAAKF,WAEjCG,KAAKb,KAAKc,KAAKC,KAAKf,MAAOA,KAAKgB,OAAOD,KAAKf,Q,GAxCFiB,KA4CjDC,OAAOC,MAAMC,IAAI,iCAAkC9B","file":"forum/components/ChangePasswordModal.js","sourcesContent":["import Modal from '../../common/components/Modal';\nimport Button from '../../common/components/Button';\n\n/**\n * The `ChangePasswordModal` component shows a modal dialog which allows the\n * user to send themself a password reset email.\n */\nexport default class ChangePasswordModal extends Modal {\n className() {\n return 'ChangePasswordModal Modal--small';\n }\n\n title() {\n return app.translator.trans('core.forum.change_password.title');\n }\n\n content() {\n return (\n <div className=\"Modal-body\">\n <div className=\"Form Form--centered\">\n <p className=\"helpText\">{app.translator.trans('core.forum.change_password.text')}</p>\n <div className=\"Form-group\">\n {Button.component(\n {\n className: 'Button Button--primary Button--block',\n type: 'submit',\n loading: this.loading,\n },\n app.translator.trans('core.forum.change_password.send_button')\n )}\n </div>\n </div>\n </div>\n );\n }\n\n onsubmit(e) {\n e.preventDefault();\n\n this.loading = true;\n\n app\n .request({\n method: 'POST',\n url: app.forum.attribute('apiUrl') + '/forgot',\n body: { email: app.session.user.email() },\n })\n .then(this.hide.bind(this), this.loaded.bind(this));\n }\n}\n\nwindow.flreg.add('components/ChangePasswordModal', ChangePasswordModal)"],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[437],{3406:(t,s,o)=>{o.r(s),o.d(s,{default:()=>a});var e=o(1788),i=o(4810),r=o(5731),n=o(1024),a=function(t){function s(){return t.apply(this,arguments)||this}(0,e.Z)(s,t),s.initAttrs=function(s){t.initAttrs.call(this,s),s.placeholder=s.placeholder||(0,r.Z)(app.translator.trans("core.forum.composer_discussion.body_placeholder")),s.submitLabel=s.submitLabel||app.translator.trans("core.forum.composer_discussion.submit_button"),s.confirmExit=s.confirmExit||(0,r.Z)(app.translator.trans("core.forum.composer_discussion.discard_confirmation")),s.titlePlaceholder=s.titlePlaceholder||(0,r.Z)(app.translator.trans("core.forum.composer_discussion.title_placeholder")),s.className="ComposerBody--discussion"};var o=s.prototype;return o.oninit=function(s){t.prototype.oninit.call(this,s),this.composer.fields.title=this.composer.fields.title||(0,n.Z)(""),this.title=this.composer.fields.title},o.headerItems=function(){var s=t.prototype.headerItems.call(this);return s.add("title",m("h3",null,app.translator.trans("core.forum.composer_discussion.title")),100),s.add("discussionTitle",m("h3",null,m("input",{className:"FormControl",bidi:this.title,placeholder:this.attrs.titlePlaceholder,disabled:!!this.attrs.disabled,onkeydown:this.onkeydown.bind(this)}))),s},o.onkeydown=function(t){13===t.which&&(t.preventDefault(),this.composer.editor.moveCursorTo(0)),t.redraw=!1},o.hasChanges=function(){return this.title()||this.composer.fields.content()},o.data=function(){return{title:this.title(),content:this.composer.fields.content()}},o.onsubmit=function(){var t=this;this.loading=!0;var s=this.data();app.store.createRecord("discussions").save(s).then((function(s){t.composer.hide(),app.discussions.refresh({deferClear:!0}),m.route.set(app.route.discussion(s))}),this.loaded.bind(this))},s}(i.Z);window.flreg.add("components/DiscussionComposer",a)}}]);
//# sourceMappingURL=DiscussionComposer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[799],{5813:(t,e,s)=>{s.a(t,(async t=>{s.r(e),s.d(e,{default:()=>r});var n=s(1788),a=s(486),o=s(2060),r=function(t){function e(){return t.apply(this,arguments)||this}(0,n.Z)(e,t);var s=e.prototype;return s.oninit=function(e){t.prototype.oninit.call(this,e),this.loadUser(m.route.param("username"))},s.show=function(e){t.prototype.show.call(this,e),this.state=new o.Z({q:"author:"+e.username(),sort:"newest"}),this.state.refresh()},s.content=function(){return m("div",{className:"DiscussionsUserPage"},a.Z.component({state:this.state}))},e}((await s.e(251).then(s.bind(s,6068))).default);window.flreg.add("components/DiscussionsUserPage",r),t()}),1)}}]);
//# sourceMappingURL=DiscussionsUserPage.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/DiscussionsUserPage.js"],"names":["DiscussionsUserPage","oninit","vnode","this","loadUser","m","route","param","show","user","state","DiscussionListState","q","username","sort","refresh","content","className","DiscussionList","window","flreg","add"],"mappings":"8KASqBA,E,sGACnBC,OAAA,SAAOC,GACL,YAAMD,OAAN,UAAaC,GAEbC,KAAKC,SAASC,EAAEC,MAAMC,MAAM,c,EAG9BC,KAAA,SAAKC,GACH,YAAMD,KAAN,UAAWC,GAEXN,KAAKO,MAAQ,IAAIC,IAAoB,CACnCC,EAAG,UAAYH,EAAKI,WACpBC,KAAM,WAGRX,KAAKO,MAAMK,W,EAGbC,QAAA,WACE,OAAO,SAAKC,UAAU,uBAAuBC,cAAyB,CAAER,MAAOP,KAAKO,U,UAzBhE,+BAAP,SA6BjBS,OAAOC,MAAMC,IAAI,iCAAkCrB,G","file":"forum/components/DiscussionsUserPage.js","sourcesContent":["import DiscussionList from './DiscussionList';\nimport DiscussionListState from '../states/DiscussionListState';\n\nconst UserPage = (await import(/* webpackChunkName: \"forum/components/UserPage\" */ './UserPage')).default;\n\n/**\n * The `DiscussionsUserPage` component shows a discussion list inside of a user\n * page.\n */\nexport default class DiscussionsUserPage extends UserPage {\n oninit(vnode) {\n super.oninit(vnode);\n\n this.loadUser(m.route.param('username'));\n }\n\n show(user) {\n super.show(user);\n\n this.state = new DiscussionListState({\n q: 'author:' + user.username(),\n sort: 'newest',\n });\n\n this.state.refresh();\n }\n\n content() {\n return <div className=\"DiscussionsUserPage\">{DiscussionList.component({ state: this.state })}</div>;\n }\n}\n\nwindow.flreg.add('components/DiscussionsUserPage', DiscussionsUserPage)"],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[293],{763:(t,o,s)=>{s.r(o),s.d(o,{default:()=>u});var e=s(1788),r=s(4810),n=s(2587),i=s(8046),a=s(9100);function p(t){app.composer.isFullScreen()&&(app.composer.minimize(),t.stopPropagation())}var u=function(t){function o(){return t.apply(this,arguments)||this}(0,e.Z)(o,t),o.initAttrs=function(o){t.initAttrs.call(this,o),o.submitLabel=o.submitLabel||app.translator.trans("core.forum.composer_edit.submit_button"),o.confirmExit=o.confirmExit||app.translator.trans("core.forum.composer_edit.discard_confirmation"),o.originalContent=o.originalContent||o.post.content(),o.user=o.user||o.post.user(),o.post.editedContent=o.originalContent};var s=o.prototype;return s.headerItems=function(){var o=t.prototype.headerItems.call(this),s=this.attrs.post;return o.add("title",m("h3",null,(0,a.Z)("fas fa-pencil-alt")," ",m(i.Z,{href:app.route.discussion(s.discussion(),s.number()),onclick:p},app.translator.trans("core.forum.composer_edit.post_link",{number:s.number(),discussion:s.discussion().title()})))),o},s.jumpToPreview=function(t){p(t),m.route.set(app.route.post(this.attrs.post))},s.data=function(){return{content:this.composer.fields.content()}},s.onsubmit=function(){var t=this,o=this.attrs.post.discussion();this.loading=!0;var s=this.data();this.attrs.post.save(s).then((function(s){if(app.viewingDiscussion(o))app.current.get("stream").goToNumber(s.number());else{var e,r=n.Z.component({className:"Button Button--link",onclick:function(){m.route.set(app.route.post(s)),app.alerts.dismiss(e)}},app.translator.trans("core.forum.composer_edit.view_button"));e=app.alerts.show({type:"success",controls:[r]},app.translator.trans("core.forum.composer_edit.edited_message"))}t.composer.hide()}),this.loaded.bind(this))},o}(r.Z);window.flreg.add("components/EditPostComposer",u)}}]);
//# sourceMappingURL=EditPostComposer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[352],{3089:(s,r,t)=>{t.r(r),t.d(r,{default:()=>p});var e=t(1788),a=t(5556),i=t(2587),n=t(7880),o=t(4032),d=t(5731),u=t(4594),l=t(1024),p=function(s){function r(){return s.apply(this,arguments)||this}(0,e.Z)(r,s);var t=r.prototype;return t.oninit=function(r){var t=this;s.prototype.oninit.call(this,r);var e=this.attrs.user;this.username=(0,l.Z)(e.username()||""),this.email=(0,l.Z)(e.email()||""),this.isEmailConfirmed=(0,l.Z)(e.isEmailConfirmed()||!1),this.setPassword=(0,l.Z)(!1),this.password=(0,l.Z)(e.password()||""),this.groups={},app.store.all("groups").filter((function(s){return-1===[o.Z.GUEST_ID,o.Z.MEMBER_ID].indexOf(s.id())})).forEach((function(s){return t.groups[s.id()]=(0,l.Z)(-1!==e.groups().indexOf(s))}))},t.className=function(){return"EditUserModal Modal--small"},t.title=function(){return app.translator.trans("core.forum.edit_user.title")},t.content=function(){var s=this.fields().toArray();return m("div",{className:"Modal-body"},s.length>1?m("div",{className:"Form"},this.fields().toArray()):app.translator.trans("core.forum.edit_user.nothing_available"))},t.fields=function(){var s=this,r=new u.Z;return app.session.user.canEditCredentials()&&(r.add("username",m("div",{className:"Form-group"},m("label",null,app.translator.trans("core.forum.edit_user.username_heading")),m("input",{className:"FormControl",placeholder:(0,d.Z)(app.translator.trans("core.forum.edit_user.username_label")),bidi:this.username,disabled:this.nonAdminEditingAdmin()})),40),app.session.user!==this.attrs.user&&(r.add("email",m("div",{className:"Form-group"},m("label",null,app.translator.trans("core.forum.edit_user.email_heading")),m("div",null,m("input",{className:"FormControl",placeholder:(0,d.Z)(app.translator.trans("core.forum.edit_user.email_label")),bidi:this.email,disabled:this.nonAdminEditingAdmin()})),!this.isEmailConfirmed()&&this.userIsAdmin(app.session.user)?m("div",null,i.Z.component({className:"Button Button--block",loading:this.loading,onclick:this.activate.bind(this)},app.translator.trans("core.forum.edit_user.activate_button"))):""),30),r.add("password",m("div",{className:"Form-group"},m("label",null,app.translator.trans("core.forum.edit_user.password_heading")),m("div",null,m("label",{className:"checkbox"},m("input",{type:"checkbox",onchange:function(r){s.setPassword(r.target.checked),m.redraw.sync(),r.target.checked&&s.$("[name=password]").select(),r.redraw=!1},disabled:this.nonAdminEditingAdmin()}),app.translator.trans("core.forum.edit_user.set_password_label")),this.setPassword()?m("input",{className:"FormControl",type:"password",name:"password",placeholder:(0,d.Z)(app.translator.trans("core.forum.edit_user.password_label")),bidi:this.password,disabled:this.nonAdminEditingAdmin()}):"")),20))),app.session.user.canEditGroups()&&r.add("groups",m("div",{className:"Form-group EditUserModal-groups"},m("label",null,app.translator.trans("core.forum.edit_user.groups_heading")),m("div",null,Object.keys(this.groups).map((function(s){return app.store.getById("groups",s)})).map((function(r){return m("label",{className:"checkbox"},m("input",{type:"checkbox",bidi:s.groups[r.id()],disabled:r.id()===o.Z.ADMINISTRATOR_ID&&(s.attrs.user===app.session.user||!s.userIsAdmin(app.session.user))}),n.Z.component({group:r,label:""})," ",r.nameSingular())})))),10),r.add("submit",m("div",{className:"Form-group"},i.Z.component({className:"Button Button--primary",type:"submit",loading:this.loading},app.translator.trans("core.forum.edit_user.submit_button"))),-10),r},t.activate=function(){var s=this;this.loading=!0;var r={username:this.username(),isEmailConfirmed:!0};this.attrs.user.save(r,{errorHandler:this.onerror.bind(this)}).then((function(){s.isEmailConfirmed(!0),s.loading=!1,m.redraw()})).catch((function(){s.loading=!1,m.redraw()}))},t.data=function(){var s=this,r={relationships:{}};return this.attrs.user.canEditCredentials()&&!this.nonAdminEditingAdmin()&&(r.username=this.username(),app.session.user!==this.attrs.user&&(r.email=this.email()),this.setPassword()&&(r.password=this.password())),this.attrs.user.canEditGroups()&&(r.relationships.groups=Object.keys(this.groups).filter((function(r){return s.groups[r]()})).map((function(s){return app.store.getById("groups",s)}))),r},t.onsubmit=function(s){var r=this;s.preventDefault(),this.loading=!0,this.attrs.user.save(this.data(),{errorHandler:this.onerror.bind(this)}).then(this.hide.bind(this)).catch((function(){r.loading=!1,m.redraw()}))},t.nonAdminEditingAdmin=function(){return this.userIsAdmin(this.attrs.user)&&!this.userIsAdmin(app.session.user)},t.userIsAdmin=function(s){return s.groups().some((function(s){return s.id()===o.Z.ADMINISTRATOR_ID}))},r}(a.Z);window.flreg.add("components/EditUserModal",p)}}]);
//# sourceMappingURL=EditUserModal.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[502],{5833:(t,o,r)=>{r.r(o),r.d(o,{default:()=>l});var a=r(1788),s=r(5556),e=r(2587),n=r(5731),i=r(1024),l=function(t){function o(){return t.apply(this,arguments)||this}(0,a.Z)(o,t);var r=o.prototype;return r.oninit=function(o){t.prototype.oninit.call(this,o),this.email=(0,i.Z)(this.attrs.email||""),this.success=!1},r.className=function(){return"ForgotPasswordModal Modal--small"},r.title=function(){return app.translator.trans("core.forum.forgot_password.title")},r.content=function(){return this.success?m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.forgot_password.email_sent_message")),m("div",{className:"Form-group"},m(e.Z,{className:"Button Button--primary Button--block",onclick:this.hide.bind(this)},app.translator.trans("core.forum.forgot_password.dismiss_button"))))):m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.forgot_password.text")),m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"email",type:"email",placeholder:(0,n.Z)(app.translator.trans("core.forum.forgot_password.email_placeholder")),bidi:this.email,disabled:this.loading})),m("div",{className:"Form-group"},e.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.forgot_password.submit_button")))))},r.onsubmit=function(t){var o=this;t.preventDefault(),this.loading=!0,app.request({method:"POST",url:app.forum.attribute("apiUrl")+"/forgot",body:{email:this.email()},errorHandler:this.onerror.bind(this)}).then((function(){o.success=!0,o.alert=null})).catch((function(){})).then(this.loaded.bind(this))},r.onerror=function(o){404===o.status&&(o.alert.content=app.translator.trans("core.forum.forgot_password.not_found_message")),t.prototype.onerror.call(this,o)},o}(s.Z);window.flreg.add("components/ForgotPasswordModal",l)}}]);
//# sourceMappingURL=ForgotPasswordModal.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[460],{7527:(t,o,i)=>{i.d(o,{Z:()=>e});var n=i(1788),r=i(8931),a=i(4594),e=function(t){function o(){return t.apply(this,arguments)||this}(0,n.Z)(o,t);var i=o.prototype;return i.view=function(){return m("div",{className:"LogInButtons"},this.items().toArray())},i.items=function(){return new a.Z},o}(r.Z);window.flreg.add("components/LogInButtons",e)},1498:(t,o,i)=>{i.r(o),i.d(o,{default:()=>c});var n=i(1788),r=i(5556),a=i(2587),e=i(7527),s=i(5731),l=i(4594),d=i(1024),c=function(t){function o(){return t.apply(this,arguments)||this}(0,n.Z)(o,t);var r=o.prototype;return r.oninit=function(o){t.prototype.oninit.call(this,o),this.identification=(0,d.Z)(this.attrs.identification||""),this.password=(0,d.Z)(this.attrs.password||""),this.remember=(0,d.Z)(!!this.attrs.remember)},r.className=function(){return"LogInModal Modal--small"},r.title=function(){return app.translator.trans("core.forum.log_in.title")},r.content=function(){return[m("div",{className:"Modal-body"},this.body()),m("div",{className:"Modal-footer"},this.footer())]},r.body=function(){return[m(e.Z,null),m("div",{className:"Form Form--centered"},this.fields().toArray())]},r.fields=function(){var t=new l.Z;return t.add("identification",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"identification",type:"text",placeholder:(0,s.Z)(app.translator.trans("core.forum.log_in.username_or_email_placeholder")),bidi:this.identification,disabled:this.loading})),30),t.add("password",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"password",type:"password",placeholder:(0,s.Z)(app.translator.trans("core.forum.log_in.password_placeholder")),bidi:this.password,disabled:this.loading})),20),t.add("remember",m("div",{className:"Form-group"},m("div",null,m("label",{className:"checkbox"},m("input",{type:"checkbox",bidi:this.remember,disabled:this.loading}),app.translator.trans("core.forum.log_in.remember_me_label")))),10),t.add("submit",m("div",{className:"Form-group"},a.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.log_in.submit_button"))),-10),t},r.footer=function(){return[m("p",{className:"LogInModal-forgotPassword"},m("a",{onclick:this.forgotPassword.bind(this)},app.translator.trans("core.forum.log_in.forgot_password_link"))),app.forum.attribute("allowSignUp")?m("p",{className:"LogInModal-signUp"},app.translator.trans("core.forum.log_in.sign_up_text",{a:m("a",{onclick:this.signUp.bind(this)})})):""]},r.forgotPassword=function(){var t=this.identification(),o=-1!==t.indexOf("@")?{email:t}:void 0;app.modal.show((function(){return i.e(502).then(i.bind(i,5833))}),o)},r.signUp=function(){var t={password:this.password()},o=this.identification();t[-1!==o.indexOf("@")?"email":"username"]=o,app.modal.show((function(){return i.e(395).then(i.bind(i,3235))}),t)},r.onready=function(){this.$("[name="+(this.identification()?"password":"identification")+"]").select()},r.onsubmit=function(t){t.preventDefault(),this.loading=!0;var o=this.identification(),i=this.password(),n=this.remember();app.session.login({identification:o,password:i,remember:n},{errorHandler:this.onerror.bind(this)}).then((function(){return window.location.reload()}),this.loaded.bind(this))},r.onerror=function(o){401===o.status&&(o.alert.content=app.translator.trans("core.forum.log_in.invalid_login_message")),t.prototype.onerror.call(this,o)},o}(r.Z);window.flreg.add("components/LogInModal",c)}}]);
//# sourceMappingURL=LogInModal.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[77],{3490:(t,i,n)=>{n.r(i),n.d(i,{default:()=>N});var o=n(1788),a=n(8931),s=n(7473),e=n(2587),c=n(8046),r=n(8726),l=n(8197),u=n(9908),f=n(9100),p=n(6961),d=function(t){function i(){return t.apply(this,arguments)||this}(0,o.Z)(i,t);var n=i.prototype;return n.view=function(){var t=this,i=this.attrs.notification,n=this.href();return m(c.Z,{className:"Notification Notification--"+i.contentType()+" "+(i.isRead()?"":"unread"),href:n,external:n.includes("://"),onclick:this.markAsRead.bind(this)},!i.isRead()&&e.Z.component({className:"Notification-action Button Button--icon Button--link",icon:"fas fa-check",title:app.translator.trans("core.forum.notifications.mark_as_read_tooltip"),onclick:function(i){i.preventDefault(),i.stopPropagation(),t.markAsRead()}}),(0,u.Z)(i.fromUser()),(0,f.Z)(this.icon(),{className:"Notification-icon"}),m("span",{className:"Notification-content"},this.content()),(0,p.Z)(i.createdAt()),m("div",{className:"Notification-excerpt"},this.excerpt()))},n.icon=function(){},n.href=function(){},n.content=function(){},n.excerpt=function(){},n.markAsRead=function(){this.attrs.notification.isRead()||(app.session.user.pushAttributes({unreadNotificationCount:app.session.user.unreadNotificationCount()-1}),this.attrs.notification.save({isRead:!0}))},i}(a.Z);window.flreg.add("components/Notification",d);var h=function(t){function i(){return t.apply(this,arguments)||this}(0,o.Z)(i,t);var n=i.prototype;return n.icon=function(){return"fas fa-pencil-alt"},n.href=function(){var t=this.attrs.notification;return app.route.discussion(t.subject(),t.content().postNumber)},n.content=function(){return app.translator.trans("core.forum.notifications.discussion_renamed_text",{user:this.attrs.notification.fromUser()})},i}(d);window.flreg.add("components/DiscussionRenamedNotification",h);var N=function(t){function i(){return t.apply(this,arguments)||this}(0,o.Z)(i,t);var n=i.prototype;return n.view=function(){var t=this,i=this.attrs.state,n=i.getNotificationPages();return m("div",{className:"NotificationList"},m("div",{className:"NotificationList-header"},m("div",{className:"App-primaryControl"},e.Z.component({className:"Button Button--icon Button--link",icon:"fas fa-check",title:app.translator.trans("core.forum.notifications.mark_all_as_read_tooltip"),onclick:i.markAllAsRead.bind(i)})),m("h4",{className:"App-titleControl App-titleControl--text"},app.translator.trans("core.forum.notifications.title"))),m("div",{className:"NotificationList-content"},n.length?n.map((function(i){var n=[],o={};return i.forEach((function(t){var i=t.subject();if(void 0!==i){var a=!1;i instanceof l.Z?a=i:i&&i.discussion&&(a=i.discussion());var s=a?a.id():0;o[s]=o[s]||{discussion:a,notifications:[]},o[s].notifications.push(t),-1===n.indexOf(o[s])&&n.push(o[s])}})),n.map((function(i){var n=i.discussion&&i.discussion.badges().toArray();return m("div",{className:"NotificationGroup"},i.discussion?m(c.Z,{className:"NotificationGroup-header",href:app.route.discussion(i.discussion)},n&&n.length?m("ul",{className:"NotificationGroup-badges badges"},(0,s.Z)(n)):"",i.discussion.title()):m("div",{className:"NotificationGroup-header"},app.forum.attribute("title")),m("ul",{className:"NotificationGroup-content"},i.notifications.map((function(i){var n=t.notificationComponents()[i.contentType()];return n?m("li",null,n.component({notification:i})):""}))))}))})):"",i.isLoading()?m(r.Z,{className:"LoadingIndicator--block"}):n.length?"":m("div",{className:"NotificationList-empty"},app.translator.trans("core.forum.notifications.empty_text"))))},n.notificationComponents=function(){return{discussionRenamed:h}},n.oncreate=function(i){t.prototype.oncreate.call(this,i),this.$notifications=this.$(".NotificationList-content"),this.$scrollParent=this.inPanel()?this.$notifications:$(window),this.boundScrollHandler=this.scrollHandler.bind(this),this.$scrollParent.on("scroll",this.boundScrollHandler)},n.onremove=function(){this.$scrollParent.off("scroll",this.boundScrollHandler)},n.scrollHandler=function(){var t=this.attrs.state,i=this.inPanel()?this.$scrollParent[0]:document.documentElement,n=Math.abs(i.scrollHeight-i.scrollTop-i.clientHeight)<=1;t.hasMoreResults()&&!t.isLoading()&&n&&t.loadMore()},n.inPanel=function(){return"auto"===this.$notifications.css("overflow")},i}(a.Z);window.flreg.add("components/NotificationList",N)}}]);
//# sourceMappingURL=NotificationList.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[765],{9440:(t,n,o)=>{o.r(n),o.d(n,{default:()=>s});var a=o(1788),e=o(9657),i=o(9100),s=function(t){function n(){return t.apply(this,arguments)||this}(0,a.Z)(n,t),n.initAttrs=function(n){n.className=n.className||"NotificationsDropdown",n.buttonClassName=n.buttonClassName||"Button Button--flat",n.menuClassName=n.menuClassName||"Dropdown-menu--right",n.label=n.label||app.translator.trans("core.forum.notifications.tooltip"),n.icon=n.icon||"fas fa-bell",n.accessibleToggleLabel=n.accessibleToggleLabel||app.translator.trans("core.forum.notifications.toggle_dropdown_accessible_label"),t.initAttrs.call(this,n)};var e=n.prototype;return e.getButton=function(){var n=this.getNewCount(),o=t.prototype.getButton.call(this);return o.attrs.title=this.attrs.label,o.attrs.className+=n?" new":"",o.attrs.onclick=this.onclick.bind(this),o},e.getButtonContent=function(){var t=this.getUnreadCount();return[(0,i.Z)(this.attrs.icon,{className:"Button-icon"}),t?m("span",{className:"NotificationsDropdown-unread"},t):"",m("span",{className:"Button-label"},this.attrs.label)]},e.getMenu=function(){var t=this.NotificationList;return m("div",{className:"Dropdown-menu "+this.attrs.menuClassName,onclick:this.menuClick.bind(this)},this.showing&&t?m(t,{state:this.attrs.state}):"")},e.onclick=function(){var t=this;o.e(77).then(o.bind(o,3490)).then((function(n){t.NotificationList=n.default,m.redraw()})),app.drawer.isOpen()?this.goToRoute():this.attrs.state.load()},e.goToRoute=function(){m.route.set(app.route("notifications"))},e.getUnreadCount=function(){return app.session.user.unreadNotificationCount()},e.getNewCount=function(){return app.session.user.newNotificationCount()},e.menuClick=function(t){(t.shiftKey||t.metaKey||t.ctrlKey||2===t.which)&&t.stopPropagation()},n}(e.Z);window.flreg.add("components/NotificationsDropdown",s)}}]);
//# sourceMappingURL=NotificationsDropdown.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/NotificationsDropdown.js"],"names":["NotificationsDropdown","initAttrs","attrs","className","buttonClassName","menuClassName","label","app","translator","trans","icon","accessibleToggleLabel","getButton","newNotifications","this","getNewCount","vdom","title","onclick","bind","getButtonContent","unread","getUnreadCount","getMenu","NotificationList","menuClick","showing","state","then","m","redraw","drawer","isOpen","goToRoute","load","route","set","session","user","unreadNotificationCount","newNotificationCount","e","shiftKey","metaKey","ctrlKey","which","stopPropagation","Dropdown","window","flreg","add"],"mappings":"8JAGqBA,E,6EACZC,UAAP,SAAiBC,GACfA,EAAMC,UAAYD,EAAMC,WAAa,wBACrCD,EAAME,gBAAkBF,EAAME,iBAAmB,sBACjDF,EAAMG,cAAgBH,EAAMG,eAAiB,uBAC7CH,EAAMI,MAAQJ,EAAMI,OAASC,IAAIC,WAAWC,MAAM,oCAClDP,EAAMQ,KAAOR,EAAMQ,MAAQ,cAE3BR,EAAMS,sBAAwBT,EAAMS,uBAAyBJ,IAAIC,WAAWC,MAAM,6DAElF,EAAMR,UAAN,UAAgBC,I,2BAGlBU,UAAA,WACE,IAAMC,EAAmBC,KAAKC,cACxBC,EAAO,EAAH,UAASJ,UAAT,WAOV,OALAI,EAAKd,MAAMe,MAAQH,KAAKZ,MAAMI,MAE9BU,EAAKd,MAAMC,WAAaU,EAAmB,OAAS,GACpDG,EAAKd,MAAMgB,QAAUJ,KAAKI,QAAQC,KAAKL,MAEhCE,G,EAGTI,iBAAA,WACE,IAAMC,EAASP,KAAKQ,iBAEpB,MAAO,EACLZ,OAAKI,KAAKZ,MAAMQ,KAAM,CAAEP,UAAW,gBACnCkB,EAAS,UAAMlB,UAAU,gCAAgCkB,GAAiB,GAC1E,UAAMlB,UAAU,gBAAgBW,KAAKZ,MAAMI,S,EAI/CiB,QAAA,WACE,IAAMC,EAAmBV,KAAKU,iBAE9B,OACE,SAAKrB,UAAW,iBAAmBW,KAAKZ,MAAMG,cAAea,QAASJ,KAAKW,UAAUN,KAAKL,OACvFA,KAAKY,SAAWF,EAAmB,EAACA,EAAD,CAAkBG,MAAOb,KAAKZ,MAAMyB,QAAY,K,EAK1FT,QAAA,WAAU,WACR,6BAAyFU,MAAK,SAACJ,GAC7F,EAAKA,iBAAmBA,EAAgB,QACxCK,EAAEC,YAGAvB,IAAIwB,OAAOC,SACblB,KAAKmB,YAELnB,KAAKZ,MAAMyB,MAAMO,Q,EAIrBD,UAAA,WACEJ,EAAEM,MAAMC,IAAI7B,IAAI4B,MAAM,mB,EAGxBb,eAAA,WACE,OAAOf,IAAI8B,QAAQC,KAAKC,2B,EAG1BxB,YAAA,WACE,OAAOR,IAAI8B,QAAQC,KAAKE,wB,EAG1Bf,UAAA,SAAUgB,IAGJA,EAAEC,UAAYD,EAAEE,SAAWF,EAAEG,SAAuB,IAAZH,EAAEI,QAAaJ,EAAEK,mB,GAzEdC,KA6EnDC,OAAOC,MAAMC,IAAI,mCAAoClD","file":"forum/components/NotificationsDropdown.js","sourcesContent":["import Dropdown from '../../common/components/Dropdown';\nimport icon from '../../common/helpers/icon';\n\nexport default class NotificationsDropdown extends Dropdown {\n static initAttrs(attrs) {\n attrs.className = attrs.className || 'NotificationsDropdown';\n attrs.buttonClassName = attrs.buttonClassName || 'Button Button--flat';\n attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right';\n attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip');\n attrs.icon = attrs.icon || 'fas fa-bell';\n // For best a11y support, both `title` and `aria-label` should be used\n attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.forum.notifications.toggle_dropdown_accessible_label');\n\n super.initAttrs(attrs);\n }\n\n getButton() {\n const newNotifications = this.getNewCount();\n const vdom = super.getButton();\n\n vdom.attrs.title = this.attrs.label;\n\n vdom.attrs.className += newNotifications ? ' new' : '';\n vdom.attrs.onclick = this.onclick.bind(this);\n\n return vdom;\n }\n\n getButtonContent() {\n const unread = this.getUnreadCount();\n\n return [\n icon(this.attrs.icon, { className: 'Button-icon' }),\n unread ? <span className=\"NotificationsDropdown-unread\">{unread}</span> : '',\n <span className=\"Button-label\">{this.attrs.label}</span>,\n ];\n }\n\n getMenu() {\n const NotificationList = this.NotificationList;\n\n return (\n <div className={'Dropdown-menu ' + this.attrs.menuClassName} onclick={this.menuClick.bind(this)}>\n {this.showing && NotificationList ? <NotificationList state={this.attrs.state} /> : ''}\n </div>\n );\n }\n\n onclick() {\n import(/* webpackChunkName: \"forum/components/NotificationList\" */ './NotificationList').then((NotificationList) => {\n this.NotificationList = NotificationList.default;\n m.redraw();\n });\n\n if (app.drawer.isOpen()) {\n this.goToRoute();\n } else {\n this.attrs.state.load();\n }\n }\n\n goToRoute() {\n m.route.set(app.route('notifications'));\n }\n\n getUnreadCount() {\n return app.session.user.unreadNotificationCount();\n }\n\n getNewCount() {\n return app.session.user.newNotificationCount();\n }\n\n menuClick(e) {\n // Don't close the notifications dropdown if the user is opening a link in a\n // new tab or window.\n if (e.shiftKey || e.metaKey || e.ctrlKey || e.which === 2) e.stopPropagation();\n }\n}\n\nwindow.flreg.add('components/NotificationsDropdown', NotificationsDropdown)"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[564],{7214:(t,i,n)=>{n.r(i),n.d(i,{default:()=>a});var s=n(1788),e=n(5556),r=n(2587),o=n(1024),a=function(t){function i(){return t.apply(this,arguments)||this}(0,s.Z)(i,t);var n=i.prototype;return n.oninit=function(i){t.prototype.oninit.call(this,i),this.discussion=this.attrs.discussion,this.currentTitle=this.attrs.currentTitle,this.newTitle=(0,o.Z)(this.currentTitle)},n.className=function(){return"RenameDiscussionModal Modal--small"},n.title=function(){return app.translator.trans("core.forum.rename_discussion.title")},n.content=function(){return m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("div",{className:"Form-group"},m("input",{className:"FormControl",bidi:this.newTitle,type:"text"})),m("div",{className:"Form-group"},r.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.rename_discussion.submit_button")))))},n.onsubmit=function(t){var i=this;t.preventDefault(),this.loading=!0;var n=this.newTitle(),s=this.currentTitle;if(n&&n!==s)return this.discussion.save({title:n}).then((function(){app.viewingDiscussion(i.discussion)&&app.current.get("stream").update(),m.redraw(),i.hide()})).catch((function(){i.loading=!1,m.redraw()}));this.hide()},i}(e.Z);window.flreg.add("components/RenameDiscussionModal",a)}}]);
//# sourceMappingURL=RenameDiscussionModal.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/RenameDiscussionModal.js"],"names":["RenameDiscussionModal","oninit","vnode","this","discussion","attrs","currentTitle","newTitle","Stream","className","title","app","translator","trans","content","bidi","type","Button","loading","onsubmit","e","preventDefault","save","then","viewingDiscussion","current","get","update","m","redraw","hide","Modal","window","flreg","add"],"mappings":"wKAOqBA,E,sGACnBC,OAAA,SAAOC,GACL,YAAMD,OAAN,UAAaC,GAEbC,KAAKC,WAAaD,KAAKE,MAAMD,WAC7BD,KAAKG,aAAeH,KAAKE,MAAMC,aAC/BH,KAAKI,UAAWC,OAAOL,KAAKG,e,EAG9BG,UAAA,WACE,MAAO,sC,EAGTC,MAAA,WACE,OAAOC,IAAIC,WAAWC,MAAM,uC,EAG9BC,QAAA,WACE,OACE,SAAKL,UAAU,cACb,SAAKA,UAAU,uBACb,SAAKA,UAAU,cACb,WAAOA,UAAU,cAAcM,KAAMZ,KAAKI,SAAUS,KAAK,UAE3D,SAAKP,UAAU,cACZQ,cACC,CACER,UAAW,uCACXO,KAAM,SACNE,QAASf,KAAKe,SAEhBP,IAAIC,WAAWC,MAAM,mD,EAQjCM,SAAA,SAASC,GAAG,WACVA,EAAEC,iBAEFlB,KAAKe,SAAU,EAEf,IAAMR,EAAQP,KAAKI,WACbD,EAAeH,KAAKG,aAK1B,GAAII,GAASA,IAAUJ,EACrB,OAAOH,KAAKC,WACTkB,KAAK,CAAEZ,UACPa,MAAK,WACAZ,IAAIa,kBAAkB,EAAKpB,aAC7BO,IAAIc,QAAQC,IAAI,UAAUC,SAE5BC,EAAEC,SACF,EAAKC,UAPF,OASE,WACL,EAAKZ,SAAU,EACfU,EAAEC,YAGN1B,KAAK2B,Q,GAjEwCC,KAsEnDC,OAAOC,MAAMC,IAAI,mCAAoClC","file":"forum/components/RenameDiscussionModal.js","sourcesContent":["import Modal from '../../common/components/Modal';\nimport Button from '../../common/components/Button';\nimport Stream from '../../common/utils/Stream';\n\n/**\n * The 'RenameDiscussionModal' displays a modal dialog with an input to rename a discussion\n */\nexport default class RenameDiscussionModal extends Modal {\n oninit(vnode) {\n super.oninit(vnode);\n\n this.discussion = this.attrs.discussion;\n this.currentTitle = this.attrs.currentTitle;\n this.newTitle = Stream(this.currentTitle);\n }\n\n className() {\n return 'RenameDiscussionModal Modal--small';\n }\n\n title() {\n return app.translator.trans('core.forum.rename_discussion.title');\n }\n\n content() {\n return (\n <div className=\"Modal-body\">\n <div className=\"Form Form--centered\">\n <div className=\"Form-group\">\n <input className=\"FormControl\" bidi={this.newTitle} type=\"text\" />\n </div>\n <div className=\"Form-group\">\n {Button.component(\n {\n className: 'Button Button--primary Button--block',\n type: 'submit',\n loading: this.loading,\n },\n app.translator.trans('core.forum.rename_discussion.submit_button')\n )}\n </div>\n </div>\n </div>\n );\n }\n\n onsubmit(e) {\n e.preventDefault();\n\n this.loading = true;\n\n const title = this.newTitle();\n const currentTitle = this.currentTitle;\n\n // If the title is different to what it was before, then save it. After the\n // save has completed, update the post stream as there will be a new post\n // indicating that the discussion was renamed.\n if (title && title !== currentTitle) {\n return this.discussion\n .save({ title })\n .then(() => {\n if (app.viewingDiscussion(this.discussion)) {\n app.current.get('stream').update();\n }\n m.redraw();\n this.hide();\n })\n .catch(() => {\n this.loading = false;\n m.redraw();\n });\n } else {\n this.hide();\n }\n }\n}\n\nwindow.flreg.add('components/RenameDiscussionModal', RenameDiscussionModal)"],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[557],{2304:(t,n,o)=>{o.r(n),o.d(n,{default:()=>f});var a=o(2122),e=o(1788),r=o(9908),s=o(507),i=o(9657),p=o(2558),u=o(2587),l=o(4594),c=o(5128),f=function(t){function n(){return t.apply(this,arguments)||this}(0,e.Z)(n,t),n.initAttrs=function(n){t.initAttrs.call(this,n),n.className="SessionDropdown",n.buttonClassName="Button Button--user Button--flat",n.menuClassName="Dropdown-menu--right",n.accessibleToggleLabel=app.translator.trans("core.forum.header.session_dropdown_accessible_label")};var o=n.prototype;return o.view=function(n){return t.prototype.view.call(this,(0,a.Z)({},n,{children:this.items().toArray()}))},o.getButtonContent=function(){var t=app.session.user;return[(0,r.Z)(t)," ",m("span",{className:"Button-label"},(0,s.Z)(t))]},o.items=function(){var t=new l.Z,n=app.session.user;return t.add("profile",p.Z.component({icon:"fas fa-user",href:app.route.user(n)},app.translator.trans("core.forum.header.profile_button")),100),t.add("settings",p.Z.component({icon:"fas fa-cog",href:app.route("settings")},app.translator.trans("core.forum.header.settings_button")),50),app.forum.attribute("adminUrl")&&t.add("administration",p.Z.component({icon:"fas fa-wrench",href:app.forum.attribute("adminUrl"),target:"_blank"},app.translator.trans("core.forum.header.admin_button")),0),t.add("separator",c.Z.component(),-90),t.add("logOut",u.Z.component({icon:"fas fa-sign-out-alt",onclick:app.session.logout.bind(app.session)},app.translator.trans("core.forum.header.log_out_button")),-100),t},n}(i.Z);window.flreg.add("components/SessionDropdown",f)}}]);
//# sourceMappingURL=SessionDropdown.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/SessionDropdown.js"],"names":["SessionDropdown","initAttrs","attrs","className","buttonClassName","menuClassName","accessibleToggleLabel","app","translator","trans","view","vnode","children","this","items","toArray","getButtonContent","user","session","avatar","username","ItemList","add","LinkButton","icon","href","route","forum","attribute","target","Separator","Button","onclick","logout","bind","Dropdown","window","flreg"],"mappings":"yNAYqBA,E,6EACZC,UAAP,SAAiBC,GACf,EAAMD,UAAN,UAAgBC,GAEhBA,EAAMC,UAAY,kBAClBD,EAAME,gBAAkB,mCACxBF,EAAMG,cAAgB,uBAEtBH,EAAMI,sBAAwBC,IAAIC,WAAWC,MAAM,wD,2BAGrDC,KAAA,SAAKC,GACH,mBAAaD,KAAb,qBAAuBC,EAAvB,CAA8BC,SAAUC,KAAKC,QAAQC,c,EAGvDC,iBAAA,WACE,IAAMC,EAAOV,IAAIW,QAAQD,KAEzB,MAAO,EAACE,OAAOF,GAAO,IAAK,UAAMd,UAAU,iBAAgBiB,OAASH,M,EAQtEH,MAAA,WACE,IAAMA,EAAQ,IAAIO,IACZJ,EAAOV,IAAIW,QAAQD,KAuDzB,OArDAH,EAAMQ,IACJ,UACAC,cACE,CACEC,KAAM,cACNC,KAAMlB,IAAImB,MAAMT,KAAKA,IAEvBV,IAAIC,WAAWC,MAAM,qCAEvB,KAGFK,EAAMQ,IACJ,WACAC,cACE,CACEC,KAAM,aACNC,KAAMlB,IAAImB,MAAM,aAElBnB,IAAIC,WAAWC,MAAM,sCAEvB,IAGEF,IAAIoB,MAAMC,UAAU,aACtBd,EAAMQ,IACJ,iBACAC,cACE,CACEC,KAAM,gBACNC,KAAMlB,IAAIoB,MAAMC,UAAU,YAC1BC,OAAQ,UAEVtB,IAAIC,WAAWC,MAAM,mCAEvB,GAIJK,EAAMQ,IAAI,YAAaQ,iBAAwB,IAE/ChB,EAAMQ,IACJ,SACAS,cACE,CACEP,KAAM,sBACNQ,QAASzB,IAAIW,QAAQe,OAAOC,KAAK3B,IAAIW,UAEvCX,IAAIC,WAAWC,MAAM,sCAEtB,KAGIK,G,GAnFkCqB,KAuF7CC,OAAOC,MAAMf,IAAI,6BAA8BtB","file":"forum/components/SessionDropdown.js","sourcesContent":["import avatar from '../../common/helpers/avatar';\nimport username from '../../common/helpers/username';\nimport Dropdown from '../../common/components/Dropdown';\nimport LinkButton from '../../common/components/LinkButton';\nimport Button from '../../common/components/Button';\nimport ItemList from '../../common/utils/ItemList';\nimport Separator from '../../common/components/Separator';\n\n/**\n * The `SessionDropdown` component shows a button with the current user's\n * avatar/name, with a dropdown of session controls.\n */\nexport default class SessionDropdown extends Dropdown {\n static initAttrs(attrs) {\n super.initAttrs(attrs);\n\n attrs.className = 'SessionDropdown';\n attrs.buttonClassName = 'Button Button--user Button--flat';\n attrs.menuClassName = 'Dropdown-menu--right';\n\n attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label');\n }\n\n view(vnode) {\n return super.view({ ...vnode, children: this.items().toArray() });\n }\n\n getButtonContent() {\n const user = app.session.user;\n\n return [avatar(user), ' ', <span className=\"Button-label\">{username(user)}</span>];\n }\n\n /**\n * Build an item list for the contents of the dropdown menu.\n *\n * @return {ItemList}\n */\n items() {\n const items = new ItemList();\n const user = app.session.user;\n\n items.add(\n 'profile',\n LinkButton.component(\n {\n icon: 'fas fa-user',\n href: app.route.user(user),\n },\n app.translator.trans('core.forum.header.profile_button')\n ),\n 100\n );\n\n items.add(\n 'settings',\n LinkButton.component(\n {\n icon: 'fas fa-cog',\n href: app.route('settings'),\n },\n app.translator.trans('core.forum.header.settings_button')\n ),\n 50\n );\n\n if (app.forum.attribute('adminUrl')) {\n items.add(\n 'administration',\n LinkButton.component(\n {\n icon: 'fas fa-wrench',\n href: app.forum.attribute('adminUrl'),\n target: '_blank',\n },\n app.translator.trans('core.forum.header.admin_button')\n ),\n 0\n );\n }\n\n items.add('separator', Separator.component(), -90);\n\n items.add(\n 'logOut',\n Button.component(\n {\n icon: 'fas fa-sign-out-alt',\n onclick: app.session.logout.bind(app.session),\n },\n app.translator.trans('core.forum.header.log_out_button')\n ),\n -100\n );\n\n return items;\n }\n}\n\nwindow.flreg.add('components/SessionDropdown', SessionDropdown)"],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[826],{6991:(t,n,e)=>{e.d(n,{Z:()=>c});var i=e(1788),a=e(8931),o=e(5971),r=e(9100),s=e(4594),c=function(t){function n(){return t.apply(this,arguments)||this}(0,i.Z)(n,t);var e=n.prototype;return e.oninit=function(n){t.prototype.oninit.call(this,n),this.methods=this.notificationMethods().toArray(),this.loading={},this.types=this.notificationTypes().toArray()},e.view=function(){var t=this,n=this.attrs.user.preferences();return m("table",{className:"NotificationGrid"},m("thead",null,m("tr",null,m("td",null),this.methods.map((function(n){return m("th",{className:"NotificationGrid-groupToggle",onclick:t.toggleMethod.bind(t,n.name)},(0,r.Z)(n.icon)," ",n.label)})))),m("tbody",null,this.types.map((function(e){return m("tr",null,m("td",{className:"NotificationGrid-groupToggle",onclick:t.toggleType.bind(t,e.name)},(0,r.Z)(e.icon)," ",e.label),t.methods.map((function(i){var a=t.preferenceKey(e.name,i.name);return m("td",{className:"NotificationGrid-checkbox"},m(o.Z,{state:!!n[a],loading:t.loading[a],disabled:!(a in n),onchange:t.toggle.bind(t,[a])}))})))}))))},e.oncreate=function(n){t.prototype.oncreate.call(this,n),this.$("thead .NotificationGrid-groupToggle").bind("mouseenter mouseleave",(function(t){var n=parseInt($(this).index(),10)+1;$(this).parents("table").find("td:nth-child("+n+")").toggleClass("highlighted","mouseenter"===t.type)})),this.$("tbody .NotificationGrid-groupToggle").bind("mouseenter mouseleave",(function(t){$(this).parent().find("td").toggleClass("highlighted","mouseenter"===t.type)}))},e.toggle=function(t){var n=this,e=this.attrs.user,i=e.preferences(),a=!i[t[0]];t.forEach((function(t){n.loading[t]=!0,i[t]=a})),m.redraw(),e.save({preferences:i}).then((function(){t.forEach((function(t){return n.loading[t]=!1})),m.redraw()}))},e.toggleMethod=function(t){var n=this,e=this.types.map((function(e){return n.preferenceKey(e.name,t)})).filter((function(t){return t in n.attrs.user.preferences()}));this.toggle(e)},e.toggleType=function(t){var n=this,e=this.methods.map((function(e){return n.preferenceKey(t,e.name)})).filter((function(t){return t in n.attrs.user.preferences()}));this.toggle(e)},e.preferenceKey=function(t,n){return"notify_"+t+"_"+n},e.notificationMethods=function(){var t=new s.Z;return t.add("alert",{name:"alert",icon:"fas fa-bell",label:app.translator.trans("core.forum.settings.notify_by_web_heading")}),t.add("email",{name:"email",icon:"far fa-envelope",label:app.translator.trans("core.forum.settings.notify_by_email_heading")}),t},e.notificationTypes=function(){var t=new s.Z;return t.add("discussionRenamed",{name:"discussionRenamed",icon:"fas fa-pencil-alt",label:app.translator.trans("core.forum.settings.notify_discussion_renamed_label")}),t},n}(a.Z);window.flreg.add("components/NotificationGrid",c)},5792:(t,n,e)=>{e.a(t,(async t=>{e.r(n),e.d(n,{default:()=>u});var i=e(1788),a=e(4594),o=e(833),r=e(2587),s=e(4926),c=e(6991),l=e(7473),u=function(t){function n(){return t.apply(this,arguments)||this}(0,i.Z)(n,t);var u=n.prototype;return u.oninit=function(n){t.prototype.oninit.call(this,n),this.show(app.session.user),app.setTitle(app.translator.trans("core.forum.settings.title"))},u.content=function(){return m("div",{className:"SettingsPage"},m("ul",null,(0,l.Z)(this.settingsItems().toArray())))},u.settingsItems=function(){var t=this,n=new a.Z;return["account","notifications","privacy"].forEach((function(e){n.add(e,m(s.Z,{className:"Settings-"+e,label:app.translator.trans("core.forum.settings."+e+"_heading")},t[e+"Items"]().toArray()))})),n},u.accountItems=function(){var t=new a.Z,n=function(){return e.e(372).then(e.bind(e,4146))},i=function(){return e.e(503).then(e.bind(e,22))};return t.add("changePassword",m(r.Z,{className:"Button",onclick:function(){return app.modal.show(i)}},app.translator.trans("core.forum.settings.change_password_button"))),t.add("changeEmail",m(r.Z,{className:"Button",onclick:function(){return app.modal.show(n)}},app.translator.trans("core.forum.settings.change_email_button"))),t},u.notificationsItems=function(){var t=new a.Z;return t.add("notificationGrid",m(c.Z,{user:this.user})),t},u.privacyItems=function(){var t=this,n=new a.Z;return n.add("discloseOnline",m(o.Z,{state:this.user.preferences().discloseOnline,onchange:function(n){t.discloseOnlineLoading=!0,t.user.savePreferences({discloseOnline:n}).then((function(){t.discloseOnlineLoading=!1,m.redraw()}))},loading:this.discloseOnlineLoading},app.translator.trans("core.forum.settings.privacy_disclose_online_label"))),n},n}((await e.e(251).then(e.bind(e,6068))).default);window.flreg.add("components/SettingsPage",u),t()}),1)}}]);
//# sourceMappingURL=SettingsPage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[395],{7527:(t,a,s)=>{s.d(a,{Z:()=>n});var r=s(1788),e=s(8931),i=s(4594),n=function(t){function a(){return t.apply(this,arguments)||this}(0,r.Z)(a,t);var s=a.prototype;return s.view=function(){return m("div",{className:"LogInButtons"},this.items().toArray())},s.items=function(){return new i.Z},a}(e.Z);window.flreg.add("components/LogInButtons",n)},3235:(t,a,s)=>{s.r(a),s.d(a,{default:()=>u});var r=s(1788),e=s(5556),i=s(2587),n=s(7527),o=s(5731),l=s(4594),d=s(1024),u=function(t){function a(){return t.apply(this,arguments)||this}(0,r.Z)(a,t);var e=a.prototype;return e.oninit=function(a){t.prototype.oninit.call(this,a),this.username=(0,d.Z)(this.attrs.username||""),this.email=(0,d.Z)(this.attrs.email||""),this.password=(0,d.Z)(this.attrs.password||"")},e.className=function(){return"Modal--small SignUpModal"},e.title=function(){return app.translator.trans("core.forum.sign_up.title")},e.content=function(){return[m("div",{className:"Modal-body"},this.body()),m("div",{className:"Modal-footer"},this.footer())]},e.isProvided=function(t){return this.attrs.provided&&-1!==this.attrs.provided.indexOf(t)},e.body=function(){return[this.attrs.token?"":m(n.Z,null),m("div",{className:"Form Form--centered"},this.fields().toArray())]},e.fields=function(){var t=new l.Z;return t.add("username",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"username",type:"text",placeholder:(0,o.Z)(app.translator.trans("core.forum.sign_up.username_placeholder")),bidi:this.username,disabled:this.loading||this.isProvided("username")})),30),t.add("email",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"email",type:"email",placeholder:(0,o.Z)(app.translator.trans("core.forum.sign_up.email_placeholder")),bidi:this.email,disabled:this.loading||this.isProvided("email")})),20),this.attrs.token||t.add("password",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"password",type:"password",placeholder:(0,o.Z)(app.translator.trans("core.forum.sign_up.password_placeholder")),bidi:this.password,disabled:this.loading})),10),t.add("submit",m("div",{className:"Form-group"},m(i.Z,{className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.sign_up.submit_button"))),-10),t},e.footer=function(){return[m("p",{className:"SignUpModal-logIn"},app.translator.trans("core.forum.sign_up.log_in_text",{a:m("a",{onclick:this.logIn.bind(this)})}))]},e.logIn=function(){var t={identification:this.email()||this.username(),password:this.password()};app.modal.show((function(){return s.e(460).then(s.bind(s,1498))}),t)},e.onready=function(){this.attrs.username&&!this.attrs.email?this.$("[name=email]").select():this.$("[name=username]").select()},e.onsubmit=function(t){t.preventDefault(),this.loading=!0;var a=this.submitData();app.request({url:app.forum.attribute("baseUrl")+"/register",method:"POST",body:a,errorHandler:this.onerror.bind(this)}).then((function(){return window.location.reload()}),this.loaded.bind(this))},e.submitData=function(){var t={username:this.username(),email:this.email()};return this.attrs.token?t.token=this.attrs.token:t.password=this.password(),t},a}(e.Z);window.flreg.add("components/SignUpModal",u)}}]);
//# sourceMappingURL=SignUpModal.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +0,0 @@
/*!
* Block below copied from Protovis: http://mbostock.github.com/protovis/
* Copyright 2010 Stanford Visualization Group
* Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
*/
/*!
* Color Thief v2.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com
*
* Thanks
* ------
* Nick Rabinowitz - For creating quantize.js.
* John Schulz - For clean up and optimization. @JFSIII
* Nathan Spady - For adding drag and drop support to the demo page.
*
* License
* -------
* Copyright 2011, 2015 Lokesh Dhakar
* Released under the MIT license
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
*
*/
/*!
* quantize.js Copyright 2008 Nick Rabinowitz.
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
(()=>{var e,o,r,t,n,a,s,i,u={},c={};function m(e){var o=c[e];if(void 0!==o)return o.exports;var r=c[e]={exports:{}};return u[e].call(r.exports,r,r.exports,m),r.exports}m.m=u,e="function"==typeof Symbol?Symbol("webpack then"):"__webpack_then__",o="function"==typeof Symbol?Symbol("webpack exports"):"__webpack_exports__",r=e=>{e&&(e.forEach((e=>e.r--)),e.forEach((e=>e.r--?e.r++:e())))},t=e=>!--e.r&&e(),n=(e,o)=>e?e.push(o):t(o),m.a=(a,s,i)=>{var u,c,m,p=i&&[],f=a.exports,l=!0,d=!1,h=(o,r,t)=>{d||(d=!0,r.r+=o.length,o.map(((o,n)=>o[e](r,t))),d=!1)},b=new Promise(((e,o)=>{m=o,c=()=>(e(f),r(p),p=0)}));b[o]=f,b[e]=(e,o)=>{if(l)return t(e);u&&h(u,e,o),n(p,e),b.catch(o)},a.exports=b,s((a=>{if(!a)return c();var s,i;u=(a=>a.map((a=>{if(null!==a&&"object"==typeof a){if(a[e])return a;if(a.then){var s=[];a.then((e=>{i[o]=e,r(s),s=0}));var i={[e]:(e,o)=>(n(s,e),a.catch(o))};return i}}return{[e]:e=>t(e),[o]:a}})))(a);var m=new Promise(((e,r)=>{(s=()=>e(i=u.map((e=>e[o])))).r=0,h(u,s,r)}));return s.r?m:i})).then(c,m),l=!1},a=[],m.O=(e,o,r,t)=>{if(!o){var n=1/0;for(u=0;u<a.length;u++){for(var[o,r,t]=a[u],s=!0,i=0;i<o.length;i++)(!1&t||n>=t)&&Object.keys(m.O).every((e=>m.O[e](o[i])))?o.splice(i--,1):(s=!1,t<n&&(n=t));s&&(a.splice(u--,1),e=r())}return e}t=t||0;for(var u=a.length;u>0&&a[u-1][2]>t;u--)a[u]=a[u-1];a[u]=[o,r,t]},m.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return m.d(o,{a:o}),o},m.d=(e,o)=>{for(var r in o)m.o(o,r)&&!m.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},m.f={},m.e=e=>Promise.all(Object.keys(m.f).reduce(((o,r)=>(m.f[r](e,o),o)),[])),m.u=e=>({28:"forum/components/PostsUserPage",77:"forum/components/NotificationList",192:"forum/components/Post",247:"common/components/TextEditor",251:"forum/components/UserPage",293:"forum/components/EditPostComposer",352:"forum/components/EditUserModal",372:"forum/components/ChangeEmailModal",395:"forum/components/SignUpModal",437:"forum/components/DiscussionComposer",460:"forum/components/LogInModal",502:"forum/components/ForgotPasswordModal",503:"forum/components/ChangePasswordModal",557:"forum/components/SessionDropdown",563:"forum/components/PostStream",564:"forum/components/RenameDiscussionModal",765:"forum/components/NotificationsDropdown",779:"forum/components/PostUser",799:"forum/components/DiscussionsUserPage",826:"forum/components/SettingsPage",847:"forum/components/IndexPage"}[e]+".js"),m.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),m.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),s={},i="@flarum/core:",m.l=(e,o,r,t)=>{if(s[e])s[e].push(o);else{var n,a;if(void 0!==r)for(var u=document.getElementsByTagName("script"),c=0;c<u.length;c++){var p=u[c];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==i+r){n=p;break}}n||(a=!0,(n=document.createElement("script")).charset="utf-8",n.timeout=120,m.nc&&n.setAttribute("nonce",m.nc),n.setAttribute("data-webpack",i+r),n.src=e),s[e]=[o];var f=(o,r)=>{n.onerror=n.onload=null,clearTimeout(l);var t=s[e];if(delete s[e],n.parentNode&&n.parentNode.removeChild(n),t&&t.forEach((e=>e(r))),o)return o(r)},l=setTimeout(f.bind(null,void 0,{type:"timeout",target:n}),12e4);n.onerror=f.bind(null,n.onerror),n.onload=f.bind(null,n.onload),a&&document.head.appendChild(n)}},m.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},m.p="/assets/",(()=>{var e={406:0};m.f.j=(o,r)=>{var t=m.o(e,o)?e[o]:void 0;if(0!==t)if(t)r.push(t[2]);else if(406!=o){var n=new Promise(((r,n)=>t=e[o]=[r,n]));r.push(t[2]=n);var a=m.p+m.u(o),s=new Error;m.l(a,(r=>{if(m.o(e,o)&&(0!==(t=e[o])&&(e[o]=void 0),t)){var n=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;s.message="Loading chunk "+o+" failed.\n("+n+": "+a+")",s.name="ChunkLoadError",s.type=n,s.request=a,t[1](s)}}),"chunk-"+o,o)}else e[o]=0},m.O.j=o=>0===e[o];var o=(o,r)=>{var t,n,[a,s,i]=r,u=0;for(t in s)m.o(s,t)&&(m.m[t]=s[t]);for(i&&i(m),o&&o(r);u<a.length;u++)n=a[u],m.o(e,n)&&e[n]&&e[n][0](),e[a[u]]=0;m.O()},r=self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[];r.forEach(o.bind(null,0)),r.push=o.bind(null,r.push.bind(r))})(),m.O()})();
//# sourceMappingURL=runtime.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,24 +0,0 @@
/*!
* Sizzle CSS Selector Engine v2.3.6
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://js.foundation/
*
* Date: 2021-02-16
*/
/*!
* jQuery JavaScript Library v3.6.0
* https://jquery.com/
*
* Includes Sizzle.js
* https://sizzlejs.com/
*
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2021-03-02T17:08Z
*/

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@
*/
export * from './src/common';
export * from './src/forum';
export * from './src/forum';

11014
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,41 +2,28 @@
"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",
"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",
"m.attrs.bidi": "github:tobscure/m.attrs.bidi",
"mithril": "^2.0.4",
"punycode": "^2.1.1",
"spin.js": "^3.1.0",
"textarea-caret": "^3.1.0",
"webpack-visualizer-plugin": "^0.1.11",
"zepto": "^1.2.0"
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-merge": "^4.1.4"
},
"devDependencies": {
"@babel/plugin-proposal-export-default-from": "^7.12.13",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-top-level-await": "^7.12.13",
"@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",
"babel-plugin-add-module-exports": "^1.0.4",
"bundlewatch": "^0.3.2",
"expose-loader": "^2.0.0",
"flarum-webpack-config": "0.1.0-beta.10",
"husky": "^4.3.8",
"loader-utils": "^1.4.0",
"prettier": "^2.2.1",
"webpack": "~5.33.2",
"webpack-bundle-analyzer": "^4.4.1",
"webpack-cli": "~4.6.0",
"webpack-merge": "~5.7.3"
"husky": "^4.2.5",
"prettier": "2.0.2"
},
"scripts": {
"dev": "webpack --mode development --watch",

17
js/shims.d.ts vendored
View File

@@ -8,8 +8,6 @@ import * as _$ from 'jquery';
// Globals from flarum/core
import Application from './src/common/Application';
import FlarumRegistry from './src/common/FlarumRegistry';
/**
* flarum/core exposes several extensions globally:
*
@@ -21,21 +19,9 @@ import FlarumRegistry from './src/common/FlarumRegistry';
* 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;
}
}
/**
@@ -43,5 +29,4 @@ declare global {
*/
declare global {
const app: Application;
const flreg: FlarumRegistry;
}

View File

@@ -1,18 +1,27 @@
import HeaderPrimary from './components/HeaderPrimary';
import HeaderSecondary from './components/HeaderSecondary';
import routes from './routes';
import ExtensionPage from './components/ExtensionPage';
import Application from '../common/Application';
import Navigation from '../common/components/Navigation';
import AdminNav from './components/AdminNav';
import ExtensionData from './utils/ExtensionData';
export default class AdminApplication extends Application {
// Deprecated as of beta 15
extensionSettings = {};
extensionData = new ExtensionData();
extensionCategories = {
feature: 30,
theme: 20,
discussion: 70,
moderation: 60,
feature: 50,
formatting: 40,
theme: 30,
authentication: 20,
language: 10,
other: 0,
};
history = {
@@ -52,6 +61,14 @@ export default class AdminApplication extends Application {
m.mount(document.getElementById('header-primary'), HeaderPrimary);
m.mount(document.getElementById('header-secondary'), HeaderSecondary);
m.mount(document.getElementById('admin-navigation'), AdminNav);
// If an extension has just been enabled, then we will run its settings
// callback.
const enabled = localStorage.getItem('enabledExtension');
if (enabled && this.extensionSettings[enabled] && typeof this.extensionSettings[enabled] === 'function') {
this.extensionSettings[enabled]();
localStorage.removeItem('enabledExtension');
}
}
getRequiredPermissions(permission) {

View File

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

View File

@@ -8,7 +8,6 @@ import SettingDropdown from './components/SettingDropdown';
import EditCustomFooterModal from './components/EditCustomFooterModal';
import SessionDropdown from './components/SessionDropdown';
import HeaderPrimary from './components/HeaderPrimary';
import AdminPage from './components/AdminPage';
import AppearancePage from './components/AppearancePage';
import StatusWidget from './components/StatusWidget';
import ExtensionsWidget from './components/ExtensionsWidget';
@@ -17,8 +16,8 @@ import SettingsModal from './components/SettingsModal';
import DashboardWidget from './components/DashboardWidget';
import ExtensionPage from './components/ExtensionPage';
import ExtensionLinkButton from './components/ExtensionLinkButton';
import AdminLinkButton from './components/AdminLinkButton';
import PermissionGrid from './components/PermissionGrid';
import ExtensionPermissionGrid from './components/ExtensionPermissionGrid';
import MailPage from './components/MailPage';
import UploadImageButton from './components/UploadImageButton';
import LoadingModal from './components/LoadingModal';
@@ -43,7 +42,6 @@ export default Object.assign(compat, {
'components/EditCustomFooterModal': EditCustomFooterModal,
'components/SessionDropdown': SessionDropdown,
'components/HeaderPrimary': HeaderPrimary,
'components/AdminPage': AdminPage,
'components/AppearancePage': AppearancePage,
'components/StatusWidget': StatusWidget,
'components/ExtensionsWidget': ExtensionsWidget,
@@ -52,8 +50,8 @@ export default Object.assign(compat, {
'components/DashboardWidget': DashboardWidget,
'components/ExtensionPage': ExtensionPage,
'components/ExtensionLinkButton': ExtensionLinkButton,
'components/AdminLinkButton': AdminLinkButton,
'components/PermissionGrid': PermissionGrid,
'components/ExtensionPermissionGrid': ExtensionPermissionGrid,
'components/MailPage': MailPage,
'components/UploadImageButton': UploadImageButton,
'components/LoadingModal': LoadingModal,

View File

@@ -0,0 +1,32 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import Modal from '../../common/components/Modal';
export default class AddExtensionModal extends Modal {
className() {
return 'AddExtensionModal Modal--small';
}
title() {
return app.translator.trans('core.admin.add_extension.title');
}
content() {
return (
<div className="Modal-body">
<p>{app.translator.trans('core.admin.add_extension.temporary_text')}</p>
<p>
{app.translator.trans('core.admin.add_extension.install_text', { a: <a href="https://discuss.flarum.org/t/extensions" target="_blank" /> })}
</p>
<p>{app.translator.trans('core.admin.add_extension.developer_text', { a: <a href="http://flarum.org/docs/extend" target="_blank" /> })}</p>
</div>
);
}
}

View File

@@ -0,0 +1,16 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import LinkButton from '../../common/components/LinkButton';
export default class AdminLinkButton extends LinkButton {
getButtonContent(children) {
return [...super.getButtonContent(children), <div className="AdminLinkButton-description">{this.attrs.description}</div>];
}
}

View File

@@ -21,34 +21,6 @@ export default class AdminNav extends Component {
);
}
oncreate(vnode) {
super.oncreate(vnode);
this.scrollToActive();
}
onupdate() {
this.scrollToActive();
}
scrollToActive() {
const children = $('.Dropdown-menu').children('.active');
const nav = $('#admin-navigation');
const time = app.previous.type ? 250 : 0;
if (
children.length > 0 &&
(children[0].offsetTop > nav.scrollTop() + nav.outerHeight() || children[0].offsetTop + children[0].offsetHeight < nav.scrollTop())
) {
nav.animate(
{
scrollTop: children[0].offsetTop - nav.height() / 2,
},
time
);
}
}
/**
* Build an item list of main links to show in the admin navigation.
*
@@ -57,8 +29,6 @@ export default class AdminNav extends Component {
items() {
const items = new ItemList();
items.add('category-core', <h4 className="ExtensionListTitle">{app.translator.trans('core.admin.nav.categories.core')}</h4>);
items.add(
'dashboard',
<LinkButton href={app.route('dashboard')} icon="far fa-chart-bar" title={app.translator.trans('core.admin.nav.dashboard_title')}>
@@ -118,7 +88,7 @@ export default class AdminNav extends Component {
Object.keys(categorizedExtensions).map((category) => {
if (!this.query()) {
items.add(
`category-${category}`,
category,
<h4 className="ExtensionListTitle">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>,
categories[category]
);
@@ -126,17 +96,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}`,
extension.id,
<ExtensionLinkButton
href={app.route('extension', { id: extension.id })}
extensionId={extension.id}
className="ExtensionNavButton"
title={description}
title={extension.description}
>
{title}
</ExtensionLinkButton>,

View File

@@ -1,180 +0,0 @@
import Page from '../../common/components/Page';
import Button from '../../common/components/Button';
import Switch from '../../common/components/Switch';
import Select from '../../common/components/Select';
import classList from '../../common/utils/classList';
import Stream from '../../common/utils/Stream';
import saveSettings from '../utils/saveSettings';
import AdminHeader from './AdminHeader';
export default class AdminPage extends Page {
oninit(vnode) {
super.oninit(vnode);
this.settings = {};
this.loading = false;
}
view() {
const className = classList(['AdminPage', this.headerInfo().className]);
return (
<div className={className}>
{this.header()}
<div className="container">{this.content()}</div>
</div>
);
}
content() {
return '';
}
submitButton() {
return (
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
{app.translator.trans('core.admin.settings.submit_button')}
</Button>
);
}
header() {
const headerInfo = this.headerInfo();
return (
<AdminHeader icon={headerInfo.icon} description={headerInfo.description} className={headerInfo.className + '-header'}>
{headerInfo.title}
</AdminHeader>
);
}
headerInfo() {
return {
className: '',
icon: '',
title: '',
description: '',
};
}
/**
* buildSettingComponent takes a settings object and turns it into a component.
* Depending on the type of input, you can set the type to 'bool', 'select', or
* any standard <input> type. Any values inside the 'extra' object will be added
* to the component as an attribute.
*
* Alternatively, you can pass a callback that will be executed in ExtensionPage's
* context to include custom JSX elements.
*
* @example
*
* {
* setting: 'acme.checkbox',
* label: app.translator.trans('acme.admin.setting_label'),
* type: 'bool',
* help: app.translator.trans('acme.admin.setting_help'),
* className: 'Setting-item'
* }
*
* @example
*
* {
* setting: 'acme.select',
* label: app.translator.trans('acme.admin.setting_label'),
* type: 'select',
* options: {
* 'option1': 'Option 1 label',
* 'option2': 'Option 2 label',
* },
* default: 'option1',
* }
*
* @param setting
* @returns {JSX.Element}
*/
buildSettingComponent(entry) {
if (typeof entry === 'function') {
return entry.call(this);
}
const { setting, help, ...componentAttrs } = entry;
delete componentAttrs.help;
const value = this.setting([setting])();
if (['bool', 'checkbox', 'switch', 'boolean'].includes(componentAttrs.type)) {
return (
<div className="Form-group">
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...componentAttrs}>
{componentAttrs.label}
</Switch>
<div className="helpText">{help}</div>
</div>
);
} else if (['select', 'dropdown', 'selectdropdown'].includes(componentAttrs.type)) {
return (
<div className="Form-group">
<label>{componentAttrs.label}</label>
<div className="helpText">{help}</div>
<Select
value={value || componentAttrs.default}
options={componentAttrs.options}
buttonClassName="Button"
onchange={this.settings[setting]}
{...componentAttrs}
/>
</div>
);
} else {
componentAttrs.className = classList(['FormControl', componentAttrs.className]);
return (
<div className="Form-group">
{componentAttrs.label ? <label>{componentAttrs.label}</label> : ''}
<div className="helpText">{help}</div>
<input type={componentAttrs.type} bidi={this.setting(setting)} {...componentAttrs} />
</div>
);
}
}
onsaved() {
this.loading = false;
app.alerts.show({ type: 'success' }, app.translator.trans('core.admin.settings.saved_message'));
}
setting(key, fallback = '') {
this.settings[key] = this.settings[key] || Stream(app.data.settings[key] || fallback);
return this.settings[key];
}
dirty() {
const dirty = {};
Object.keys(this.settings).forEach((key) => {
const value = this.settings[key]();
if (value !== app.data.settings[key]) {
dirty[key] = value;
}
});
return dirty;
}
isChanged() {
return Object.keys(this.dirty()).length;
}
saveSettings(e) {
e.preventDefault();
app.alerts.clear();
this.loading = true;
return saveSettings(this.dirty()).then(this.onsaved.bind(this));
}
}

View File

@@ -1,120 +1,141 @@
import Page from '../../common/components/Page';
import Button from '../../common/components/Button';
import Switch from '../../common/components/Switch';
import Stream from '../../common/utils/Stream';
import EditCustomCssModal from './EditCustomCssModal';
import EditCustomHeaderModal from './EditCustomHeaderModal';
import EditCustomFooterModal from './EditCustomFooterModal';
import UploadImageButton from './UploadImageButton';
import AdminPage from './AdminPage';
import saveSettings from '../utils/saveSettings';
import AdminHeader from './AdminHeader';
export default class AppearancePage extends AdminPage {
headerInfo() {
return {
className: 'AppearancePage',
icon: 'fas fa-paint-brush',
title: app.translator.trans('core.admin.appearance.title'),
description: app.translator.trans('core.admin.appearance.description'),
};
export default class AppearancePage extends Page {
oninit(vnode) {
super.oninit(vnode);
this.primaryColor = Stream(app.data.settings.theme_primary_color);
this.secondaryColor = Stream(app.data.settings.theme_secondary_color);
this.darkMode = Stream(app.data.settings.theme_dark_mode);
this.coloredHeader = Stream(app.data.settings.theme_colored_header);
}
content() {
return [
<div className="Form">
<fieldset className="AppearancePage-colors">
<legend>{app.translator.trans('core.admin.appearance.colors_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.colors_text')}</div>
view() {
return (
<div className="AppearancePage">
<AdminHeader
icon="fas fa-paint-brush"
description={app.translator.trans('core.admin.appearance.description')}
className="AppearancePage-header"
>
{app.translator.trans('core.admin.appearance.title')}
</AdminHeader>
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
<fieldset className="AppearancePage-colors">
<legend>{app.translator.trans('core.admin.appearance.colors_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.colors_text')}</div>
<div className="AppearancePage-colors-input">
{this.buildSettingComponent({
type: 'text',
setting: 'theme_primary_color',
placeholder: '#aaaaaa',
})}
{this.buildSettingComponent({
type: 'text',
setting: 'theme_secondary_color',
placeholder: '#aaaaaa',
})}
</div>
<div className="AppearancePage-colors-input">
<input className="FormControl" type="text" placeholder="#aaaaaa" bidi={this.primaryColor} />
<input className="FormControl" type="text" placeholder="#aaaaaa" bidi={this.secondaryColor} />
</div>
{this.buildSettingComponent({
type: 'switch',
setting: 'theme_dark_mode',
label: app.translator.trans('core.admin.appearance.dark_mode_label'),
})}
{Switch.component(
{
state: this.darkMode(),
onchange: this.darkMode,
},
app.translator.trans('core.admin.appearance.dark_mode_label')
)}
{this.buildSettingComponent({
type: 'switch',
setting: 'theme_colored_header',
label: app.translator.trans('core.admin.appearance.colored_header_label'),
})}
{Switch.component(
{
state: this.coloredHeader(),
onchange: this.coloredHeader,
},
app.translator.trans('core.admin.appearance.colored_header_label')
)}
{this.submitButton()}
</fieldset>
</div>,
{Button.component(
{
className: 'Button Button--primary',
type: 'submit',
loading: this.loading,
},
app.translator.trans('core.admin.appearance.submit_button')
)}
</fieldset>
</form>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
<UploadImageButton name="logo" />
</fieldset>,
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
<UploadImageButton name="logo" />
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
<UploadImageButton name="favicon" />
</fieldset>,
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
<UploadImageButton name="favicon" />
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
{Button.component(
{
className: 'Button',
onclick: () => app.modal.show(EditCustomHeaderModal),
},
app.translator.trans('core.admin.appearance.edit_header_button')
)}
</fieldset>,
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
{Button.component(
{
className: 'Button',
onclick: () => app.modal.show(EditCustomHeaderModal),
},
app.translator.trans('core.admin.appearance.edit_header_button')
)}
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
{Button.component(
{
className: 'Button',
onclick: () => app.modal.show(EditCustomFooterModal),
},
app.translator.trans('core.admin.appearance.edit_footer_button')
)}
</fieldset>,
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
{Button.component(
{
className: 'Button',
onclick: () => app.modal.show(EditCustomFooterModal),
},
app.translator.trans('core.admin.appearance.edit_footer_button')
)}
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
{Button.component(
{
className: 'Button',
onclick: () => app.modal.show(EditCustomCssModal),
},
app.translator.trans('core.admin.appearance.edit_css_button')
)}
</fieldset>,
];
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
{Button.component(
{
className: 'Button',
onclick: () => app.modal.show(EditCustomCssModal),
},
app.translator.trans('core.admin.appearance.edit_css_button')
)}
</fieldset>
</div>
</div>
);
}
onsaved() {
window.location.reload();
}
saveSettings(e) {
onsubmit(e) {
e.preventDefault();
const hex = /^#[0-9a-f]{3}([0-9a-f]{3})?$/i;
if (!hex.test(this.settings['theme_primary_color']()) || !hex.test(this.settings['theme_secondary_color']())) {
if (!hex.test(this.primaryColor()) || !hex.test(this.secondaryColor())) {
alert(app.translator.trans('core.admin.appearance.enter_hex_message'));
return;
}
super.saveSettings(e);
this.loading = true;
saveSettings({
theme_primary_color: this.primaryColor(),
theme_secondary_color: this.secondaryColor(),
theme_dark_mode: this.darkMode(),
theme_colored_header: this.coloredHeader(),
}).then(() => window.location.reload());
}
}

View File

@@ -1,11 +1,31 @@
import Page from '../../common/components/Page';
import FieldSet from '../../common/components/FieldSet';
import Select from '../../common/components/Select';
import Button from '../../common/components/Button';
import saveSettings from '../utils/saveSettings';
import ItemList from '../../common/utils/ItemList';
import AdminPage from './AdminPage';
import Switch from '../../common/components/Switch';
import Stream from '../../common/utils/Stream';
import withAttr from '../../common/utils/withAttr';
import AdminHeader from './AdminHeader';
export default class BasicsPage extends AdminPage {
export default class BasicsPage extends Page {
oninit(vnode) {
super.oninit(vnode);
this.loading = false;
this.fields = [
'forum_title',
'forum_description',
'default_locale',
'show_language_selector',
'default_route',
'welcome_title',
'welcome_message',
'display_name_driver',
];
this.localeOptions = {};
const locales = app.data.locales;
for (const i in locales) {
@@ -20,99 +40,157 @@ export default class BasicsPage extends AdminPage {
this.slugDriverOptions = {};
Object.keys(app.data.slugDrivers).forEach((model) => {
this.fields.push(`slug_driver_${model}`);
this.slugDriverOptions[model] = {};
app.data.slugDrivers[model].forEach((option) => {
this.slugDriverOptions[model][option] = option;
});
});
this.values = {};
const settings = app.data.settings;
this.fields.forEach((key) => (this.values[key] = Stream(settings[key])));
if (!this.values.display_name_driver() && displayNameDrivers.includes('username')) this.values.display_name_driver('username');
Object.keys(app.data.slugDrivers).forEach((model) => {
if (!this.values[`slug_driver_${model}`]() && 'default' in this.slugDriverOptions[model]) {
this.values[`slug_driver_${model}`]('default');
}
});
if (typeof this.values.show_language_selector() !== 'number') this.values.show_language_selector(1);
}
headerInfo() {
return {
className: 'BasicsPage',
icon: 'fas fa-pencil-alt',
title: app.translator.trans('core.admin.basics.title'),
description: app.translator.trans('core.admin.basics.description'),
};
}
view() {
return (
<div className="BasicsPage">
<AdminHeader icon="fas fa-pencil-alt" description={app.translator.trans('core.admin.basics.description')} className="BasicsPage-header">
{app.translator.trans('core.admin.basics.title')}
</AdminHeader>
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
{FieldSet.component(
{
label: app.translator.trans('core.admin.basics.forum_title_heading'),
},
[<input className="FormControl" bidi={this.values.forum_title} />]
)}
content() {
return [
<div className="Form">
{this.buildSettingComponent({
type: 'text',
setting: 'forum_title',
label: app.translator.trans('core.admin.basics.forum_title_heading'),
})}
{this.buildSettingComponent({
type: 'text',
setting: 'forum_description',
label: app.translator.trans('core.admin.basics.forum_description_heading'),
help: app.translator.trans('core.admin.basics.forum_description_text'),
})}
{FieldSet.component(
{
label: app.translator.trans('core.admin.basics.forum_description_heading'),
},
[
<div className="helpText">{app.translator.trans('core.admin.basics.forum_description_text')}</div>,
<textarea className="FormControl" bidi={this.values.forum_description} />,
]
)}
{Object.keys(this.localeOptions).length > 1
? [
this.buildSettingComponent({
type: 'select',
setting: 'default_locale',
options: this.localeOptions,
label: app.translator.trans('core.admin.basics.default_language_heading'),
}),
this.buildSettingComponent({
type: 'switch',
setting: 'show_language_selector',
label: app.translator.trans('core.admin.basics.show_language_selector_label'),
}),
]
: ''}
{Object.keys(this.localeOptions).length > 1
? FieldSet.component(
{
label: app.translator.trans('core.admin.basics.default_language_heading'),
},
[
Select.component({
options: this.localeOptions,
value: this.values.default_locale(),
onchange: this.values.default_locale,
}),
Switch.component(
{
state: this.values.show_language_selector(),
onchange: this.values.show_language_selector,
},
app.translator.trans('core.admin.basics.show_language_selector_label')
),
]
)
: ''}
<FieldSet className="BasicsPage-homePage Form-group" label={app.translator.trans('core.admin.basics.home_page_heading')}>
<div className="helpText">{app.translator.trans('core.admin.basics.home_page_text')}</div>
{this.homePageItems()
.toArray()
.map(({ path, label }) => (
<label className="checkbox">
<input type="radio" name="homePage" value={path} bidi={this.setting('default_route')} />
{label}
</label>
))}
</FieldSet>
{FieldSet.component(
{
label: app.translator.trans('core.admin.basics.home_page_heading'),
className: 'BasicsPage-homePage',
},
[
<div className="helpText">{app.translator.trans('core.admin.basics.home_page_text')}</div>,
this.homePageItems()
.toArray()
.map(({ path, label }) => (
<label className="checkbox">
<input
type="radio"
name="homePage"
value={path}
checked={this.values.default_route() === path}
onclick={withAttr('value', this.values.default_route)}
/>
{label}
</label>
)),
]
)}
<div className="Form-group BasicsPage-welcomeBanner-input">
<label>{app.translator.trans('core.admin.basics.welcome_banner_heading')}</label>
<div className="helpText">{app.translator.trans('core.admin.basics.welcome_banner_text')}</div>
<input type="text" className="FormControl" bidi={this.setting('welcome_title')} />
<textarea className="FormControl" bidi={this.setting('welcome_message')} />
{FieldSet.component(
{
label: app.translator.trans('core.admin.basics.welcome_banner_heading'),
className: 'BasicsPage-welcomeBanner',
},
[
<div className="helpText">{app.translator.trans('core.admin.basics.welcome_banner_text')}</div>,
<div className="BasicsPage-welcomeBanner-input">
<input className="FormControl" bidi={this.values.welcome_title} />
<textarea className="FormControl" bidi={this.values.welcome_message} />
</div>,
]
)}
{Object.keys(this.displayNameOptions).length > 1 ? (
<FieldSet label={app.translator.trans('core.admin.basics.display_name_heading')}>
<div className="helpText">{app.translator.trans('core.admin.basics.display_name_text')}</div>
<Select
options={this.displayNameOptions}
value={this.values.display_name_driver()}
onchange={this.values.display_name_driver}
></Select>
</FieldSet>
) : (
''
)}
{Object.keys(this.slugDriverOptions).map((model) => {
const options = this.slugDriverOptions[model];
if (Object.keys(options).length > 1) {
return (
<FieldSet label={app.translator.trans('core.admin.basics.slug_driver_heading', { model })}>
<div className="helpText">{app.translator.trans('core.admin.basics.slug_driver_text', { model })}</div>
<Select options={options} value={this.values[`slug_driver_${model}`]()} onchange={this.values[`slug_driver_${model}`]}></Select>
</FieldSet>
);
}
})}
{Button.component(
{
type: 'submit',
className: 'Button Button--primary',
loading: this.loading,
disabled: !this.changed(),
},
app.translator.trans('core.admin.basics.submit_button')
)}
</form>
</div>
</div>
);
}
{Object.keys(this.displayNameOptions).length > 1
? this.buildSettingComponent({
type: 'select',
setting: 'display_name_driver',
options: this.displayNameOptions,
label: app.translator.trans('core.admin.basics.display_name_heading'),
help: app.translator.trans('core.admin.basics.display_name_text'),
})
: ''}
{Object.keys(this.slugDriverOptions).map((model) => {
const options = this.slugDriverOptions[model];
if (Object.keys(options).length > 1) {
return this.buildSettingComponent({
type: 'select',
setting: `slug_driver_${model}`,
options,
label: app.translator.trans('core.admin.basics.slug_driver_heading', { model }),
help: app.translator.trans('core.admin.basics.slug_driver_text', { model }),
});
}
})}
{this.submitButton()}
</div>,
];
changed() {
return this.fields.some((key) => this.values[key]() !== app.data.settings[key]);
}
/**
@@ -132,4 +210,27 @@ export default class BasicsPage extends AdminPage {
return items;
}
onsubmit(e) {
e.preventDefault();
if (this.loading) return;
this.loading = true;
app.alerts.dismiss(this.successAlert);
const settings = {};
this.fields.forEach((key) => (settings[key] = this.values[key]()));
saveSettings(settings)
.then(() => {
this.successAlert = app.alerts.show({ type: 'success' }, app.translator.trans('core.admin.basics.saved_message'));
})
.catch(() => {})
.then(() => {
this.loading = false;
m.redraw();
});
}
}

View File

@@ -1,20 +1,20 @@
import Page from '../../common/components/Page';
import StatusWidget from './StatusWidget';
import ExtensionsWidget from './ExtensionsWidget';
import AdminHeader from './AdminHeader';
import ItemList from '../../common/utils/ItemList';
import AdminPage from './AdminPage';
import listItems from '../../common/helpers/listItems';
export default class DashboardPage extends AdminPage {
headerInfo() {
return {
className: 'DashboardPage',
icon: 'fas fa-chart-bar',
title: app.translator.trans('core.admin.dashboard.title'),
description: app.translator.trans('core.admin.dashboard.description'),
};
}
content() {
return this.availableWidgets().toArray();
export default class DashboardPage extends Page {
view() {
return (
<div className="DashboardPage">
<AdminHeader icon="fas fa-chart-bar" description={app.translator.trans('core.admin.dashboard.description')} className="DashboardPage-header">
{app.translator.trans('core.admin.dashboard.title')}
</AdminHeader>
<div className="container">{this.availableWidgets().toArray()}</div>
</div>
);
}
availableWidgets() {

View File

@@ -1,22 +1,28 @@
import Button from '../../common/components/Button';
import Link from '../../common/components/Link';
import LinkButton from '../../common/components/LinkButton';
import Page from '../../common/components/Page';
import Select from '../../common/components/Select';
import Switch from '../../common/components/Switch';
import icon from '../../common/helpers/icon';
import punctuateSeries from '../../common/helpers/punctuateSeries';
import listItems from '../../common/helpers/listItems';
import ItemList from '../../common/utils/ItemList';
import Stream from '../../common/utils/Stream';
import LoadingModal from './LoadingModal';
import ExtensionPermissionGrid from './ExtensionPermissionGrid';
import saveSettings from '../utils/saveSettings';
import ExtensionData from '../utils/ExtensionData';
import isExtensionEnabled from '../utils/isExtensionEnabled';
import AdminPage from './AdminPage';
export default class ExtensionPage extends AdminPage {
export default class ExtensionPage extends Page {
oninit(vnode) {
super.oninit(vnode);
this.loading = false;
this.extension = app.data.extensions[this.attrs.id];
this.changingState = false;
this.settings = {};
this.infoFields = {
discuss: 'fas fa-comment-alt',
@@ -24,29 +30,26 @@ export default class ExtensionPage extends AdminPage {
support: 'fas fa-life-ring',
website: 'fas fa-link',
donate: 'fas fa-donate',
source: 'fas fa-code',
};
if (!this.extension) {
return m.route.set(app.route('dashboard'));
// Backwards compatibility layer will be removed in
// Beta 16
if (app.extensionSettings[this.extension.id]) {
app.extensionData[this.extension.id] = app.extensionSettings[this.extension.id];
}
}
className() {
if (!this.extension) return '';
return this.extension.id + '-Page';
}
view() {
if (!this.extension) return null;
return (
<div className={'ExtensionPage ' + this.className()}>
{this.header()}
{!this.isEnabled() ? (
<div className="container">
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.enable_to_see')}</h3>
<h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.enable_to_see')}</h2>
</div>
) : (
<div className="ExtensionPage-body">{this.sections().toArray()}</div>
@@ -56,8 +59,6 @@ export default class ExtensionPage extends AdminPage {
}
header() {
const isEnabled = this.isEnabled();
return [
<div className="ExtensionPage-header">
<div className="container">
@@ -74,12 +75,10 @@ export default class ExtensionPage extends AdminPage {
</div>
<div className="helpText">{this.extension.description}</div>
<div className="ExtensionPage-headerItems">
<Switch
state={this.changingState ? !isEnabled : isEnabled}
loading={this.changingState}
onchange={this.toggle.bind(this, this.extension.id)}
>
{isEnabled ? app.translator.trans('core.admin.extension.enabled') : app.translator.trans('core.admin.extension.disabled')}
<Switch state={this.isEnabled()} onchange={this.toggle.bind(this, this.extension.id)}>
{this.isEnabled(this.extension.id)
? app.translator.trans('core.admin.extension.enabled')
: app.translator.trans('core.admin.extension.disabled')}
</Switch>
<aside className="ExtensionInfo">
<ul>{listItems(this.infoItems().toArray())}</ul>
@@ -106,7 +105,7 @@ export default class ExtensionPage extends AdminPage {
{app.extensionData.extensionHasPermissions(this.extension.id) ? (
ExtensionPermissionGrid.component({ extensionId: this.extension.id })
) : (
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h3>
<h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h2>
)}
</div>
</div>,
@@ -121,13 +120,17 @@ export default class ExtensionPage extends AdminPage {
return (
<div className="ExtensionPage-settings">
<div className="container">
{settings ? (
{typeof app.extensionData[this.extension.id] === 'function' ? (
<Button onclick={app.extensionData[this.extension.id].bind(this)} className="Button Button--primary">
{app.translator.trans('core.admin.extension.open_modal')}
</Button>
) : settings ? (
<div className="Form">
{settings.map(this.buildSettingComponent.bind(this))}
<div className="Form-group">{this.submitButton()}</div>
</div>
) : (
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
<h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h2>
)}
</div>
</div>
@@ -167,15 +170,17 @@ export default class ExtensionPage extends AdminPage {
infoItems() {
const items = new ItemList();
const links = this.extension.links;
if (links.authors.length) {
if (this.extension.authors) {
let authors = [];
links.authors.map((author) => {
Object.keys(this.extension.authors).map((author, i) => {
const link = this.extension.authors[author].homepage
? this.extension.authors[author].homepage
: 'mailto:' + this.extension.authors[author].email;
authors.push(
<Link href={author.link} external={true} target="_blank">
{author.name}
<Link href={link} external={true} target="_blank">
{this.extension.authors[author].name}
</Link>
);
});
@@ -183,20 +188,103 @@ export default class ExtensionPage extends AdminPage {
items.add('authors', [icon('fas fa-user'), <span>{punctuateSeries(authors)}</span>]);
}
const infoData = {};
if (this.extension.source || this.extension.support) {
infoData.source = {
icon: 'fas fa-code',
href: this.extension.source ? this.extension.source.url : this.extension.support.source,
};
}
Object.keys(this.infoFields).map((field) => {
if (links[field]) {
items.add(
field,
<LinkButton href={links[field]} icon={this.infoFields[field]} external={true} target="_blank">
{app.translator.trans(`core.admin.extension.info_links.${field}`)}
</LinkButton>
);
const info = this.extension.extra['flarum-extension'].info;
if (info && info[field]) {
infoData[field] = {
icon: this.infoFields[field],
href: info[field],
};
}
});
Object.entries(infoData).map(([field, value]) => {
items.add(
field,
<LinkButton href={value.href} icon={value.icon} external={true} target="_blank">
{app.translator.trans(`core.admin.extension.info_links.${field}`)}
</LinkButton>
);
});
return items;
}
submitButton() {
return (
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
{app.translator.trans('core.admin.settings.submit_button')}
</Button>
);
}
/**
* getSetting takes a settings object and turns it into a component.
* Depending on the type of input, you can set the type to 'bool', 'select', or
* any standard <input> type.
*
* @example
*
* {
* setting: 'acme.checkbox',
* label: app.translator.trans('acme.admin.setting_label'),
* type: 'bool'
* }
*
* @example
*
* {
* setting: 'acme.select',
* label: app.translator.trans('acme.admin.setting_label'),
* type: 'select',
* options: {
* 'option1': 'Option 1 label',
* 'option2': 'Option 2 label',
* },
* default: 'option1',
* }
*
* @param setting
* @returns {JSX.Element}
*/
buildSettingComponent(entry) {
const setting = entry.setting;
const value = this.setting([setting])();
if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {
return (
<div className="Form-group">
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]}>
{entry.label}
</Switch>
</div>
);
} else if (['select', 'dropdown', 'selectdropdown'].includes(entry.type)) {
return (
<div className="Form-group">
<label>{entry.label}</label>
<Select value={value || entry.default} options={entry.options} buttonClassName="Button" onchange={this.settings[setting]} />
</div>
);
} else {
return (
<div className="Form-group">
<label>{entry.label}</label>
<input type={entry.type} className="FormControl" bidi={this.setting(setting)} />
</div>
);
}
}
toggle() {
const enabled = this.isEnabled();
@@ -217,8 +305,50 @@ export default class ExtensionPage extends AdminPage {
app.modal.show(LoadingModal);
}
dirty() {
const dirty = {};
Object.keys(this.settings).forEach((key) => {
const value = this.settings[key]();
if (value !== app.data.settings[key]) {
dirty[key] = value;
}
});
return dirty;
}
isChanged() {
return Object.keys(this.dirty()).length;
}
saveSettings(e) {
e.preventDefault();
app.alerts.clear();
this.loading = true;
saveSettings(this.dirty()).then(this.onsaved.bind(this));
}
onsaved() {
this.loading = false;
app.alerts.show({ type: 'success' }, app.translator.trans('core.admin.extension.saved_message'));
}
setting(key, fallback = '') {
this.settings[key] = this.settings[key] || Stream(app.data.settings[key] || fallback);
return this.settings[key];
}
isEnabled() {
return isExtensionEnabled(this.extension.id);
let isEnabled = isExtensionEnabled(this.extension.id);
return this.changingState ? !isEnabled : isEnabled;
}
onerror(e) {
@@ -229,8 +359,6 @@ export default class ExtensionPage extends AdminPage {
app.modal.close();
}, 300); // Bootstrap's Modal.TRANSITION_DURATION is 300 ms.
this.changingState = false;
if (e.status !== 409) {
throw e;
}

View File

@@ -1,5 +1,4 @@
import PermissionGrid from './PermissionGrid';
import Button from '../../common/components/Button';
import ItemList from '../../common/utils/ItemList';
export default class ExtensionPermissionGrid extends PermissionGrid {
@@ -37,17 +36,4 @@ export default class ExtensionPermissionGrid extends PermissionGrid {
moderateItems() {
return app.extensionData.getExtensionPermissions(this.extensionId, 'moderate') || new ItemList();
}
scopeControlItems() {
const items = new ItemList();
items.add(
'configureScopes',
<Button className="Button Button--text" onclick={() => m.route.set(app.route('permissions'))}>
{app.translator.trans('core.admin.extension.configure_scopes')}
</Button>
);
return items;
}
}

View File

@@ -5,47 +5,44 @@ import Link from '../../common/components/Link';
import icon from '../../common/helpers/icon';
export default class ExtensionsWidget extends DashboardWidget {
oninit(vnode) {
super.oninit(vnode);
this.categorizedExtensions = getCategorizedExtensions();
}
className() {
return 'ExtensionsWidget';
}
content() {
const categorizedExtensions = getCategorizedExtensions();
const categories = app.extensionCategories;
return (
<div className="ExtensionsWidget-list">
{Object.keys(categories).map((category) => (this.categorizedExtensions[category] ? this.extensionCategory(category) : ''))}
<div className="container">
{Object.keys(categories).map((category) => {
if (categorizedExtensions[category]) {
return (
<div className="ExtensionList-Category">
<h4 className="ExtensionList-Label">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>
<ul className="ExtensionList">
{categorizedExtensions[category].map((extension) => {
return (
<li className={'ExtensionListItem ' + (!isExtensionEnabled(extension.id) ? 'disabled' : '')}>
<Link href={app.route('extension', { id: extension.id })}>
<div className="ExtensionListItem-content">
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
{extension.icon ? icon(extension.icon.name) : ''}
</span>
<span className="ExtensionListItem-title">{extension.extra['flarum-extension'].title}</span>
</div>
</Link>
</li>
);
})}
</ul>
</div>
);
}
})}
</div>
</div>
);
}
extensionCategory(category) {
return (
<div className="ExtensionList-Category">
<h4 className="ExtensionList-Label">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>
<ul className="ExtensionList">{this.categorizedExtensions[category].map((extension) => this.extensionWidget(extension))}</ul>
</div>
);
}
extensionWidget(extension) {
return (
<li className={'ExtensionListItem ' + (!isExtensionEnabled(extension.id) ? 'disabled' : '')}>
<Link href={app.route('extension', { id: extension.id })}>
<div className="ExtensionListItem-content">
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
{extension.icon ? icon(extension.icon.name) : ''}
</span>
<span className="ExtensionListItem-title">{extension.extra['flarum-extension'].title}</span>
</div>
</Link>
</li>
);
}
}

View File

@@ -1,31 +1,34 @@
import Page from '../../common/components/Page';
import FieldSet from '../../common/components/FieldSet';
import Button from '../../common/components/Button';
import Alert from '../../common/components/Alert';
import Select from '../../common/components/Select';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import AdminPage from './AdminPage';
import saveSettings from '../utils/saveSettings';
import Stream from '../../common/utils/Stream';
import icon from '../../common/helpers/icon';
import AdminHeader from './AdminHeader';
export default class MailPage extends AdminPage {
export default class MailPage extends Page {
oninit(vnode) {
super.oninit(vnode);
this.saving = false;
this.sendingTest = false;
this.refresh();
}
headerInfo() {
return {
className: 'MailPage',
icon: 'fas fa-envelope',
title: app.translator.trans('core.admin.email.title'),
description: app.translator.trans('core.admin.email.description'),
};
}
refresh() {
this.loading = true;
this.driverFields = {};
this.fields = ['mail_driver', 'mail_from'];
this.values = {};
this.status = { sending: false, errors: {} };
const settings = app.data.settings;
this.fields.forEach((key) => (this.values[key] = Stream(settings[key])));
app
.request({
method: 'GET',
@@ -36,78 +39,150 @@ export default class MailPage extends AdminPage {
this.status.sending = response['data']['attributes']['sending'];
this.status.errors = response['data']['attributes']['errors'];
for (const driver in this.driverFields) {
for (const field in this.driverFields[driver]) {
this.fields.push(field);
this.values[field] = Stream(settings[field]);
}
}
this.loading = false;
m.redraw();
});
}
content() {
if (this.loading) {
return <LoadingIndicator />;
view() {
if (this.loading || this.saving) {
return (
<div className="MailPage">
<div className="container">
<LoadingIndicator />
</div>
</div>
);
}
const fields = this.driverFields[this.setting('mail_driver')()];
const fields = this.driverFields[this.values.mail_driver()];
const fieldKeys = Object.keys(fields);
return (
<div className="Form">
{this.buildSettingComponent({
type: 'text',
setting: 'mail_from',
label: app.translator.trans('core.admin.email.addresses_heading'),
className: 'MailPage-MailSettings',
})}
{this.buildSettingComponent({
type: 'select',
setting: 'mail_driver',
options: Object.keys(this.driverFields).reduce((memo, val) => ({ ...memo, [val]: val }), {}),
label: app.translator.trans('core.admin.email.driver_heading'),
className: 'MailPage-MailSettings',
})}
{this.status.sending ||
Alert.component(
{
dismissible: false,
},
app.translator.trans('core.admin.email.not_sending_message')
)}
<div className="MailPage">
<AdminHeader icon="fas fa-envelope" description={app.translator.trans('core.admin.email.description')} className="MailPage-header">
{app.translator.trans('core.admin.email.title')}
</AdminHeader>
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
{FieldSet.component(
{
label: app.translator.trans('core.admin.email.addresses_heading'),
className: 'MailPage-MailSettings',
},
[
<div className="MailPage-MailSettings-input">
<label>
{app.translator.trans('core.admin.email.from_label')}
<input className="FormControl" bidi={this.values.mail_from} />
</label>
</div>,
]
)}
{fieldKeys.length > 0 && (
<FieldSet label={app.translator.trans(`core.admin.email.${this.setting('mail_driver')()}_heading`)} className="MailPage-MailSettings">
<div className="MailPage-MailSettings-input">
{fieldKeys.map((field) => {
const fieldInfo = fields[field];
{FieldSet.component(
{
label: app.translator.trans('core.admin.email.driver_heading'),
className: 'MailPage-MailSettings',
},
[
<div className="MailPage-MailSettings-input">
<label>
{app.translator.trans('core.admin.email.driver_label')}
<Select
value={this.values.mail_driver()}
options={Object.keys(this.driverFields).reduce((memo, val) => ({ ...memo, [val]: val }), {})}
onchange={this.values.mail_driver}
/>
</label>
</div>,
]
)}
return [
this.buildSettingComponent({
type: typeof this.setting(field)() === 'string' ? 'text' : 'select',
label: app.translator.trans(`core.admin.email.${field}_label`),
setting: field,
options: fieldInfo,
}),
this.status.errors[field] && <p className="ValidationError">{this.status.errors[field]}</p>,
];
})}
</div>
</FieldSet>
)}
{this.submitButton()}
{this.status.sending ||
Alert.component(
{
dismissible: false,
},
app.translator.trans('core.admin.email.not_sending_message')
)}
<FieldSet label={app.translator.trans('core.admin.email.send_test_mail_heading')} className="MailPage-MailSettings">
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user.email() })}</div>
{Button.component(
{
className: 'Button Button--primary',
disabled: this.sendingTest || this.isChanged(),
onclick: () => this.sendTestEmail(),
},
app.translator.trans('core.admin.email.send_test_mail_button')
)}
</FieldSet>
{fieldKeys.length > 0 &&
FieldSet.component(
{
label: app.translator.trans(`core.admin.email.${this.values.mail_driver()}_heading`),
className: 'MailPage-MailSettings',
},
[
<div className="MailPage-MailSettings-input">
{fieldKeys.map((field) => [
<label>
{app.translator.trans(`core.admin.email.${field}_label`)}
{this.renderField(field)}
</label>,
this.status.errors[field] && <p className="ValidationError">{this.status.errors[field]}</p>,
])}
</div>,
]
)}
<FieldSet>
{Button.component(
{
type: 'submit',
className: 'Button Button--primary',
disabled: !this.changed(),
},
app.translator.trans('core.admin.email.submit_button')
)}
</FieldSet>
{FieldSet.component(
{
label: app.translator.trans('core.admin.email.send_test_mail_heading'),
className: 'MailPage-MailSettings',
},
[
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user.email() })}</div>,
Button.component(
{
className: 'Button Button--primary',
disabled: this.sendingTest || this.changed(),
onclick: () => this.sendTestEmail(),
},
app.translator.trans('core.admin.email.send_test_mail_button')
),
]
)}
</form>
</div>
</div>
);
}
renderField(name) {
const driver = this.values.mail_driver();
const field = this.driverFields[driver][name];
const prop = this.values[name];
if (typeof field === 'string') {
return <input className="FormControl" bidi={prop} />;
} else {
return <Select value={prop()} options={field} onchange={prop} />;
}
}
changed() {
return this.fields.some((key) => this.values[key]() !== app.data.settings[key]);
}
sendTestEmail() {
if (this.saving || this.sendingTest) return;
@@ -130,7 +205,26 @@ export default class MailPage extends AdminPage {
});
}
saveSettings(e) {
super.saveSettings(e).then(this.refresh());
onsubmit(e) {
e.preventDefault();
if (this.saving || this.sendingTest) return;
this.saving = true;
app.alerts.dismiss(this.successAlert);
const settings = {};
this.fields.forEach((key) => (settings[key] = this.values[key]()));
saveSettings(settings)
.then(() => {
this.successAlert = app.alerts.show({ type: 'success' }, app.translator.trans('core.admin.basics.saved_message'));
})
.catch(() => {})
.then(() => {
this.saving = false;
this.refresh();
});
}
}

View File

@@ -326,30 +326,10 @@ export default class PermissionGrid extends Component {
60
);
items.add(
'userEditCredentials',
{
icon: 'fas fa-user-cog',
label: app.translator.trans('core.admin.permissions.edit_users_credentials_label'),
permission: 'user.editCredentials',
},
60
);
items.add(
'userEditGroups',
{
icon: 'fas fa-users-cog',
label: app.translator.trans('core.admin.permissions.edit_users_groups_label'),
permission: 'user.editGroups',
},
60
);
items.add(
'userEdit',
{
icon: 'fas fa-address-card',
icon: 'fas fa-user-cog',
label: app.translator.trans('core.admin.permissions.edit_users_label'),
permission: 'user.edit',
},

View File

@@ -1,43 +1,44 @@
import Page from '../../common/components/Page';
import GroupBadge from '../../common/components/GroupBadge';
import EditGroupModal from './EditGroupModal';
import Group from '../../common/models/Group';
import icon from '../../common/helpers/icon';
import PermissionGrid from './PermissionGrid';
import AdminPage from './AdminPage';
import AdminHeader from './AdminHeader';
export default class PermissionsPage extends AdminPage {
headerInfo() {
return {
className: 'PermissionsPage',
icon: 'fas fa-key',
title: app.translator.trans('core.admin.permissions.title'),
description: app.translator.trans('core.admin.permissions.description'),
};
}
content() {
return [
<div className="PermissionsPage-groups">
{app.store
.all('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map((group) => (
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
{GroupBadge.component({
group,
className: 'Group-icon',
label: null,
})}
<span className="Group-name">{group.namePlural()}</span>
export default class PermissionsPage extends Page {
view() {
return (
<div className="PermissionsPage">
<AdminHeader icon="fas fa-key" description={app.translator.trans('core.admin.permissions.description')} className="PermissionsPage-header">
{app.translator.trans('core.admin.permissions.title')}
</AdminHeader>
<div className="PermissionsPage-groups">
<div className="container">
{app.store
.all('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map((group) => (
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
{GroupBadge.component({
group,
className: 'Group-icon',
label: null,
})}
<span className="Group-name">{group.namePlural()}</span>
</button>
))}
<button className="Button Group Group--add" onclick={() => app.modal.show(EditGroupModal)}>
{icon('fas fa-plus', { className: 'Group-icon' })}
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
</button>
))}
<button className="Button Group Group--add" onclick={() => app.modal.show(EditGroupModal)}>
{icon('fas fa-plus', { className: 'Group-icon' })}
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
</button>
</div>,
</div>
</div>
<div className="PermissionsPage-permissions">{PermissionGrid.component()}</div>,
];
<div className="PermissionsPage-permissions">
<div className="container">{PermissionGrid.component()}</div>
</div>
</div>
);
}
}

View File

@@ -36,7 +36,7 @@ export default class UploadImageButton extends Button {
$input
.appendTo('body')
.hide()
.trigger('click')
.click()
.on('change', (e) => {
const body = new FormData();
body.append(this.attrs.name, $(e.target)[0].files[0]);

View File

@@ -1,5 +1,17 @@
import app from './app';
import AdminApplication from './AdminApplication';
const app = new AdminApplication();
// Backwards compatibility
window.app = app;
export { app };
// Export public API
// Export compat API
import compat from './compat';
compat.app = app;
export { compat };

View File

@@ -26,8 +26,6 @@ export default class ExtensionData {
/**
* This function registers your settings with Flarum
*
* It takes either a settings object or a callback.
*
* @example
*
* .registerSetting({
@@ -44,14 +42,6 @@ export default class ExtensionData {
registerSetting(content, priority = 0) {
this.data[this.currentExtension].settings = this.data[this.currentExtension].settings || new ItemList();
// Callbacks can be passed in instead of settings to display custom content.
// By default, they will be added with the `null` key, since they don't have a `.setting` attr.
// To support multiple such items for one extension, we assign a random ID.
// 36 is arbitrary length, but makes collisions very unlikely.
if (typeof content === 'function') {
content.setting = Math.random().toString(36);
}
this.data[this.currentExtension].settings.add(content.setting, content, priority);
return this;

View File

@@ -15,9 +15,9 @@ export default function getCategorizedExtensions() {
extensions[category].push(extension);
} else {
extensions.feature = extensions.feature || [];
extensions.other = extensions.other || [];
extensions.feature.push(extension);
extensions.other.push(extension);
}
});

View File

@@ -159,8 +159,6 @@ export default class Application {
title = '';
titleCount = 0;
initialRoute;
load(payload) {
this.data = payload;
this.translator.locale = payload.locale;
@@ -176,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) {
@@ -230,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;
@@ -275,7 +270,7 @@ export default class Application {
updateTitle() {
const count = this.titleCount ? `(${this.titleCount}) ` : '';
const pageTitleWithSeparator = this.title && m.route.get() !== this.forum.attribute('basePath') + '/' ? this.title + ' - ' : '';
const pageTitleWithSeparator = this.title && m.route.get() !== '/' ? this.title + ' - ' : '';
const title = this.forum.attribute('title');
document.title = count + pageTitleWithSeparator + title;
}

View File

@@ -1,5 +1,8 @@
import * as Mithril from 'mithril';
let deprecatedPropsWarned = false;
let deprecatedInitPropsWarned = false;
export interface ComponentAttrs extends Mithril.Attributes {}
/**
@@ -77,12 +80,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 +97,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);
}
@@ -128,5 +131,38 @@ export default abstract class Component<T extends ComponentAttrs = ComponentAttr
*
* This can be used to assign default values for missing, optional attrs.
*/
protected static initAttrs<T>(attrs: T): void {}
protected static initAttrs<T>(attrs: T): void {
// Deprecated, part of Mithril 2 BC layer
if ('initProps' in this && !deprecatedInitPropsWarned) {
deprecatedInitPropsWarned = true;
console.warn('initProps is deprecated, please use initAttrs instead.');
(this as any).initProps(attrs);
}
}
// BEGIN DEPRECATED MITHRIL 2 BC LAYER
/**
* The attributes passed into the component.
*
* @see https://mithril.js.org/components.html#passing-data-to-components
*
* @deprecated, use attrs instead.
*/
get props() {
if (!deprecatedPropsWarned) {
deprecatedPropsWarned = true;
console.warn('this.props is deprecated, please use this.attrs instead.');
}
return this.attrs;
}
set props(props) {
if (!deprecatedPropsWarned) {
deprecatedPropsWarned = true;
console.warn('this.props is deprecated, please use this.attrs instead.');
}
this.attrs = props;
}
// END DEPRECATED MITHRIL 2 BC LAYER
}

View File

@@ -1,55 +0,0 @@
interface ExportRegistry {
moduleExports: object;
onLoads: object;
/**
* Add an instance to the registry.
* This serves as the equivalent of `flarum.core.compat[id] = object`
*/
add(id: string, object: any);
/**
* Add a function to run when object of id "id" is added (or overriden).
* If such an object is already registered, the handler will be applied immediately.
*/
onLoad(id: string, handler: Function);
/**
* Retrieve an object of type `id` from the registry.
*/
get(id: string): any;
}
export default class FlarumRegistry implements ExportRegistry {
moduleExports = {};
onLoads = {};
add(id: string, object: any) {
const onLoads = this.onLoads[id];
if (onLoads) {
onLoads.map((onLoad) => {
object = onLoad(object);
});
}
this.moduleExports[id] = object;
}
onLoad(id: string, handler: Function) {
const loadedObject = this.moduleExports[id];
if (loadedObject) {
this.moduleExports[id] = handler(loadedObject);
} else {
this.onLoads[id] = this.onLoads[id] || [];
this.onLoads[id].push(handler);
}
}
get(id: string): any {
if (this.moduleExports[id]) {
return this.moduleExports[id];
}
}
}

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