mirror of
https://github.com/flarum/core.git
synced 2025-08-18 14:22:02 +02:00
Compare commits
2 Commits
fl/fronten
...
0.1.0-beta
Author | SHA1 | Date | |
---|---|---|---|
|
5bcf72dd49 | ||
|
0536b208e1 |
BIN
.deploy.enc
BIN
.deploy.enc
Binary file not shown.
@@ -15,5 +15,5 @@ indent_size = 2
|
|||||||
[*.{diff,md}]
|
[*.{diff,md}]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.{php,xml}]
|
[*.php]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,8 +1,6 @@
|
|||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
.gitmodules export-ignore
|
.gitmodules export-ignore
|
||||||
.github export-ignore
|
|
||||||
.travis export-ignore
|
|
||||||
.travis.yml export-ignore
|
.travis.yml export-ignore
|
||||||
.editorconfig export-ignore
|
.editorconfig export-ignore
|
||||||
.styleci.yml export-ignore
|
.styleci.yml export-ignore
|
||||||
@@ -10,4 +8,4 @@
|
|||||||
phpunit.xml export-ignore
|
phpunit.xml export-ignore
|
||||||
tests export-ignore
|
tests export-ignore
|
||||||
|
|
||||||
js/dist/* -diff
|
js/*/dist/*.js -diff
|
||||||
|
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
> Issues on Github are meant for bug reporting. Please post feature requests on the [discussion forum](https://discuss.flarum.org/t/features).
|
||||||
|
---
|
||||||
|
> Try to complete the below form as far as you are able and are willing to share. Add a screenshot of the issue if you can.
|
||||||
|
|
||||||
|
## Bug report
|
||||||
|
- Version of Flarum: x.y.z
|
||||||
|
- Website URL where the bug is visible: http://example.com
|
||||||
|
- The webserver you are running: apache, nginx or something else
|
||||||
|
- PHP version: x.y.z
|
||||||
|
- Hosted environment: shared or vps
|
||||||
|
- Hosting provider: http://some-amazing-provider.com
|
||||||
|
|
||||||
|
## Flarum info
|
||||||
|
|
||||||
|
```
|
||||||
|
Output of "php flarum info", run this in terminal in your Flarum directory.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional comments
|
||||||
|
Some additional information you'd like to share, eg what have you tried so far.
|
||||||
|
|
||||||
|
## Log files
|
||||||
|
|
||||||
|
```
|
||||||
|
Put any relevant logs here.
|
||||||
|
```
|
42
.github/ISSUE_TEMPLATE/bug-report.md
vendored
42
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🐛 Bug Report"
|
|
||||||
about: "If something isn't working as expected"
|
|
||||||
|
|
||||||
---
|
|
||||||
<!--
|
|
||||||
IMPORTANT: If you discover a security vulnerability within Flarum, please send an email to [security@flarum.org](mailto:security@flarum.org) instead. We will address these with the utmost urgency and it will prevent vulnerabilities, which may be abused, from popping up on our issue tracker.
|
|
||||||
-->
|
|
||||||
## Bug Report
|
|
||||||
|
|
||||||
**Current Behavior**
|
|
||||||
A clear and concise description of the behavior.
|
|
||||||
|
|
||||||
**Steps to Reproduce**
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected Behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Environment**
|
|
||||||
- Flarum version: x.y.z
|
|
||||||
- Website URL: http://example.com
|
|
||||||
- Webserver: [e.g. apache, nginx]
|
|
||||||
- Hosting environment: [e.g. shared, vps]
|
|
||||||
- PHP version: x.y.z
|
|
||||||
- Browser: [e.g. chrome 67, safari 11]
|
|
||||||
|
|
||||||
```
|
|
||||||
Output of "php flarum info", run this in terminal in your Flarum directory.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Possible Solution**
|
|
||||||
<!--- Only if you have suggestions or a fix for the bug -->
|
|
||||||
|
|
||||||
**Additional Context**
|
|
||||||
Add any other context about the problem here.
|
|
26
.github/ISSUE_TEMPLATE/feature-request.md
vendored
26
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🚀 Feature Request"
|
|
||||||
about: "I have a suggestion (and may want to implement it!)"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
IMPORTANT: Feature requests on this GitHub issue tracker are only accepted in case they have been approved by a core developer or contain extensive argumentation and directions for implementation. For all other feature requests, ideas and feedback please post in the Flarum Community: https://discuss.flarum.org/t/feedback.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Feature Request
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. eg. I have an issue when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A detailed description of your proposed solution. Include:
|
|
||||||
- How the feature would work/behave
|
|
||||||
- Any potential drawbacks
|
|
||||||
- Maybe a screenshot, design, or example code
|
|
||||||
|
|
||||||
**Justify why this feature belongs in Flarum's core, rather than in a third-party extension**
|
|
||||||
Consider who this change will be useful to – most Flarum forums, or just a few?
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
11
.github/ISSUE_TEMPLATE/support-question.md
vendored
11
.github/ISSUE_TEMPLATE/support-question.md
vendored
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🙋 Support Question"
|
|
||||||
about: "If you have a question, please check out our forum or Discord!"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks!
|
|
||||||
|
|
||||||
* Flarum Community: https://discuss.flarum.org/
|
|
||||||
* Discord Chat: https://flarum.org/discord/
|
|
||||||
* Twitter: https://twitter.com/Flarum
|
|
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
<!--
|
|
||||||
IMPORTANT: We applaud pull requests, they excite us every single time. As we have an obligation to maintain a healthy code standard and quality, we take sufficient time for reviews. Please do create a separate pull request per change/issue/feature; we will ask you to split bundled pull requests.
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Fixes #0000**
|
|
||||||
|
|
||||||
**Changes proposed in this pull request:**
|
|
||||||
<!-- fill this out, mention the pages and/or components which have been impacted -->
|
|
||||||
|
|
||||||
**Reviewers should focus on:**
|
|
||||||
<!-- fill this out, ask for feedback on specific changes you are unsure about -->
|
|
||||||
|
|
||||||
**Screenshot**
|
|
||||||
<!-- include an image of the most relevant user-facing change, if any -->
|
|
||||||
|
|
||||||
**Confirmed**
|
|
||||||
|
|
||||||
- [ ] Frontend changes: tested on a local Flarum installation.
|
|
||||||
- [ ] Backend changes: tests are green (run `php vendor/bin/phpunit`).
|
|
||||||
|
|
||||||
**Required changes:**
|
|
||||||
|
|
||||||
- [ ] Related documentation PR: (Remove if irrelevant)
|
|
||||||
- [ ] Related core extension PRs: (Remove if irrelevant)
|
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,9 +1,7 @@
|
|||||||
/vendor
|
/vendor
|
||||||
composer.lock
|
|
||||||
composer.phar
|
composer.phar
|
||||||
node_modules
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
/tests/tmp
|
tests/_output/*
|
||||||
.vagrant
|
.vagrant
|
||||||
.idea/*
|
.idea/*
|
||||||
|
59
.travis.yml
59
.travis.yml
@@ -1,48 +1,35 @@
|
|||||||
language: php
|
language: php
|
||||||
|
|
||||||
sudo: false
|
php:
|
||||||
|
- 5.6
|
||||||
|
- 7.0
|
||||||
|
- 7.1
|
||||||
|
- hhvm
|
||||||
|
|
||||||
cache:
|
matrix:
|
||||||
directories:
|
allow_failures:
|
||||||
- $HOME/.composer/cache
|
- php: hhvm
|
||||||
- $HOME/.npm
|
fast_finish: true
|
||||||
|
|
||||||
install:
|
before_script:
|
||||||
|
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi;
|
||||||
|
- composer self-update
|
||||||
- composer install
|
- composer install
|
||||||
- mysql -e 'CREATE DATABASE flarum;'
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- vendor/bin/phpunit --coverage-clover=coverage.xml
|
- vendor/bin/phpunit --coverage-clover=coverage.xml
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_failure: change
|
||||||
|
webhooks:
|
||||||
|
urls:
|
||||||
|
- https://webhooks.gitter.im/e/7b9e9827a03b44a16588
|
||||||
|
on_success: always
|
||||||
|
on_failure: always
|
||||||
|
on_start: false
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
||||||
jobs:
|
sudo: false
|
||||||
include:
|
|
||||||
- php: 7.1
|
|
||||||
env: DB=mysql
|
|
||||||
|
|
||||||
- php: 7.2
|
|
||||||
env: DB=mysql
|
|
||||||
|
|
||||||
- php: 7.2
|
|
||||||
env: DB=mysql PREFIX=forum_
|
|
||||||
|
|
||||||
- php: 7.1
|
|
||||||
addons:
|
|
||||||
mariadb: '10.2'
|
|
||||||
env: DB=mariadb
|
|
||||||
|
|
||||||
- php: 7.2
|
|
||||||
addons:
|
|
||||||
mariadb: '10.2'
|
|
||||||
env: DB=mariadb
|
|
||||||
|
|
||||||
- stage: build
|
|
||||||
language: generic
|
|
||||||
if: branch = master AND type = push
|
|
||||||
install: skip
|
|
||||||
script: bash .travis/build.sh
|
|
||||||
-k $encrypted_678139e2bc67_key
|
|
||||||
-i $encrypted_678139e2bc67_iv
|
|
||||||
after_success: skip
|
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
main() {
|
|
||||||
while getopts ":k:i:" opt; do
|
|
||||||
case $opt in
|
|
||||||
k) encrypted_key="$OPTARG"
|
|
||||||
;;
|
|
||||||
i) encrypted_iv="$OPTARG"
|
|
||||||
;;
|
|
||||||
\?) echo "Invalid option -$OPTARG" >&2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
git checkout -f $TRAVIS_BRANCH
|
|
||||||
git config user.name "flarum-bot"
|
|
||||||
git config user.email "bot@flarum.org"
|
|
||||||
|
|
||||||
cd js
|
|
||||||
npm i -g npm@6.1.0
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
git add dist/* -f
|
|
||||||
git commit -m "Bundled output for commit $TRAVIS_COMMIT [skip ci]"
|
|
||||||
|
|
||||||
eval `ssh-agent -s`
|
|
||||||
openssl aes-256-cbc -K $encrypted_key -iv $encrypted_iv -in ../.deploy.enc -d | ssh-add -
|
|
||||||
|
|
||||||
git push git@github.com:$TRAVIS_REPO_SLUG.git $TRAVIS_BRANCH
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014-2018 Toby Zerner
|
Copyright (c) 2014-2017 Toby Zerner
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@@ -1,11 +1,7 @@
|
|||||||
# Flarum Core
|
# Flarum Core
|
||||||
|
|
||||||
This repository contains Flarum's core code. If you want to set up a forum, visit the [main Flarum repository](https://github.com/flarum/flarum).
|
This repository contains Flarum's core code. If you want to set up a forum, visit the [main Flarum repository](http://github.com/flarum/flarum).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Flarum is open-source and we would love your help building it! Please read the [Contributing Guide](https://github.com/flarum/flarum/blob/master/CONTRIBUTING.md) to learn how you can help.
|
Flarum is open-source and we would love your help building it! Please read the [Contributing Guide](https://github.com/flarum/flarum/blob/master/CONTRIBUTING.md) to learn how you can help.
|
||||||
|
|
||||||
### Security Vulnerabilities
|
|
||||||
|
|
||||||
If you discover a security vulnerability within Flarum, please send an email to [security@flarum.org](mailto:security@flarum.org).
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "flarum/core",
|
"name": "flarum/core",
|
||||||
"description": "Delightfully simple forum software.",
|
"description": "Delightfully simple forum software.",
|
||||||
"keywords": ["forum", "discussion"],
|
"keywords": ["forum", "discussion"],
|
||||||
"homepage": "https://flarum.org/",
|
"homepage": "http://flarum.org",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
@@ -17,55 +17,47 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/flarum/core/issues",
|
"issues": "https://github.com/flarum/core/issues",
|
||||||
"source": "https://github.com/flarum/core",
|
"source": "https://github.com/flarum/core",
|
||||||
"docs": "https://flarum.org/docs/"
|
"docs": "http://flarum.org/docs"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1",
|
"php": ">=5.6.0",
|
||||||
"axy/sourcemap": "^0.1.4",
|
|
||||||
"components/font-awesome": "^5.0.6",
|
|
||||||
"dflydev/fig-cookies": "^1.0.2",
|
"dflydev/fig-cookies": "^1.0.2",
|
||||||
"doctrine/dbal": "^2.7",
|
"doctrine/dbal": "^2.5",
|
||||||
|
"components/font-awesome": "^4.6",
|
||||||
"franzl/whoops-middleware": "^0.4.0",
|
"franzl/whoops-middleware": "^0.4.0",
|
||||||
"illuminate/bus": "5.5.*",
|
"illuminate/bus": "5.1.*",
|
||||||
"illuminate/cache": "5.5.*",
|
"illuminate/cache": "5.1.*",
|
||||||
"illuminate/config": "5.5.*",
|
"illuminate/config": "5.1.*",
|
||||||
"illuminate/container": "5.5.*",
|
"illuminate/container": "5.1.*",
|
||||||
"illuminate/contracts": "5.5.*",
|
"illuminate/contracts": "5.1.*",
|
||||||
"illuminate/database": "5.5.*",
|
"illuminate/database": "^5.1.31",
|
||||||
"illuminate/events": "5.5.*",
|
"illuminate/events": "5.1.*",
|
||||||
"illuminate/filesystem": "5.5.*",
|
"illuminate/filesystem": "5.1.*",
|
||||||
"illuminate/hashing": "5.5.*",
|
"illuminate/hashing": "5.1.*",
|
||||||
"illuminate/mail": "5.5.*",
|
"illuminate/mail": "5.1.*",
|
||||||
"illuminate/session": "5.5.*",
|
"illuminate/support": "5.1.*",
|
||||||
"illuminate/support": "5.5.*",
|
"illuminate/validation": "5.1.*",
|
||||||
"illuminate/validation": "5.5.*",
|
"illuminate/view": "5.1.*",
|
||||||
"illuminate/view": "5.5.*",
|
|
||||||
"intervention/image": "^2.3.0",
|
"intervention/image": "^2.3.0",
|
||||||
"league/flysystem": "^1.0.11",
|
"league/flysystem": "^1.0.11",
|
||||||
|
"league/oauth2-client": "~1.0",
|
||||||
"matthiasmullie/minify": "^1.3",
|
"matthiasmullie/minify": "^1.3",
|
||||||
"middlewares/base-path": "^1.1",
|
|
||||||
"middlewares/base-path-router": "^0.2.1",
|
|
||||||
"middlewares/request-handler": "^1.2",
|
|
||||||
"monolog/monolog": "^1.16.0",
|
"monolog/monolog": "^1.16.0",
|
||||||
"nikic/fast-route": "^0.6",
|
"nikic/fast-route": "^0.6",
|
||||||
"oyejorge/less.php": "^1.7",
|
"oyejorge/less.php": "~1.5",
|
||||||
"psr/http-message": "^1.0",
|
"psr/http-message": "^1.0",
|
||||||
"psr/http-server-handler": "^1.0",
|
"symfony/console": "^2.7",
|
||||||
"psr/http-server-middleware": "^1.0",
|
"symfony/http-foundation": "^2.7",
|
||||||
"s9e/text-formatter": "^1.2.0",
|
"symfony/translation": "^2.7",
|
||||||
"symfony/config": "^3.3",
|
"symfony/yaml": "^2.7",
|
||||||
"symfony/console": "^3.3",
|
"s9e/text-formatter": "^0.8.1",
|
||||||
"symfony/http-foundation": "^3.3",
|
|
||||||
"symfony/translation": "^3.3",
|
|
||||||
"symfony/yaml": "^3.3",
|
|
||||||
"tobscure/json-api": "^0.3.0",
|
"tobscure/json-api": "^0.3.0",
|
||||||
"zendframework/zend-diactoros": "^1.8.4",
|
"zendframework/zend-diactoros": "^1.1",
|
||||||
"zendframework/zend-httphandlerrunner": "^1.0",
|
"zendframework/zend-stratigility": "^1.3"
|
||||||
"zendframework/zend-stratigility": "^3.0"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mockery/mockery": "^0.9.4",
|
"mockery/mockery": "^0.9.4",
|
||||||
"phpunit/phpunit": "^6.0"
|
"phpunit/phpunit": "^4.8"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -77,12 +69,9 @@
|
|||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Flarum\\Tests\\": "tests/"
|
"Tests\\": "tests/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
|
||||||
"sort-packages": true
|
|
||||||
},
|
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "0.1.x-dev"
|
"dev-master": "0.1.x-dev"
|
||||||
|
13
error/403.html
Normal file
13
error/403.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>403 Forbidden</h1>
|
||||||
|
<p>You do not have permissions to access this page.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
13
error/404.html
Normal file
13
error/404.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>404 Not Found</h1>
|
||||||
|
<p>Looks like this page could not be found.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
13
error/500.html
Normal file
13
error/500.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>500 Internal Server Error</h1>
|
||||||
|
<p>Something went wrong on our server.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
13
error/503.html
Normal file
13
error/503.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>503 Service Unavailable</h1>
|
||||||
|
<p>This forum is down for maintenance.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
1
js/.gitignore
vendored
Normal file
1
js/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bower_components
|
11
js/admin.js
11
js/admin.js
@@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './src/common';
|
|
||||||
export * from './src/admin';
|
|
1
js/admin/.gitignore
vendored
Normal file
1
js/admin/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
31
js/admin/Gulpfile.js
Normal file
31
js/admin/Gulpfile.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
var gulp = require('flarum-gulp');
|
||||||
|
|
||||||
|
var bowerDir = '../bower_components';
|
||||||
|
|
||||||
|
gulp({
|
||||||
|
includeHelpers: true,
|
||||||
|
files: [
|
||||||
|
bowerDir + '/es6-micro-loader/dist/system-polyfill.js',
|
||||||
|
|
||||||
|
bowerDir + '/mithril/mithril.js',
|
||||||
|
bowerDir + '/m.attrs.bidi/bidi.js',
|
||||||
|
bowerDir + '/jquery/dist/jquery.js',
|
||||||
|
bowerDir + '/moment/moment.js',
|
||||||
|
|
||||||
|
bowerDir + '/bootstrap/js/affix.js',
|
||||||
|
bowerDir + '/bootstrap/js/dropdown.js',
|
||||||
|
bowerDir + '/bootstrap/js/modal.js',
|
||||||
|
bowerDir + '/bootstrap/js/tooltip.js',
|
||||||
|
bowerDir + '/bootstrap/js/transition.js',
|
||||||
|
|
||||||
|
bowerDir + '/spin.js/spin.js',
|
||||||
|
bowerDir + '/spin.js/jquery.spin.js'
|
||||||
|
],
|
||||||
|
modules: {
|
||||||
|
'flarum': [
|
||||||
|
'src/**/*.js',
|
||||||
|
'../lib/**/*.js'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
outputFile: 'dist/app.js'
|
||||||
|
});
|
23990
js/admin/dist/app.js
vendored
Normal file
23990
js/admin/dist/app.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
js/admin/package.json
Normal file
7
js/admin/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^3.9.1",
|
||||||
|
"flarum-gulp": "^0.2.0"
|
||||||
|
}
|
||||||
|
}
|
33
js/admin/src/app.js
Normal file
33
js/admin/src/app.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import App from 'flarum/App';
|
||||||
|
import store from 'flarum/initializers/store';
|
||||||
|
import preload from 'flarum/initializers/preload';
|
||||||
|
import routes from 'flarum/initializers/routes';
|
||||||
|
import boot from 'flarum/initializers/boot';
|
||||||
|
|
||||||
|
const app = new App();
|
||||||
|
|
||||||
|
app.initializers.add('store', store);
|
||||||
|
app.initializers.add('routes', routes);
|
||||||
|
|
||||||
|
app.initializers.add('preload', preload, -100);
|
||||||
|
app.initializers.add('boot', boot, -100);
|
||||||
|
|
||||||
|
app.extensionSettings = {};
|
||||||
|
|
||||||
|
app.getRequiredPermissions = function(permission) {
|
||||||
|
const required = [];
|
||||||
|
|
||||||
|
if (permission === 'startDiscussion' || permission.indexOf('discussion.') === 0) {
|
||||||
|
required.push('viewDiscussions');
|
||||||
|
}
|
||||||
|
if (permission === 'discussion.delete') {
|
||||||
|
required.push('discussion.hide');
|
||||||
|
}
|
||||||
|
if (permission === 'discussion.deletePosts') {
|
||||||
|
required.push('discussion.editPosts');
|
||||||
|
}
|
||||||
|
|
||||||
|
return required;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default app;
|
@@ -7,7 +7,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
|
|
||||||
export default class AddExtensionModal extends Modal {
|
export default class AddExtensionModal extends Modal {
|
||||||
className() {
|
className() {
|
@@ -7,7 +7,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import LinkButton from '../../common/components/LinkButton';
|
import LinkButton from 'flarum/components/LinkButton';
|
||||||
|
|
||||||
export default class AdminLinkButton extends LinkButton {
|
export default class AdminLinkButton extends LinkButton {
|
||||||
getButtonContent() {
|
getButtonContent() {
|
@@ -7,19 +7,20 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import AdminLinkButton from './AdminLinkButton';
|
import AdminLinkButton from 'flarum/components/AdminLinkButton';
|
||||||
import SelectDropdown from '../../common/components/SelectDropdown';
|
import SelectDropdown from 'flarum/components/SelectDropdown';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
|
||||||
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
export default class AdminNav extends Component {
|
export default class AdminNav extends Component {
|
||||||
view() {
|
view() {
|
||||||
return (
|
return (
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
className="AdminNav App-titleControl"
|
className="AdminNav App-titleControl"
|
||||||
buttonClassName="Button">
|
buttonClassName="Button"
|
||||||
{this.items().toArray()}
|
children={this.items().toArray()}
|
||||||
</SelectDropdown>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,42 +34,42 @@ export default class AdminNav extends Component {
|
|||||||
|
|
||||||
items.add('dashboard', AdminLinkButton.component({
|
items.add('dashboard', AdminLinkButton.component({
|
||||||
href: app.route('dashboard'),
|
href: app.route('dashboard'),
|
||||||
icon: 'far fa-chart-bar',
|
icon: 'bar-chart',
|
||||||
children: app.translator.trans('core.admin.nav.dashboard_button'),
|
children: app.translator.trans('core.admin.nav.dashboard_button'),
|
||||||
description: app.translator.trans('core.admin.nav.dashboard_text')
|
description: app.translator.trans('core.admin.nav.dashboard_text')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add('basics', AdminLinkButton.component({
|
items.add('basics', AdminLinkButton.component({
|
||||||
href: app.route('basics'),
|
href: app.route('basics'),
|
||||||
icon: 'fas fa-pencil-alt',
|
icon: 'pencil',
|
||||||
children: app.translator.trans('core.admin.nav.basics_button'),
|
children: app.translator.trans('core.admin.nav.basics_button'),
|
||||||
description: app.translator.trans('core.admin.nav.basics_text')
|
description: app.translator.trans('core.admin.nav.basics_text')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add('mail', AdminLinkButton.component({
|
items.add('mail', AdminLinkButton.component({
|
||||||
href: app.route('mail'),
|
href: app.route('mail'),
|
||||||
icon: 'fas fa-envelope',
|
icon: 'envelope',
|
||||||
children: app.translator.trans('core.admin.nav.email_button'),
|
children: app.translator.trans('core.admin.nav.email_button'),
|
||||||
description: app.translator.trans('core.admin.nav.email_text')
|
description: app.translator.trans('core.admin.nav.email_text')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add('permissions', AdminLinkButton.component({
|
items.add('permissions', AdminLinkButton.component({
|
||||||
href: app.route('permissions'),
|
href: app.route('permissions'),
|
||||||
icon: 'fas fa-key',
|
icon: 'key',
|
||||||
children: app.translator.trans('core.admin.nav.permissions_button'),
|
children: app.translator.trans('core.admin.nav.permissions_button'),
|
||||||
description: app.translator.trans('core.admin.nav.permissions_text')
|
description: app.translator.trans('core.admin.nav.permissions_text')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add('appearance', AdminLinkButton.component({
|
items.add('appearance', AdminLinkButton.component({
|
||||||
href: app.route('appearance'),
|
href: app.route('appearance'),
|
||||||
icon: 'fas fa-paint-brush',
|
icon: 'paint-brush',
|
||||||
children: app.translator.trans('core.admin.nav.appearance_button'),
|
children: app.translator.trans('core.admin.nav.appearance_button'),
|
||||||
description: app.translator.trans('core.admin.nav.appearance_text')
|
description: app.translator.trans('core.admin.nav.appearance_text')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add('extensions', AdminLinkButton.component({
|
items.add('extensions', AdminLinkButton.component({
|
||||||
href: app.route('extensions'),
|
href: app.route('extensions'),
|
||||||
icon: 'fas fa-puzzle-piece',
|
icon: 'puzzle-piece',
|
||||||
children: app.translator.trans('core.admin.nav.extensions_button'),
|
children: app.translator.trans('core.admin.nav.extensions_button'),
|
||||||
description: app.translator.trans('core.admin.nav.extensions_text')
|
description: app.translator.trans('core.admin.nav.extensions_text')
|
||||||
}));
|
}));
|
@@ -1,11 +1,10 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import Switch from '../../common/components/Switch';
|
import Switch from 'flarum/components/Switch';
|
||||||
import EditCustomCssModal from './EditCustomCssModal';
|
import EditCustomCssModal from 'flarum/components/EditCustomCssModal';
|
||||||
import EditCustomHeaderModal from './EditCustomHeaderModal';
|
import EditCustomHeaderModal from 'flarum/components/EditCustomHeaderModal';
|
||||||
import EditCustomFooterModal from './EditCustomFooterModal';
|
import UploadImageButton from 'flarum/components/UploadImageButton';
|
||||||
import UploadImageButton from './UploadImageButton';
|
import saveSettings from 'flarum/utils/saveSettings';
|
||||||
import saveSettings from '../utils/saveSettings';
|
|
||||||
|
|
||||||
export default class AppearancePage extends Page {
|
export default class AppearancePage extends Page {
|
||||||
init() {
|
init() {
|
||||||
@@ -29,8 +28,8 @@ export default class AppearancePage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="AppearancePage-colors-input">
|
<div className="AppearancePage-colors-input">
|
||||||
<input className="FormControl" type="text" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
|
<input className="FormControl" type="color" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
|
||||||
<input className="FormControl" type="text" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
|
<input className="FormControl" type="color" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Switch.component({
|
{Switch.component({
|
||||||
@@ -82,18 +81,6 @@ export default class AppearancePage extends Page {
|
|||||||
})}
|
})}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</legend>
|
|
||||||
<div className="helpText">
|
|
||||||
{app.translator.trans('core.admin.appearance.custom_footer_text')}
|
|
||||||
</div>
|
|
||||||
{Button.component({
|
|
||||||
className: 'Button',
|
|
||||||
children: app.translator.trans('core.admin.appearance.edit_footer_button'),
|
|
||||||
onclick: () => app.modal.show(new EditCustomFooterModal())
|
|
||||||
})}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
|
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
|
||||||
<div className="helpText">
|
<div className="helpText">
|
@@ -1,11 +1,11 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import FieldSet from '../../common/components/FieldSet';
|
import FieldSet from 'flarum/components/FieldSet';
|
||||||
import Select from '../../common/components/Select';
|
import Select from 'flarum/components/Select';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import Alert from '../../common/components/Alert';
|
import Alert from 'flarum/components/Alert';
|
||||||
import saveSettings from '../utils/saveSettings';
|
import saveSettings from 'flarum/utils/saveSettings';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import Switch from '../../common/components/Switch';
|
import Switch from 'flarum/components/Switch';
|
||||||
|
|
||||||
export default class BasicsPage extends Page {
|
export default class BasicsPage extends Page {
|
||||||
init() {
|
init() {
|
||||||
@@ -25,7 +25,7 @@ export default class BasicsPage extends Page {
|
|||||||
this.values = {};
|
this.values = {};
|
||||||
|
|
||||||
const settings = app.data.settings;
|
const settings = app.data.settings;
|
||||||
this.fields.forEach(key => this.values[key] = m.prop(settings[key]));
|
this.fields.forEach(key => this.values[key] = m.prop(settings[key] || false));
|
||||||
|
|
||||||
this.localeOptions = {};
|
this.localeOptions = {};
|
||||||
const locales = app.data.locales;
|
const locales = app.data.locales;
|
22
js/admin/src/components/DashboardPage.js
Normal file
22
js/admin/src/components/DashboardPage.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Page from 'flarum/components/Page';
|
||||||
|
|
||||||
|
export default class DashboardPage extends Page {
|
||||||
|
view() {
|
||||||
|
return (
|
||||||
|
<div className="DashboardPage">
|
||||||
|
<div className="container">
|
||||||
|
<h2>{app.translator.trans('core.admin.dashboard.welcome_text')}</h2>
|
||||||
|
<p>{app.translator.trans('core.admin.dashboard.version_text', {version: <strong>{app.forum.attribute('version')}</strong>})}</p>
|
||||||
|
<p>{app.translator.trans('core.admin.dashboard.beta_warning_text', {strong: <strong/>})}</p>
|
||||||
|
<ul>
|
||||||
|
<li>{app.translator.trans('core.admin.dashboard.contributing_text', {a: <a href="http://flarum.org/docs/contributing" target="_blank"/>})}</li>
|
||||||
|
<li>{app.translator.trans('core.admin.dashboard.troubleshooting_text', {a: <a href="http://flarum.org/docs/troubleshooting" target="_blank"/>})}</li>
|
||||||
|
<li>{app.translator.trans('core.admin.dashboard.support_text', {a: <a href="http://discuss.flarum.org/t/support" target="_blank"/>})}</li>
|
||||||
|
<li>{app.translator.trans('core.admin.dashboard.features_text', {a: <a href="http://discuss.flarum.org/t/features" target="_blank"/>})}</li>
|
||||||
|
<li>{app.translator.trans('core.admin.dashboard.extension_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import SettingsModal from './SettingsModal';
|
import SettingsModal from 'flarum/components/SettingsModal';
|
||||||
|
|
||||||
export default class EditCustomCssModal extends SettingsModal {
|
export default class EditCustomCssModal extends SettingsModal {
|
||||||
className() {
|
className() {
|
@@ -1,4 +1,4 @@
|
|||||||
import SettingsModal from './SettingsModal';
|
import SettingsModal from 'flarum/components/SettingsModal';
|
||||||
|
|
||||||
export default class EditCustomHeaderModal extends SettingsModal {
|
export default class EditCustomHeaderModal extends SettingsModal {
|
||||||
className() {
|
className() {
|
102
js/admin/src/components/EditGroupModal.js
Normal file
102
js/admin/src/components/EditGroupModal.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import Modal from 'flarum/components/Modal';
|
||||||
|
import Button from 'flarum/components/Button';
|
||||||
|
import Badge from 'flarum/components/Badge';
|
||||||
|
import Group from 'flarum/models/Group';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `EditGroupModal` component shows a modal dialog which allows the user
|
||||||
|
* to create or edit a group.
|
||||||
|
*/
|
||||||
|
export default class EditGroupModal extends Modal {
|
||||||
|
init() {
|
||||||
|
this.group = this.props.group || app.store.createRecord('groups');
|
||||||
|
|
||||||
|
this.nameSingular = m.prop(this.group.nameSingular() || '');
|
||||||
|
this.namePlural = m.prop(this.group.namePlural() || '');
|
||||||
|
this.icon = m.prop(this.group.icon() || '');
|
||||||
|
this.color = m.prop(this.group.color() || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
className() {
|
||||||
|
return 'EditGroupModal Modal--small';
|
||||||
|
}
|
||||||
|
|
||||||
|
title() {
|
||||||
|
return [
|
||||||
|
this.color() || this.icon() ? Badge.component({
|
||||||
|
icon: this.icon(),
|
||||||
|
style: {backgroundColor: this.color()}
|
||||||
|
}) : '',
|
||||||
|
' ',
|
||||||
|
this.namePlural() || app.translator.trans('core.admin.edit_group.title')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
content() {
|
||||||
|
return (
|
||||||
|
<div className="Modal-body">
|
||||||
|
<div className="Form">
|
||||||
|
<div className="Form-group">
|
||||||
|
<label>{app.translator.trans('core.admin.edit_group.name_label')}</label>
|
||||||
|
<div className="EditGroupModal-name-input">
|
||||||
|
<input className="FormControl" placeholder={app.translator.trans('core.admin.edit_group.singular_placeholder')} value={this.nameSingular()} oninput={m.withAttr('value', this.nameSingular)}/>
|
||||||
|
<input className="FormControl" placeholder={app.translator.trans('core.admin.edit_group.plural_placeholder')} value={this.namePlural()} oninput={m.withAttr('value', this.namePlural)}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="Form-group">
|
||||||
|
<label>{app.translator.trans('core.admin.edit_group.color_label')}</label>
|
||||||
|
<input className="FormControl" placeholder="#aaaaaa" value={this.color()} oninput={m.withAttr('value', this.color)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="Form-group">
|
||||||
|
<label>{app.translator.trans('core.admin.edit_group.icon_label')}</label>
|
||||||
|
<div className="helpText">
|
||||||
|
{app.translator.trans('core.admin.edit_group.icon_text', {a: <a href="http://fortawesome.github.io/Font-Awesome/icons/" tabindex="-1"/>})}
|
||||||
|
</div>
|
||||||
|
<input className="FormControl" placeholder="bolt" value={this.icon()} oninput={m.withAttr('value', this.icon)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="Form-group">
|
||||||
|
{Button.component({
|
||||||
|
type: 'submit',
|
||||||
|
className: 'Button Button--primary EditGroupModal-save',
|
||||||
|
loading: this.loading,
|
||||||
|
children: app.translator.trans('core.admin.edit_group.submit_button')
|
||||||
|
})}
|
||||||
|
{this.group.exists && this.group.id() !== Group.ADMINISTRATOR_ID ? (
|
||||||
|
<button type="button" className="Button EditGroupModal-delete" onclick={this.deleteGroup.bind(this)}>
|
||||||
|
{app.translator.trans('core.admin.edit_group.delete_button')}
|
||||||
|
</button>
|
||||||
|
) : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onsubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
this.group.save({
|
||||||
|
nameSingular: this.nameSingular(),
|
||||||
|
namePlural: this.namePlural(),
|
||||||
|
color: this.color(),
|
||||||
|
icon: this.icon()
|
||||||
|
}, {errorHandler: this.onerror.bind(this)})
|
||||||
|
.then(this.hide.bind(this))
|
||||||
|
.catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGroup() {
|
||||||
|
if (confirm(app.translator.trans('core.admin.edit_group.delete_confirmation'))) {
|
||||||
|
this.group.delete().then(() => m.redraw());
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import LinkButton from '../../common/components/LinkButton';
|
import LinkButton from 'flarum/components/LinkButton';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import Separator from '../../common/components/Separator';
|
import Separator from 'flarum/components/Separator';
|
||||||
import AddExtensionModal from './AddExtensionModal';
|
import AddExtensionModal from 'flarum/components/AddExtensionModal';
|
||||||
import LoadingModal from './LoadingModal';
|
import LoadingModal from 'flarum/components/LoadingModal';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
export default class ExtensionsPage extends Page {
|
export default class ExtensionsPage extends Page {
|
||||||
view() {
|
view() {
|
||||||
@@ -17,7 +17,7 @@ export default class ExtensionsPage extends Page {
|
|||||||
<div className="container">
|
<div className="container">
|
||||||
{Button.component({
|
{Button.component({
|
||||||
children: app.translator.trans('core.admin.extensions.add_button'),
|
children: app.translator.trans('core.admin.extensions.add_button'),
|
||||||
icon: 'fas fa-plus',
|
icon: 'plus',
|
||||||
className: 'Button Button--primary',
|
className: 'Button Button--primary',
|
||||||
onclick: () => app.modal.show(new AddExtensionModal())
|
onclick: () => app.modal.show(new AddExtensionModal())
|
||||||
})}
|
})}
|
||||||
@@ -42,18 +42,15 @@ export default class ExtensionsPage extends Page {
|
|||||||
className="ExtensionListItem-controls"
|
className="ExtensionListItem-controls"
|
||||||
buttonClassName="Button Button--icon Button--flat"
|
buttonClassName="Button Button--icon Button--flat"
|
||||||
menuClassName="Dropdown-menu--right"
|
menuClassName="Dropdown-menu--right"
|
||||||
icon="fas fa-ellipsis-h">
|
icon="ellipsis-h">
|
||||||
{controls}
|
{controls}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
) : ''}
|
) : ''}
|
||||||
<div className="ExtensionListItem-main">
|
|
||||||
<label className="ExtensionListItem-title">
|
<label className="ExtensionListItem-title">
|
||||||
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
|
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
|
||||||
{extension.extra['flarum-extension'].title}
|
{extension.extra['flarum-extension'].title}
|
||||||
</label>
|
</label>
|
||||||
<div className="ExtensionListItem-version">{extension.version}</div>
|
<div className="ExtensionListItem-version">{extension.version}</div>
|
||||||
<div className="ExtensionListItem-description">{extension.description}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>;
|
</li>;
|
||||||
})}
|
})}
|
||||||
@@ -70,7 +67,7 @@ export default class ExtensionsPage extends Page {
|
|||||||
|
|
||||||
if (app.extensionSettings[name]) {
|
if (app.extensionSettings[name]) {
|
||||||
items.add('settings', Button.component({
|
items.add('settings', Button.component({
|
||||||
icon: 'fas fa-cog',
|
icon: 'cog',
|
||||||
children: app.translator.trans('core.admin.extensions.settings_button'),
|
children: app.translator.trans('core.admin.extensions.settings_button'),
|
||||||
onclick: app.extensionSettings[name]
|
onclick: app.extensionSettings[name]
|
||||||
}));
|
}));
|
||||||
@@ -78,7 +75,7 @@ export default class ExtensionsPage extends Page {
|
|||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
items.add('uninstall', Button.component({
|
items.add('uninstall', Button.component({
|
||||||
icon: 'far fa-trash-alt',
|
icon: 'trash-o',
|
||||||
children: app.translator.trans('core.admin.extensions.uninstall_button'),
|
children: app.translator.trans('core.admin.extensions.uninstall_button'),
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
app.request({
|
app.request({
|
@@ -1,6 +1,6 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `HeaderPrimary` component displays primary header controls. On the
|
* The `HeaderPrimary` component displays primary header controls. On the
|
@@ -1,7 +1,7 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import SessionDropdown from './SessionDropdown';
|
import SessionDropdown from 'flarum/components/SessionDropdown';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `HeaderSecondary` component displays secondary header controls.
|
* The `HeaderSecondary` component displays secondary header controls.
|
@@ -1,4 +1,4 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
|
|
||||||
export default class LoadingModal extends Modal {
|
export default class LoadingModal extends Modal {
|
||||||
isDismissible() {
|
isDismissible() {
|
@@ -1,8 +1,8 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import FieldSet from '../../common/components/FieldSet';
|
import FieldSet from 'flarum/components/FieldSet';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import Alert from '../../common/components/Alert';
|
import Alert from 'flarum/components/Alert';
|
||||||
import saveSettings from '../utils/saveSettings';
|
import saveSettings from 'flarum/utils/saveSettings';
|
||||||
|
|
||||||
export default class MailPage extends Page {
|
export default class MailPage extends Page {
|
||||||
init() {
|
init() {
|
@@ -1,4 +1,4 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Page` component
|
* The `Page` component
|
@@ -1,9 +1,9 @@
|
|||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import Separator from '../../common/components/Separator';
|
import Separator from 'flarum/components/Separator';
|
||||||
import Group from '../../common/models/Group';
|
import Group from 'flarum/models/Group';
|
||||||
import Badge from '../../common/components/Badge';
|
import Badge from 'flarum/components/Badge';
|
||||||
import GroupBadge from '../../common/components/GroupBadge';
|
import GroupBadge from 'flarum/components/GroupBadge';
|
||||||
|
|
||||||
function badgeForId(id) {
|
function badgeForId(id) {
|
||||||
const group = app.store.getById('groups', id);
|
const group = app.store.getById('groups', id);
|
||||||
@@ -52,9 +52,9 @@ export default class PermissionDropdown extends Dropdown {
|
|||||||
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
|
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
|
||||||
|
|
||||||
if (everyone) {
|
if (everyone) {
|
||||||
this.props.label = Badge.component({icon: 'fas fa-globe'});
|
this.props.label = Badge.component({icon: 'globe'});
|
||||||
} else if (members) {
|
} else if (members) {
|
||||||
this.props.label = Badge.component({icon: 'fas fa-user'});
|
this.props.label = Badge.component({icon: 'user'});
|
||||||
} else {
|
} else {
|
||||||
this.props.label = [
|
this.props.label = [
|
||||||
badgeForId(Group.ADMINISTRATOR_ID),
|
badgeForId(Group.ADMINISTRATOR_ID),
|
||||||
@@ -66,8 +66,8 @@ export default class PermissionDropdown extends Dropdown {
|
|||||||
if (this.props.allowGuest) {
|
if (this.props.allowGuest) {
|
||||||
this.props.children.push(
|
this.props.children.push(
|
||||||
Button.component({
|
Button.component({
|
||||||
children: [Badge.component({icon: 'fas fa-globe'}), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
|
children: [Badge.component({icon: 'globe'}), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
|
||||||
icon: everyone ? 'fas fa-check' : true,
|
icon: everyone ? 'check' : true,
|
||||||
onclick: () => this.save([Group.GUEST_ID]),
|
onclick: () => this.save([Group.GUEST_ID]),
|
||||||
disabled: this.isGroupDisabled(Group.GUEST_ID)
|
disabled: this.isGroupDisabled(Group.GUEST_ID)
|
||||||
})
|
})
|
||||||
@@ -76,8 +76,8 @@ export default class PermissionDropdown extends Dropdown {
|
|||||||
|
|
||||||
this.props.children.push(
|
this.props.children.push(
|
||||||
Button.component({
|
Button.component({
|
||||||
children: [Badge.component({icon: 'fas fa-user'}), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
|
children: [Badge.component({icon: 'user'}), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
|
||||||
icon: members ? 'fas fa-check' : true,
|
icon: members ? 'check' : true,
|
||||||
onclick: () => this.save([Group.MEMBER_ID]),
|
onclick: () => this.save([Group.MEMBER_ID]),
|
||||||
disabled: this.isGroupDisabled(Group.MEMBER_ID)
|
disabled: this.isGroupDisabled(Group.MEMBER_ID)
|
||||||
}),
|
}),
|
||||||
@@ -86,7 +86,7 @@ export default class PermissionDropdown extends Dropdown {
|
|||||||
|
|
||||||
Button.component({
|
Button.component({
|
||||||
children: [badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()],
|
children: [badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()],
|
||||||
icon: !everyone && !members ? 'fas fa-check' : true,
|
icon: !everyone && !members ? 'check' : true,
|
||||||
disabled: !everyone && !members,
|
disabled: !everyone && !members,
|
||||||
onclick: e => {
|
onclick: e => {
|
||||||
if (e.shiftKey) e.stopPropagation();
|
if (e.shiftKey) e.stopPropagation();
|
||||||
@@ -101,7 +101,7 @@ export default class PermissionDropdown extends Dropdown {
|
|||||||
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
|
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
|
||||||
.map(group => Button.component({
|
.map(group => Button.component({
|
||||||
children: [badgeForId(group.id()), ' ', group.namePlural()],
|
children: [badgeForId(group.id()), ' ', group.namePlural()],
|
||||||
icon: groupIds.indexOf(group.id()) !== -1 ? 'fas fa-check' : true,
|
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
|
||||||
onclick: (e) => {
|
onclick: (e) => {
|
||||||
if (e.shiftKey) e.stopPropagation();
|
if (e.shiftKey) e.stopPropagation();
|
||||||
this.toggle(group.id());
|
this.toggle(group.id());
|
@@ -1,9 +1,9 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import PermissionDropdown from './PermissionDropdown';
|
import PermissionDropdown from 'flarum/components/PermissionDropdown';
|
||||||
import SettingDropdown from './SettingDropdown';
|
import SettingDropdown from 'flarum/components/SettingDropdown';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
|
|
||||||
export default class PermissionGrid extends Component {
|
export default class PermissionGrid extends Component {
|
||||||
init() {
|
init() {
|
||||||
@@ -29,7 +29,7 @@ export default class PermissionGrid extends Component {
|
|||||||
{scopes.map(scope => (
|
{scopes.map(scope => (
|
||||||
<th>
|
<th>
|
||||||
{scope.label}{' '}
|
{scope.label}{' '}
|
||||||
{scope.onremove ? Button.component({icon: 'fas fa-times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove}) : ''}
|
{scope.onremove ? Button.component({icon: 'times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove}) : ''}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
<th>{this.scopeControlItems().toArray()}</th>
|
<th>{this.scopeControlItems().toArray()}</th>
|
||||||
@@ -85,21 +85,21 @@ export default class PermissionGrid extends Component {
|
|||||||
const items = new ItemList();
|
const items = new ItemList();
|
||||||
|
|
||||||
items.add('viewDiscussions', {
|
items.add('viewDiscussions', {
|
||||||
icon: 'fas fa-eye',
|
icon: 'eye',
|
||||||
label: app.translator.trans('core.admin.permissions.view_discussions_label'),
|
label: app.translator.trans('core.admin.permissions.view_discussions_label'),
|
||||||
permission: 'viewDiscussions',
|
permission: 'viewDiscussions',
|
||||||
allowGuest: true
|
allowGuest: true
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
items.add('viewUserList', {
|
items.add('viewUserList', {
|
||||||
icon: 'fas fa-users',
|
icon: 'users',
|
||||||
label: app.translator.trans('core.admin.permissions.view_user_list_label'),
|
label: app.translator.trans('core.admin.permissions.view_user_list_label'),
|
||||||
permission: 'viewUserList',
|
permission: 'viewUserList',
|
||||||
allowGuest: true
|
allowGuest: true
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
items.add('signUp', {
|
items.add('signUp', {
|
||||||
icon: 'fas fa-user-plus',
|
icon: 'user-plus',
|
||||||
label: app.translator.trans('core.admin.permissions.sign_up_label'),
|
label: app.translator.trans('core.admin.permissions.sign_up_label'),
|
||||||
setting: () => SettingDropdown.component({
|
setting: () => SettingDropdown.component({
|
||||||
key: 'allow_sign_up',
|
key: 'allow_sign_up',
|
||||||
@@ -110,12 +110,6 @@ export default class PermissionGrid extends Component {
|
|||||||
})
|
})
|
||||||
}, 90);
|
}, 90);
|
||||||
|
|
||||||
items.add('viewLastSeenAt', {
|
|
||||||
icon: 'far fa-clock',
|
|
||||||
label: app.translator.trans('core.admin.permissions.view_last_seen_at_label'),
|
|
||||||
permission: 'user.viewLastSeenAt',
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,13 +117,13 @@ export default class PermissionGrid extends Component {
|
|||||||
const items = new ItemList();
|
const items = new ItemList();
|
||||||
|
|
||||||
items.add('start', {
|
items.add('start', {
|
||||||
icon: 'fas fa-edit',
|
icon: 'edit',
|
||||||
label: app.translator.trans('core.admin.permissions.start_discussions_label'),
|
label: app.translator.trans('core.admin.permissions.start_discussions_label'),
|
||||||
permission: 'startDiscussion'
|
permission: 'startDiscussion'
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
items.add('allowRenaming', {
|
items.add('allowRenaming', {
|
||||||
icon: 'fas fa-i-cursor',
|
icon: 'i-cursor',
|
||||||
label: app.translator.trans('core.admin.permissions.allow_renaming_label'),
|
label: app.translator.trans('core.admin.permissions.allow_renaming_label'),
|
||||||
setting: () => {
|
setting: () => {
|
||||||
const minutes = parseInt(app.data.settings.allow_renaming, 10);
|
const minutes = parseInt(app.data.settings.allow_renaming, 10);
|
||||||
@@ -155,13 +149,13 @@ export default class PermissionGrid extends Component {
|
|||||||
const items = new ItemList();
|
const items = new ItemList();
|
||||||
|
|
||||||
items.add('reply', {
|
items.add('reply', {
|
||||||
icon: 'fas fa-reply',
|
icon: 'reply',
|
||||||
label: app.translator.trans('core.admin.permissions.reply_to_discussions_label'),
|
label: app.translator.trans('core.admin.permissions.reply_to_discussions_label'),
|
||||||
permission: 'discussion.reply'
|
permission: 'discussion.reply'
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
items.add('allowPostEditing', {
|
items.add('allowPostEditing', {
|
||||||
icon: 'fas fa-pencil-alt',
|
icon: 'pencil',
|
||||||
label: app.translator.trans('core.admin.permissions.allow_post_editing_label'),
|
label: app.translator.trans('core.admin.permissions.allow_post_editing_label'),
|
||||||
setting: () => {
|
setting: () => {
|
||||||
const minutes = parseInt(app.data.settings.allow_post_editing, 10);
|
const minutes = parseInt(app.data.settings.allow_post_editing, 10);
|
||||||
@@ -187,43 +181,37 @@ export default class PermissionGrid extends Component {
|
|||||||
const items = new ItemList();
|
const items = new ItemList();
|
||||||
|
|
||||||
items.add('viewIpsPosts', {
|
items.add('viewIpsPosts', {
|
||||||
icon: 'fas fa-bullseye',
|
icon: 'bullseye',
|
||||||
label: app.translator.trans('core.admin.permissions.view_post_ips_label'),
|
label: app.translator.trans('core.admin.permissions.view_post_ips_label'),
|
||||||
permission: 'discussion.viewIpsPosts'
|
permission: 'discussion.viewIpsPosts'
|
||||||
}, 110);
|
}, 110);
|
||||||
|
|
||||||
items.add('renameDiscussions', {
|
items.add('renameDiscussions', {
|
||||||
icon: 'fas fa-i-cursor',
|
icon: 'i-cursor',
|
||||||
label: app.translator.trans('core.admin.permissions.rename_discussions_label'),
|
label: app.translator.trans('core.admin.permissions.rename_discussions_label'),
|
||||||
permission: 'discussion.rename'
|
permission: 'discussion.rename'
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
items.add('hideDiscussions', {
|
items.add('hideDiscussions', {
|
||||||
icon: 'far fa-trash-alt',
|
icon: 'trash-o',
|
||||||
label: app.translator.trans('core.admin.permissions.delete_discussions_label'),
|
label: app.translator.trans('core.admin.permissions.delete_discussions_label'),
|
||||||
permission: 'discussion.hide'
|
permission: 'discussion.hide'
|
||||||
}, 90);
|
}, 90);
|
||||||
|
|
||||||
items.add('deleteDiscussions', {
|
items.add('deleteDiscussions', {
|
||||||
icon: 'fas fa-times',
|
icon: 'times',
|
||||||
label: app.translator.trans('core.admin.permissions.delete_discussions_forever_label'),
|
label: app.translator.trans('core.admin.permissions.delete_discussions_forever_label'),
|
||||||
permission: 'discussion.delete'
|
permission: 'discussion.delete'
|
||||||
}, 80);
|
}, 80);
|
||||||
|
|
||||||
items.add('editPosts', {
|
items.add('editPosts', {
|
||||||
icon: 'fas fa-pencil-alt',
|
icon: 'pencil',
|
||||||
label: app.translator.trans('core.admin.permissions.edit_posts_label'),
|
label: app.translator.trans('core.admin.permissions.edit_and_delete_posts_label'),
|
||||||
permission: 'discussion.editPosts'
|
permission: 'discussion.editPosts'
|
||||||
}, 70);
|
}, 70);
|
||||||
|
|
||||||
items.add('hidePosts', {
|
|
||||||
icon: 'far fa-trash-alt',
|
|
||||||
label: app.translator.trans('core.admin.permissions.delete_posts_label'),
|
|
||||||
permission: 'discussion.hidePosts'
|
|
||||||
}, 60);
|
|
||||||
|
|
||||||
items.add('deletePosts', {
|
items.add('deletePosts', {
|
||||||
icon: 'fas fa-times',
|
icon: 'times',
|
||||||
label: app.translator.trans('core.admin.permissions.delete_posts_forever_label'),
|
label: app.translator.trans('core.admin.permissions.delete_posts_forever_label'),
|
||||||
permission: 'discussion.deletePosts'
|
permission: 'discussion.deletePosts'
|
||||||
}, 60);
|
}, 60);
|
@@ -1,9 +1,9 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import GroupBadge from '../../common/components/GroupBadge';
|
import GroupBadge from 'flarum/components/GroupBadge';
|
||||||
import EditGroupModal from './EditGroupModal';
|
import EditGroupModal from 'flarum/components/EditGroupModal';
|
||||||
import Group from '../../common/models/Group';
|
import Group from 'flarum/models/Group';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import PermissionGrid from './PermissionGrid';
|
import PermissionGrid from 'flarum/components/PermissionGrid';
|
||||||
|
|
||||||
export default class PermissionsPage extends Page {
|
export default class PermissionsPage extends Page {
|
||||||
view() {
|
view() {
|
||||||
@@ -24,7 +24,7 @@ export default class PermissionsPage extends Page {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
<button className="Button Group Group--add" onclick={() => app.modal.show(new EditGroupModal())}>
|
<button className="Button Group Group--add" onclick={() => app.modal.show(new EditGroupModal())}>
|
||||||
{icon('fas fa-plus', {className: 'Group-icon'})}
|
{icon('plus', {className: 'Group-icon'})}
|
||||||
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
|
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@@ -1,8 +1,8 @@
|
|||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
import username from '../../common/helpers/username';
|
import username from 'flarum/helpers/username';
|
||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `SessionDropdown` component shows a button with the current user's
|
* The `SessionDropdown` component shows a button with the current user's
|
||||||
@@ -42,7 +42,7 @@ export default class SessionDropdown extends Dropdown {
|
|||||||
|
|
||||||
items.add('logOut',
|
items.add('logOut',
|
||||||
Button.component({
|
Button.component({
|
||||||
icon: 'fas fa-sign-out-alt',
|
icon: 'sign-out',
|
||||||
children: app.translator.trans('core.admin.header.log_out_button'),
|
children: app.translator.trans('core.admin.header.log_out_button'),
|
||||||
onclick: app.session.logout.bind(app.session)
|
onclick: app.session.logout.bind(app.session)
|
||||||
}),
|
}),
|
@@ -1,6 +1,6 @@
|
|||||||
import SelectDropdown from '../../common/components/SelectDropdown';
|
import SelectDropdown from 'flarum/components/SelectDropdown';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import saveSettings from '../utils/saveSettings';
|
import saveSettings from 'flarum/utils/saveSettings';
|
||||||
|
|
||||||
export default class SettingDropdown extends SelectDropdown {
|
export default class SettingDropdown extends SelectDropdown {
|
||||||
static initProps(props) {
|
static initProps(props) {
|
||||||
@@ -8,7 +8,7 @@ export default class SettingDropdown extends SelectDropdown {
|
|||||||
|
|
||||||
props.className = 'SettingDropdown';
|
props.className = 'SettingDropdown';
|
||||||
props.buttonClassName = 'Button Button--text';
|
props.buttonClassName = 'Button Button--text';
|
||||||
props.caretIcon = 'fas fa-caret-down';
|
props.caretIcon = 'caret-down';
|
||||||
props.defaultLabel = 'Custom';
|
props.defaultLabel = 'Custom';
|
||||||
|
|
||||||
props.children = props.options.map(({value, label}) => {
|
props.children = props.options.map(({value, label}) => {
|
||||||
@@ -16,7 +16,7 @@ export default class SettingDropdown extends SelectDropdown {
|
|||||||
|
|
||||||
return Button.component({
|
return Button.component({
|
||||||
children: label,
|
children: label,
|
||||||
icon: active ? 'fas fa-check' : true,
|
icon: active ? 'check' : true,
|
||||||
onclick: saveSettings.bind(this, {[props.key]: value}),
|
onclick: saveSettings.bind(this, {[props.key]: value}),
|
||||||
active
|
active
|
||||||
});
|
});
|
@@ -1,6 +1,6 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import saveSettings from '../utils/saveSettings';
|
import saveSettings from 'flarum/utils/saveSettings';
|
||||||
|
|
||||||
export default class SettingsModal extends Modal {
|
export default class SettingsModal extends Modal {
|
||||||
init() {
|
init() {
|
@@ -1,4 +1,4 @@
|
|||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
export default class UploadImageButton extends Button {
|
export default class UploadImageButton extends Button {
|
||||||
init() {
|
init() {
|
66
js/admin/src/initializers/boot.js
Normal file
66
js/admin/src/initializers/boot.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*global FastClick*/
|
||||||
|
|
||||||
|
import ScrollListener from 'flarum/utils/ScrollListener';
|
||||||
|
import Drawer from 'flarum/utils/Drawer';
|
||||||
|
import mapRoutes from 'flarum/utils/mapRoutes';
|
||||||
|
|
||||||
|
import Navigation from 'flarum/components/Navigation';
|
||||||
|
import HeaderPrimary from 'flarum/components/HeaderPrimary';
|
||||||
|
import HeaderSecondary from 'flarum/components/HeaderSecondary';
|
||||||
|
import AdminNav from 'flarum/components/AdminNav';
|
||||||
|
import ModalManager from 'flarum/components/ModalManager';
|
||||||
|
import AlertManager from 'flarum/components/AlertManager';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `boot` initializer boots up the admin app. It initializes some app
|
||||||
|
* globals, mounts components to the page, and begins routing.
|
||||||
|
*
|
||||||
|
* @param {ForumApp} app
|
||||||
|
*/
|
||||||
|
export default function boot(app) {
|
||||||
|
m.startComputation();
|
||||||
|
|
||||||
|
m.mount(document.getElementById('app-navigation'), Navigation.component({className: 'App-backControl', drawer: true}));
|
||||||
|
m.mount(document.getElementById('header-navigation'), Navigation.component());
|
||||||
|
m.mount(document.getElementById('header-primary'), HeaderPrimary.component());
|
||||||
|
m.mount(document.getElementById('header-secondary'), HeaderSecondary.component());
|
||||||
|
m.mount(document.getElementById('admin-navigation'), AdminNav.component());
|
||||||
|
|
||||||
|
app.drawer = new Drawer();
|
||||||
|
app.modal = m.mount(document.getElementById('modal'), ModalManager.component());
|
||||||
|
app.alerts = m.mount(document.getElementById('alerts'), AlertManager.component());
|
||||||
|
app.history = {
|
||||||
|
canGoBack: () => true,
|
||||||
|
getPrevious: () => {},
|
||||||
|
backUrl: () => app.forum.attribute('baseUrl'),
|
||||||
|
back: function() {
|
||||||
|
window.location = this.backUrl();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
m.route.mode = 'hash';
|
||||||
|
m.route(document.getElementById('content'), '/', mapRoutes(app.routes));
|
||||||
|
|
||||||
|
m.endComputation();
|
||||||
|
|
||||||
|
// Add a class to the body which indicates that the page has been scrolled
|
||||||
|
// down.
|
||||||
|
new ScrollListener(top => {
|
||||||
|
const $app = $('#app');
|
||||||
|
const offset = $app.offset().top;
|
||||||
|
|
||||||
|
$app
|
||||||
|
.toggleClass('affix', top >= offset)
|
||||||
|
.toggleClass('scrolled', top > offset);
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
app.booted = true;
|
||||||
|
|
||||||
|
// If an extension has just been enabled, then we will run its settings
|
||||||
|
// callback.
|
||||||
|
const enabled = localStorage.getItem('enabledExtension');
|
||||||
|
if (enabled && app.extensionSettings[enabled]) {
|
||||||
|
app.extensionSettings[enabled]();
|
||||||
|
localStorage.removeItem('enabledExtension');
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,12 @@
|
|||||||
import DashboardPage from './components/DashboardPage';
|
import DashboardPage from 'flarum/components/DashboardPage';
|
||||||
import BasicsPage from './components/BasicsPage';
|
import BasicsPage from 'flarum/components/BasicsPage';
|
||||||
import PermissionsPage from './components/PermissionsPage';
|
import PermissionsPage from 'flarum/components/PermissionsPage';
|
||||||
import AppearancePage from './components/AppearancePage';
|
import AppearancePage from 'flarum/components/AppearancePage';
|
||||||
import ExtensionsPage from './components/ExtensionsPage';
|
import ExtensionsPage from 'flarum/components/ExtensionsPage';
|
||||||
import MailPage from './components/MailPage';
|
import MailPage from 'flarum/components/MailPage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `routes` initializer defines the forum app's routes.
|
* The `routes` initializer defines the admin app's routes.
|
||||||
*
|
*
|
||||||
* @param {App} app
|
* @param {App} app
|
||||||
*/
|
*/
|
17
js/bower.json
Normal file
17
js/bower.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "flarum",
|
||||||
|
"dependencies": {
|
||||||
|
"jquery": "~2.1.3",
|
||||||
|
"jquery.hotkeys": "jeresig/jquery.hotkeys#0.2.0",
|
||||||
|
"bootstrap": "~3.3.2",
|
||||||
|
"spin.js": "~2.0.1",
|
||||||
|
"moment": "~2.8.4",
|
||||||
|
"color-thief": "v2.0",
|
||||||
|
"mithril": "lhorie/mithril.js#v0.2.5",
|
||||||
|
"es6-micro-loader": "caridy/es6-micro-loader#v0.2.1",
|
||||||
|
"fastclick": "~1.0.6",
|
||||||
|
"autolink": "~1.0.0",
|
||||||
|
"m.attrs.bidi": "tobscure/m.attrs.bidi",
|
||||||
|
"punycode": "http://cdnjs.cloudflare.com/ajax/libs/punycode/1.4.1/punycode.js"
|
||||||
|
}
|
||||||
|
}
|
40
js/dist/admin.js
vendored
40
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/admin.js.map
vendored
1
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
67
js/dist/forum.js
vendored
67
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/forum.js.map
vendored
1
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
11
js/forum.js
11
js/forum.js
@@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './src/common';
|
|
||||||
export * from './src/forum';
|
|
1
js/forum/.gitignore
vendored
Normal file
1
js/forum/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
36
js/forum/Gulpfile.js
Normal file
36
js/forum/Gulpfile.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
var gulp = require('flarum-gulp');
|
||||||
|
|
||||||
|
var bowerDir = '../bower_components';
|
||||||
|
|
||||||
|
gulp({
|
||||||
|
includeHelpers: true,
|
||||||
|
files: [
|
||||||
|
bowerDir + '/es6-micro-loader/dist/system-polyfill.js',
|
||||||
|
|
||||||
|
bowerDir + '/mithril/mithril.js',
|
||||||
|
bowerDir + '/m.attrs.bidi/bidi.js',
|
||||||
|
bowerDir + '/jquery/dist/jquery.js',
|
||||||
|
bowerDir + '/jquery.hotkeys/jquery.hotkeys.js',
|
||||||
|
bowerDir + '/color-thief/src/color-thief.js',
|
||||||
|
bowerDir + '/moment/moment.js',
|
||||||
|
bowerDir + '/autolink/autolink-min.js',
|
||||||
|
|
||||||
|
bowerDir + '/bootstrap/js/affix.js',
|
||||||
|
bowerDir + '/bootstrap/js/dropdown.js',
|
||||||
|
bowerDir + '/bootstrap/js/modal.js',
|
||||||
|
bowerDir + '/bootstrap/js/tooltip.js',
|
||||||
|
bowerDir + '/bootstrap/js/transition.js',
|
||||||
|
|
||||||
|
bowerDir + '/spin.js/spin.js',
|
||||||
|
bowerDir + '/spin.js/jquery.spin.js',
|
||||||
|
bowerDir + '/fastclick/lib/fastclick.js',
|
||||||
|
bowerDir + '/punycode/index.js'
|
||||||
|
],
|
||||||
|
modules: {
|
||||||
|
'flarum': [
|
||||||
|
'src/**/*.js',
|
||||||
|
'../lib/**/*.js'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
outputFile: 'dist/app.js'
|
||||||
|
});
|
32990
js/forum/dist/app.js
vendored
Normal file
32990
js/forum/dist/app.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
js/forum/package.json
Normal file
7
js/forum/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^3.9.1",
|
||||||
|
"flarum-gulp": "^0.2.0"
|
||||||
|
}
|
||||||
|
}
|
103
js/forum/src/ForumApp.js
Normal file
103
js/forum/src/ForumApp.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import History from 'flarum/utils/History';
|
||||||
|
import App from 'flarum/App';
|
||||||
|
import Search from 'flarum/components/Search';
|
||||||
|
import Composer from 'flarum/components/Composer';
|
||||||
|
import ReplyComposer from 'flarum/components/ReplyComposer';
|
||||||
|
import DiscussionPage from 'flarum/components/DiscussionPage';
|
||||||
|
import SignUpModal from 'flarum/components/SignUpModal';
|
||||||
|
|
||||||
|
export default class ForumApp extends App {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The app's history stack, which keeps track of which routes the user visits
|
||||||
|
* so that they can easily navigate back to the previous route.
|
||||||
|
*
|
||||||
|
* @type {History}
|
||||||
|
*/
|
||||||
|
this.history = new History();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which controls the state of the page's side pane.
|
||||||
|
*
|
||||||
|
* @type {Pane}
|
||||||
|
*/
|
||||||
|
this.pane = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The page's search component instance.
|
||||||
|
*
|
||||||
|
* @type {SearchBox}
|
||||||
|
*/
|
||||||
|
this.search = new Search();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which controls the state of the page's drawer.
|
||||||
|
*
|
||||||
|
* @type {Drawer}
|
||||||
|
*/
|
||||||
|
this.drawer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of post types to their components.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.postComponents = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of notification types to their components.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.notificationComponents = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether or not the user is currently composing a reply to a
|
||||||
|
* discussion.
|
||||||
|
*
|
||||||
|
* @param {Discussion} discussion
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
composingReplyTo(discussion) {
|
||||||
|
return this.composer.component instanceof ReplyComposer &&
|
||||||
|
this.composer.component.props.discussion === discussion &&
|
||||||
|
this.composer.position !== Composer.PositionEnum.HIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether or not the user is currently viewing a discussion.
|
||||||
|
*
|
||||||
|
* @param {Discussion} discussion
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
viewingDiscussion(discussion) {
|
||||||
|
return this.current instanceof DiscussionPage &&
|
||||||
|
this.current.discussion === discussion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when an external authenticator (social login) action has
|
||||||
|
* completed.
|
||||||
|
*
|
||||||
|
* If the payload indicates that the user has been logged in, then the page
|
||||||
|
* will be reloaded. Otherwise, a SignUpModal will be opened, prefilled
|
||||||
|
* with the provided details.
|
||||||
|
*
|
||||||
|
* @param {Object} payload A dictionary of props to pass into the sign up
|
||||||
|
* modal. A truthy `authenticated` prop indicates that the user has logged
|
||||||
|
* in, and thus the page is reloaded.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
authenticationComplete(payload) {
|
||||||
|
if (payload.authenticated) {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
const modal = new SignUpModal(payload);
|
||||||
|
this.modal.show(modal);
|
||||||
|
modal.$('[name=password]').focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
js/forum/src/app.js
Normal file
21
js/forum/src/app.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import ForumApp from 'flarum/ForumApp';
|
||||||
|
import store from 'flarum/initializers/store';
|
||||||
|
import preload from 'flarum/initializers/preload';
|
||||||
|
import routes from 'flarum/initializers/routes';
|
||||||
|
import components from 'flarum/initializers/components';
|
||||||
|
import humanTime from 'flarum/initializers/humanTime';
|
||||||
|
import boot from 'flarum/initializers/boot';
|
||||||
|
import alertEmailConfirmation from 'flarum/initializers/alertEmailConfirmation';
|
||||||
|
|
||||||
|
const app = new ForumApp();
|
||||||
|
|
||||||
|
app.initializers.add('store', store);
|
||||||
|
app.initializers.add('routes', routes);
|
||||||
|
app.initializers.add('components', components);
|
||||||
|
app.initializers.add('humanTime', humanTime);
|
||||||
|
|
||||||
|
app.initializers.add('preload', preload, -100);
|
||||||
|
app.initializers.add('boot', boot, -100);
|
||||||
|
app.initializers.add('alertEmailConfirmation', alertEmailConfirmation, -100);
|
||||||
|
|
||||||
|
export default app;
|
@@ -1,10 +1,10 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `AvatarEditor` component displays a user's avatar along with a dropdown
|
* The `AvatarEditor` component displays a user's avatar along with a dropdown
|
||||||
@@ -23,13 +23,6 @@ export default class AvatarEditor extends Component {
|
|||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not an image has been dragged over the dropzone.
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
this.isDraggedOver = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static initProps(props) {
|
static initProps(props) {
|
||||||
@@ -42,18 +35,13 @@ export default class AvatarEditor extends Component {
|
|||||||
const user = this.props.user;
|
const user = this.props.user;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '') + (this.isDraggedOver ? ' dragover' : '')}>
|
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '')}>
|
||||||
{avatar(user)}
|
{avatar(user)}
|
||||||
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
|
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
|
||||||
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||||
data-toggle="dropdown"
|
data-toggle="dropdown"
|
||||||
onclick={this.quickUpload.bind(this)}
|
onclick={this.quickUpload.bind(this)}>
|
||||||
ondragover={this.enableDragover.bind(this)}
|
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
|
||||||
ondragenter={this.enableDragover.bind(this)}
|
|
||||||
ondragleave={this.disableDragover.bind(this)}
|
|
||||||
ondragend={this.disableDragover.bind(this)}
|
|
||||||
ondrop={this.dropUpload.bind(this)}>
|
|
||||||
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('fas fa-pencil-alt') : icon('fas fa-plus-circle'))}
|
|
||||||
</a>
|
</a>
|
||||||
<ul className="Dropdown-menu Menu">
|
<ul className="Dropdown-menu Menu">
|
||||||
{listItems(this.controlItems().toArray())}
|
{listItems(this.controlItems().toArray())}
|
||||||
@@ -72,15 +60,15 @@ export default class AvatarEditor extends Component {
|
|||||||
|
|
||||||
items.add('upload',
|
items.add('upload',
|
||||||
Button.component({
|
Button.component({
|
||||||
icon: 'fas fa-upload',
|
icon: 'upload',
|
||||||
children: app.translator.trans('core.forum.user.avatar_upload_button'),
|
children: app.translator.trans('core.forum.user.avatar_upload_button'),
|
||||||
onclick: this.openPicker.bind(this)
|
onclick: this.upload.bind(this)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
items.add('remove',
|
items.add('remove',
|
||||||
Button.component({
|
Button.component({
|
||||||
icon: 'fas fa-times',
|
icon: 'times',
|
||||||
children: app.translator.trans('core.forum.user.avatar_remove_button'),
|
children: app.translator.trans('core.forum.user.avatar_remove_button'),
|
||||||
onclick: this.remove.bind(this)
|
onclick: this.remove.bind(this)
|
||||||
})
|
})
|
||||||
@@ -89,40 +77,6 @@ export default class AvatarEditor extends Component {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable dragover style
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
*/
|
|
||||||
enableDragover(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.isDraggedOver = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable dragover style
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
*/
|
|
||||||
disableDragover(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.isDraggedOver = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload avatar when file is dropped into dropzone.
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
*/
|
|
||||||
dropUpload(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.isDraggedOver = false;
|
|
||||||
this.upload(e.dataTransfer.files[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the user doesn't have an avatar, there's no point in showing the
|
* If the user doesn't have an avatar, there's no point in showing the
|
||||||
* controls dropdown, because only one option would be viable: uploading.
|
* controls dropdown, because only one option would be viable: uploading.
|
||||||
@@ -135,14 +89,14 @@ export default class AvatarEditor extends Component {
|
|||||||
if (!this.props.user.avatarUrl()) {
|
if (!this.props.user.avatarUrl()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.openPicker();
|
this.upload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload avatar using file picker
|
* Prompt the user to upload a new avatar.
|
||||||
*/
|
*/
|
||||||
openPicker() {
|
upload() {
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
|
|
||||||
// Create a hidden HTML input element and click on it so the user can select
|
// Create a hidden HTML input element and click on it so the user can select
|
||||||
@@ -151,21 +105,8 @@ export default class AvatarEditor extends Component {
|
|||||||
const $input = $('<input type="file">');
|
const $input = $('<input type="file">');
|
||||||
|
|
||||||
$input.appendTo('body').hide().click().on('change', e => {
|
$input.appendTo('body').hide().click().on('change', e => {
|
||||||
this.upload($(e.target)[0].files[0]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload avatar
|
|
||||||
*
|
|
||||||
* @param {File} file
|
|
||||||
*/
|
|
||||||
upload(file) {
|
|
||||||
if (this.loading) return;
|
|
||||||
|
|
||||||
const user = this.props.user;
|
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('avatar', file);
|
data.append('avatar', $(e.target)[0].files[0]);
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
@@ -179,6 +120,7 @@ export default class AvatarEditor extends Component {
|
|||||||
this.success.bind(this),
|
this.success.bind(this),
|
||||||
this.failure.bind(this)
|
this.failure.bind(this)
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@@ -1,5 +1,5 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ChangeEmailModal` component shows a modal dialog which allows the user
|
* The `ChangeEmailModal` component shows a modal dialog which allows the user
|
@@ -1,5 +1,5 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ChangePasswordModal` component shows a modal dialog which allows the
|
* The `ChangePasswordModal` component shows a modal dialog which allows the
|
@@ -1,14 +1,14 @@
|
|||||||
/*global s9e, hljs*/
|
/*global s9e, hljs*/
|
||||||
|
|
||||||
import Post from './Post';
|
import Post from 'flarum/components/Post';
|
||||||
import classList from '../../common/utils/classList';
|
import classList from 'flarum/utils/classList';
|
||||||
import PostUser from './PostUser';
|
import PostUser from 'flarum/components/PostUser';
|
||||||
import PostMeta from './PostMeta';
|
import PostMeta from 'flarum/components/PostMeta';
|
||||||
import PostEdited from './PostEdited';
|
import PostEdited from 'flarum/components/PostEdited';
|
||||||
import EditPostComposer from './EditPostComposer';
|
import EditPostComposer from 'flarum/components/EditPostComposer';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `CommentPost` component displays a standard `comment`-typed post. This
|
* The `CommentPost` component displays a standard `comment`-typed post. This
|
||||||
@@ -80,7 +80,7 @@ export default class CommentPost extends Post {
|
|||||||
const post = this.props.post;
|
const post = this.props.post;
|
||||||
const attrs = super.attrs();
|
const attrs = super.attrs();
|
||||||
|
|
||||||
attrs.className = (attrs.className || '') + ' ' + classList({
|
attrs.className += ' '+classList({
|
||||||
'CommentPost': true,
|
'CommentPost': true,
|
||||||
'Post--hidden': post.isHidden(),
|
'Post--hidden': post.isHidden(),
|
||||||
'Post--edited': post.isEdited(),
|
'Post--edited': post.isEdited(),
|
||||||
@@ -142,7 +142,7 @@ export default class CommentPost extends Post {
|
|||||||
items.add('toggle', (
|
items.add('toggle', (
|
||||||
Button.component({
|
Button.component({
|
||||||
className: 'Button Button--default Button--more',
|
className: 'Button Button--default Button--more',
|
||||||
icon: 'fas fa-ellipsis-h',
|
icon: 'ellipsis-h',
|
||||||
onclick: this.toggleContent.bind(this)
|
onclick: this.toggleContent.bind(this)
|
||||||
})
|
})
|
||||||
));
|
));
|
@@ -1,8 +1,9 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import ComposerButton from './ComposerButton';
|
import ComposerButton from 'flarum/components/ComposerButton';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import classList from '../../common/utils/classList';
|
import classList from 'flarum/utils/classList';
|
||||||
|
import computed from 'flarum/utils/computed';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Composer` component displays the composer. It can be loaded with a
|
* The `Composer` component displays the composer. It can be loaded with a
|
||||||
@@ -32,6 +33,28 @@ class Composer extends Component {
|
|||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
this.active = false;
|
this.active = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed the composer's current height, based on the intended height, and
|
||||||
|
* the composer's current state. This will be applied to the composer's
|
||||||
|
* content's DOM element.
|
||||||
|
*
|
||||||
|
* @return {Integer}
|
||||||
|
*/
|
||||||
|
this.computedHeight = computed('height', 'position', (height, position) => {
|
||||||
|
// If the composer is minimized, then we don't want to set a height; we'll
|
||||||
|
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
||||||
|
// make it as high as the window.
|
||||||
|
if (position === Composer.PositionEnum.MINIMIZED) {
|
||||||
|
return '';
|
||||||
|
} else if (position === Composer.PositionEnum.FULLSCREEN) {
|
||||||
|
return $(window).height();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if it's normal or hidden, then we use the intended height.
|
||||||
|
// We don't let the composer get too small or too big, though.
|
||||||
|
return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
@@ -62,9 +85,11 @@ class Composer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config(isInitialized, context) {
|
config(isInitialized, context) {
|
||||||
// Set the height of the Composer element and its contents on each redraw,
|
let defaultHeight;
|
||||||
// so that they do not lose it if their DOM elements are recreated.
|
|
||||||
this.updateHeight();
|
if (!isInitialized) {
|
||||||
|
defaultHeight = this.$().height();
|
||||||
|
}
|
||||||
|
|
||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
|
|
||||||
@@ -72,8 +97,11 @@ class Composer extends Component {
|
|||||||
// routes, we will flag the DOM to be retained across route changes.
|
// routes, we will flag the DOM to be retained across route changes.
|
||||||
context.retain = true;
|
context.retain = true;
|
||||||
|
|
||||||
this.initializeHeight();
|
// Initialize the composer's intended height based on what the user has set
|
||||||
this.$().hide().css('bottom', -this.computedHeight());
|
// it at previously, or otherwise the composer's default height. After that,
|
||||||
|
// we'll hide the composer.
|
||||||
|
this.height = localStorage.getItem('composerHeight') || defaultHeight;
|
||||||
|
this.$().hide().css('bottom', -this.height);
|
||||||
|
|
||||||
// Whenever any of the inputs inside the composer are have focus, we want to
|
// Whenever any of the inputs inside the composer are have focus, we want to
|
||||||
// add a class to the composer to draw attention to it.
|
// add a class to the composer to draw attention to it.
|
||||||
@@ -144,7 +172,8 @@ class Composer extends Component {
|
|||||||
// height so that it fills the height of the composer, and update the
|
// height so that it fills the height of the composer, and update the
|
||||||
// body's padding.
|
// body's padding.
|
||||||
const deltaPixels = this.mouseStart - e.clientY;
|
const deltaPixels = this.mouseStart - e.clientY;
|
||||||
this.changeHeight(this.heightStart + deltaPixels);
|
this.height = this.heightStart + deltaPixels;
|
||||||
|
this.updateHeight();
|
||||||
|
|
||||||
// Update the body's padding-bottom so that no content on the page will ever
|
// Update the body's padding-bottom so that no content on the page will ever
|
||||||
// get permanently hidden behind the composer. If the user is already
|
// get permanently hidden behind the composer. If the user is already
|
||||||
@@ -153,6 +182,8 @@ class Composer extends Component {
|
|||||||
const scrollTop = $(window).scrollTop();
|
const scrollTop = $(window).scrollTop();
|
||||||
const anchorToBottom = scrollTop > 0 && scrollTop + $(window).height() >= $(document).height();
|
const anchorToBottom = scrollTop > 0 && scrollTop + $(window).height() >= $(document).height();
|
||||||
this.updateBodyPadding(anchorToBottom);
|
this.updateBodyPadding(anchorToBottom);
|
||||||
|
|
||||||
|
localStorage.setItem('composerHeight', this.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -422,28 +453,28 @@ class Composer extends Component {
|
|||||||
|
|
||||||
if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
||||||
items.add('exitFullScreen', ComposerButton.component({
|
items.add('exitFullScreen', ComposerButton.component({
|
||||||
icon: 'fas fa-compress',
|
icon: 'compress',
|
||||||
title: app.translator.trans('core.forum.composer.exit_full_screen_tooltip'),
|
title: app.translator.trans('core.forum.composer.exit_full_screen_tooltip'),
|
||||||
onclick: this.exitFullScreen.bind(this)
|
onclick: this.exitFullScreen.bind(this)
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
if (this.position !== Composer.PositionEnum.MINIMIZED) {
|
if (this.position !== Composer.PositionEnum.MINIMIZED) {
|
||||||
items.add('minimize', ComposerButton.component({
|
items.add('minimize', ComposerButton.component({
|
||||||
icon: 'fas fa-minus minimize',
|
icon: 'minus minimize',
|
||||||
title: app.translator.trans('core.forum.composer.minimize_tooltip'),
|
title: app.translator.trans('core.forum.composer.minimize_tooltip'),
|
||||||
onclick: this.minimize.bind(this),
|
onclick: this.minimize.bind(this),
|
||||||
itemClassName: 'App-backControl'
|
itemClassName: 'App-backControl'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add('fullScreen', ComposerButton.component({
|
items.add('fullScreen', ComposerButton.component({
|
||||||
icon: 'fas fa-expand',
|
icon: 'expand',
|
||||||
title: app.translator.trans('core.forum.composer.full_screen_tooltip'),
|
title: app.translator.trans('core.forum.composer.full_screen_tooltip'),
|
||||||
onclick: this.fullScreen.bind(this)
|
onclick: this.fullScreen.bind(this)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add('close', ComposerButton.component({
|
items.add('close', ComposerButton.component({
|
||||||
icon: 'fas fa-times',
|
icon: 'times',
|
||||||
title: app.translator.trans('core.forum.composer.close_tooltip'),
|
title: app.translator.trans('core.forum.composer.close_tooltip'),
|
||||||
onclick: this.close.bind(this)
|
onclick: this.close.bind(this)
|
||||||
}));
|
}));
|
||||||
@@ -451,73 +482,6 @@ class Composer extends Component {
|
|||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize default Composer height.
|
|
||||||
*/
|
|
||||||
initializeHeight() {
|
|
||||||
this.height = localStorage.getItem('composerHeight');
|
|
||||||
|
|
||||||
if (!this.height) {
|
|
||||||
this.height = this.defaultHeight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default height of the Composer in case none is saved.
|
|
||||||
* @returns {Integer}
|
|
||||||
*/
|
|
||||||
defaultHeight() {
|
|
||||||
return this.$().height();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum height of the Composer.
|
|
||||||
* @returns {Integer}
|
|
||||||
*/
|
|
||||||
minimumHeight() {
|
|
||||||
return 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maxmimum height of the Composer.
|
|
||||||
* @returns {Integer}
|
|
||||||
*/
|
|
||||||
maximumHeight() {
|
|
||||||
return $(window).height() - $('#header').outerHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed the composer's current height, based on the intended height, and
|
|
||||||
* the composer's current state. This will be applied to the composer's
|
|
||||||
* content's DOM element.
|
|
||||||
* @returns {Integer|String}
|
|
||||||
*/
|
|
||||||
computedHeight() {
|
|
||||||
// If the composer is minimized, then we don't want to set a height; we'll
|
|
||||||
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
|
||||||
// make it as high as the window.
|
|
||||||
if (this.position === Composer.PositionEnum.MINIMIZED) {
|
|
||||||
return '';
|
|
||||||
} else if (this.position === Composer.PositionEnum.FULLSCREEN) {
|
|
||||||
return $(window).height();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, if it's normal or hidden, then we use the intended height.
|
|
||||||
// We don't let the composer get too small or too big, though.
|
|
||||||
return Math.max(this.minimumHeight(), Math.min(this.height, this.maximumHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save a new Composer height and update the DOM.
|
|
||||||
* @param {Integer} height
|
|
||||||
*/
|
|
||||||
changeHeight(height) {
|
|
||||||
this.height = height;
|
|
||||||
this.updateHeight();
|
|
||||||
|
|
||||||
localStorage.setItem('composerHeight', this.height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Composer.PositionEnum = {
|
Composer.PositionEnum = {
|
@@ -1,9 +1,9 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||||
import TextEditor from './TextEditor';
|
import TextEditor from 'flarum/components/TextEditor';
|
||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ComposerBody` component handles the body, or the content, of the
|
* The `ComposerBody` component handles the body, or the content, of the
|
@@ -1,4 +1,4 @@
|
|||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ComposerButton` component displays a button suitable for the composer
|
* The `ComposerButton` component displays a button suitable for the composer
|
@@ -1,5 +1,5 @@
|
|||||||
import ComposerBody from './ComposerBody';
|
import ComposerBody from 'flarum/components/ComposerBody';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionComposer` component displays the composer content for starting
|
* The `DiscussionComposer` component displays the composer content for starting
|
@@ -1,6 +1,6 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionHero` component displays the hero on a discussion page.
|
* The `DiscussionHero` component displays the hero on a discussion page.
|
@@ -1,8 +1,8 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import DiscussionListItem from './DiscussionListItem';
|
import DiscussionListItem from 'flarum/components/DiscussionListItem';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||||
import Placeholder from '../../common/components/Placeholder';
|
import Placeholder from 'flarum/components/Placeholder';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionList` component displays a list of discussions.
|
* The `DiscussionList` component displays a list of discussions.
|
||||||
@@ -62,7 +62,7 @@ export default class DiscussionList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'DiscussionList'+(this.props.params.q ? ' DiscussionList--searchResults' : '')}>
|
<div className="DiscussionList">
|
||||||
<ul className="DiscussionList-discussions">
|
<ul className="DiscussionList-discussions">
|
||||||
{this.discussions.map(discussion => {
|
{this.discussions.map(discussion => {
|
||||||
return (
|
return (
|
||||||
@@ -87,14 +87,14 @@ export default class DiscussionList extends Component {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
requestParams() {
|
requestParams() {
|
||||||
const params = {include: ['user', 'lastPostedUser'], filter: {}};
|
const params = {include: ['startUser', 'lastUser'], filter: {}};
|
||||||
|
|
||||||
params.sort = this.sortMap()[this.props.params.sort];
|
params.sort = this.sortMap()[this.props.params.sort];
|
||||||
|
|
||||||
if (this.props.params.q) {
|
if (this.props.params.q) {
|
||||||
params.filter.q = this.props.params.q;
|
params.filter.q = this.props.params.q;
|
||||||
|
|
||||||
params.include.push('mostRelevantPost', 'mostRelevantPost.user');
|
params.include.push('relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
@@ -112,10 +112,10 @@ export default class DiscussionList extends Component {
|
|||||||
if (this.props.params.q) {
|
if (this.props.params.q) {
|
||||||
map.relevance = '';
|
map.relevance = '';
|
||||||
}
|
}
|
||||||
map.latest = '-lastPostedAt';
|
map.latest = '-lastTime';
|
||||||
map.top = '-commentCount';
|
map.top = '-commentsCount';
|
||||||
map.newest = '-createdAt';
|
map.newest = '-startTime';
|
||||||
map.oldest = 'createdAt';
|
map.oldest = 'startTime';
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ export default class DiscussionList extends Component {
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
loadResults(offset) {
|
loadResults(offset) {
|
||||||
const preloadedDiscussions = app.preloadedApiDocument();
|
const preloadedDiscussions = app.preloadedDocument();
|
||||||
|
|
||||||
if (preloadedDiscussions) {
|
if (preloadedDiscussions) {
|
||||||
return m.deferred().resolve(preloadedDiscussions).promise;
|
return m.deferred().resolve(preloadedDiscussions).promise;
|
@@ -1,19 +1,19 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import highlight from '../../common/helpers/highlight';
|
import highlight from 'flarum/helpers/highlight';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import humanTime from '../../common/utils/humanTime';
|
import humanTime from 'flarum/utils/humanTime';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import abbreviateNumber from '../../common/utils/abbreviateNumber';
|
import abbreviateNumber from 'flarum/utils/abbreviateNumber';
|
||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import TerminalPost from './TerminalPost';
|
import TerminalPost from 'flarum/components/TerminalPost';
|
||||||
import PostPreview from './PostPreview';
|
import PostPreview from 'flarum/components/PostPreview';
|
||||||
import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
|
import SubtreeRetainer from 'flarum/utils/SubtreeRetainer';
|
||||||
import DiscussionControls from '../utils/DiscussionControls';
|
import DiscussionControls from 'flarum/utils/DiscussionControls';
|
||||||
import slidable from '../utils/slidable';
|
import slidable from 'flarum/utils/slidable';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
import classList from '../../common/utils/classList';
|
import classList from 'flarum/utils/classList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionListItem` component shows a single discussion in the
|
* The `DiscussionListItem` component shows a single discussion in the
|
||||||
@@ -35,7 +35,7 @@ export default class DiscussionListItem extends Component {
|
|||||||
this.subtree = new SubtreeRetainer(
|
this.subtree = new SubtreeRetainer(
|
||||||
() => this.props.discussion.freshness,
|
() => this.props.discussion.freshness,
|
||||||
() => {
|
() => {
|
||||||
const time = app.session.user && app.session.user.markedAllAsReadAt();
|
const time = app.session.user && app.session.user.readTime();
|
||||||
return time && time.getTime();
|
return time && time.getTime();
|
||||||
},
|
},
|
||||||
() => this.active()
|
() => this.active()
|
||||||
@@ -58,30 +58,20 @@ export default class DiscussionListItem extends Component {
|
|||||||
if (retain) return retain;
|
if (retain) return retain;
|
||||||
|
|
||||||
const discussion = this.props.discussion;
|
const discussion = this.props.discussion;
|
||||||
const user = discussion.user();
|
const startUser = discussion.startUser();
|
||||||
const isUnread = discussion.isUnread();
|
const isUnread = discussion.isUnread();
|
||||||
const isRead = discussion.isRead();
|
const isRead = discussion.isRead();
|
||||||
const showUnread = !this.showRepliesCount() && isUnread;
|
const showUnread = !this.showRepliesCount() && isUnread;
|
||||||
let jumpTo = 0;
|
const jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
|
||||||
|
const relevantPosts = this.props.params.q ? discussion.relevantPosts() : [];
|
||||||
const controls = DiscussionControls.controls(discussion, this).toArray();
|
const controls = DiscussionControls.controls(discussion, this).toArray();
|
||||||
const attrs = this.attrs();
|
const attrs = this.attrs();
|
||||||
|
|
||||||
if (this.props.params.q) {
|
|
||||||
const post = discussion.mostRelevantPost();
|
|
||||||
if (post) {
|
|
||||||
jumpTo = post.number();
|
|
||||||
}
|
|
||||||
|
|
||||||
const phrase = this.props.params.q;
|
|
||||||
this.highlightRegExp = new RegExp(phrase+'|'+phrase.trim().replace(/\s+/g, '|'), 'gi');
|
|
||||||
} else {
|
|
||||||
jumpTo = Math.min(discussion.lastPostNumber(), (discussion.lastReadPostNumber() || 0) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...attrs}>
|
<div {...attrs}>
|
||||||
|
|
||||||
{controls.length ? Dropdown.component({
|
{controls.length ? Dropdown.component({
|
||||||
icon: 'fas fa-ellipsis-v',
|
icon: 'ellipsis-v',
|
||||||
children: controls,
|
children: controls,
|
||||||
className: 'DiscussionListItem-controls',
|
className: 'DiscussionListItem-controls',
|
||||||
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right'
|
buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right'
|
||||||
@@ -89,18 +79,18 @@ export default class DiscussionListItem extends Component {
|
|||||||
|
|
||||||
<a className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
|
<a className={'Slidable-underneath Slidable-underneath--left Slidable-underneath--elastic' + (isUnread ? '' : ' disabled')}
|
||||||
onclick={this.markAsRead.bind(this)}>
|
onclick={this.markAsRead.bind(this)}>
|
||||||
{icon('fas fa-check')}
|
{icon('check')}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div className={'DiscussionListItem-content Slidable-content' + (isUnread ? ' unread' : '') + (isRead ? ' read' : '')}>
|
<div className={'DiscussionListItem-content Slidable-content' + (isUnread ? ' unread' : '') + (isRead ? ' read' : '')}>
|
||||||
<a href={user ? app.route.user(user) : '#'}
|
<a href={startUser ? app.route.user(startUser) : '#'}
|
||||||
className="DiscussionListItem-author"
|
className="DiscussionListItem-author"
|
||||||
title={extractText(app.translator.trans('core.forum.discussion_list.started_text', {user: user, ago: humanTime(discussion.createdAt())}))}
|
title={extractText(app.translator.trans('core.forum.discussion_list.started_text', {user: startUser, ago: humanTime(discussion.startTime())}))}
|
||||||
config={function(element) {
|
config={function(element) {
|
||||||
$(element).tooltip({placement: 'right'});
|
$(element).tooltip({placement: 'right'});
|
||||||
m.route.apply(this, arguments);
|
m.route.apply(this, arguments);
|
||||||
}}>
|
}}>
|
||||||
{avatar(user, {title: ''})}
|
{avatar(startUser, {title: ''})}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul className="DiscussionListItem-badges badges">
|
<ul className="DiscussionListItem-badges badges">
|
||||||
@@ -110,15 +100,22 @@ export default class DiscussionListItem extends Component {
|
|||||||
<a href={app.route.discussion(discussion, jumpTo)}
|
<a href={app.route.discussion(discussion, jumpTo)}
|
||||||
config={m.route}
|
config={m.route}
|
||||||
className="DiscussionListItem-main">
|
className="DiscussionListItem-main">
|
||||||
<h3 className="DiscussionListItem-title">{highlight(discussion.title(), this.highlightRegExp)}</h3>
|
<h3 className="DiscussionListItem-title">{highlight(discussion.title(), this.props.params.q)}</h3>
|
||||||
<ul className="DiscussionListItem-info">{listItems(this.infoItems().toArray())}</ul>
|
<ul className="DiscussionListItem-info">{listItems(this.infoItems().toArray())}</ul>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span className="DiscussionListItem-count"
|
<span className="DiscussionListItem-count"
|
||||||
onclick={this.markAsRead.bind(this)}
|
onclick={this.markAsRead.bind(this)}
|
||||||
title={showUnread ? app.translator.trans('core.forum.discussion_list.mark_as_read_tooltip') : ''}>
|
title={showUnread ? app.translator.trans('core.forum.discussion_list.mark_as_read_tooltip') : ''}>
|
||||||
{abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'replyCount']())}
|
{abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'repliesCount']())}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
{relevantPosts && relevantPosts.length
|
||||||
|
? <div className="DiscussionListItem-relevantPosts">
|
||||||
|
{relevantPosts.map(post => PostPreview.component({post, highlight: this.props.params.q}))}
|
||||||
|
</div>
|
||||||
|
: ''}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -156,7 +153,7 @@ export default class DiscussionListItem extends Component {
|
|||||||
*
|
*
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
showFirstPost() {
|
showStartPost() {
|
||||||
return ['newest', 'oldest'].indexOf(this.props.params.sort) !== -1;
|
return ['newest', 'oldest'].indexOf(this.props.params.sort) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +174,7 @@ export default class DiscussionListItem extends Component {
|
|||||||
const discussion = this.props.discussion;
|
const discussion = this.props.discussion;
|
||||||
|
|
||||||
if (discussion.isUnread()) {
|
if (discussion.isUnread()) {
|
||||||
discussion.save({lastReadPostNumber: discussion.lastPostNumber()});
|
discussion.save({readNumber: discussion.lastPostNumber()});
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,21 +188,12 @@ export default class DiscussionListItem extends Component {
|
|||||||
infoItems() {
|
infoItems() {
|
||||||
const items = new ItemList();
|
const items = new ItemList();
|
||||||
|
|
||||||
if (this.props.params.q) {
|
|
||||||
const post = this.props.discussion.mostRelevantPost() || this.props.discussion.firstPost();
|
|
||||||
|
|
||||||
if (post && post.contentType() === 'comment') {
|
|
||||||
const excerpt = highlight(post.contentPlain(), this.highlightRegExp, 175);
|
|
||||||
items.add('excerpt', excerpt, -100);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items.add('terminalPost',
|
items.add('terminalPost',
|
||||||
TerminalPost.component({
|
TerminalPost.component({
|
||||||
discussion: this.props.discussion,
|
discussion: this.props.discussion,
|
||||||
lastPost: !this.showFirstPost()
|
lastPost: !this.showStartPost()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
@@ -1,12 +1,12 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import DiscussionHero from './DiscussionHero';
|
import DiscussionHero from 'flarum/components/DiscussionHero';
|
||||||
import PostStream from './PostStream';
|
import PostStream from 'flarum/components/PostStream';
|
||||||
import PostStreamScrubber from './PostStreamScrubber';
|
import PostStreamScrubber from 'flarum/components/PostStreamScrubber';
|
||||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||||
import SplitDropdown from '../../common/components/SplitDropdown';
|
import SplitDropdown from 'flarum/components/SplitDropdown';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import DiscussionControls from '../utils/DiscussionControls';
|
import DiscussionControls from 'flarum/utils/DiscussionControls';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionPage` component displays a whole discussion page, including
|
* The `DiscussionPage` component displays a whole discussion page, including
|
||||||
@@ -115,14 +115,6 @@ export default class DiscussionPage extends Page {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
config(...args) {
|
|
||||||
super.config(...args);
|
|
||||||
|
|
||||||
if (this.discussion) {
|
|
||||||
app.setTitle(this.discussion.title());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear and reload the discussion.
|
* Clear and reload the discussion.
|
||||||
*/
|
*/
|
||||||
@@ -130,7 +122,7 @@ export default class DiscussionPage extends Page {
|
|||||||
this.near = m.route.param('near') || 0;
|
this.near = m.route.param('near') || 0;
|
||||||
this.discussion = null;
|
this.discussion = null;
|
||||||
|
|
||||||
const preloadedDiscussion = app.preloadedApiDocument();
|
const preloadedDiscussion = app.preloadedDocument();
|
||||||
if (preloadedDiscussion) {
|
if (preloadedDiscussion) {
|
||||||
// We must wrap this in a setTimeout because if we are mounting this
|
// We must wrap this in a setTimeout because if we are mounting this
|
||||||
// component for the first time on page load, then any calls to m.redraw
|
// component for the first time on page load, then any calls to m.redraw
|
||||||
@@ -168,6 +160,7 @@ export default class DiscussionPage extends Page {
|
|||||||
this.discussion = discussion;
|
this.discussion = discussion;
|
||||||
|
|
||||||
app.history.push('discussion', discussion.title());
|
app.history.push('discussion', discussion.title());
|
||||||
|
app.setTitle(discussion.title());
|
||||||
app.setTitleCount(0);
|
app.setTitleCount(0);
|
||||||
|
|
||||||
// When the API responds with a discussion, it will also include a number of
|
// When the API responds with a discussion, it will also include a number of
|
||||||
@@ -179,13 +172,8 @@ export default class DiscussionPage extends Page {
|
|||||||
// the 'discussion' relationship linked, then sorting and splicing.
|
// the 'discussion' relationship linked, then sorting and splicing.
|
||||||
let includedPosts = [];
|
let includedPosts = [];
|
||||||
if (discussion.payload && discussion.payload.included) {
|
if (discussion.payload && discussion.payload.included) {
|
||||||
const discussionId = discussion.id();
|
|
||||||
|
|
||||||
includedPosts = discussion.payload.included
|
includedPosts = discussion.payload.included
|
||||||
.filter(record => record.type === 'posts'
|
.filter(record => record.type === 'posts' && record.relationships && record.relationships.discussion)
|
||||||
&& record.relationships
|
|
||||||
&& record.relationships.discussion
|
|
||||||
&& record.relationships.discussion.data.id === discussionId)
|
|
||||||
.map(record => app.store.getById('posts', record.id))
|
.map(record => app.store.getById('posts', record.id))
|
||||||
.sort((a, b) => a.id() - b.id())
|
.sort((a, b) => a.id() - b.id())
|
||||||
.slice(0, 20);
|
.slice(0, 20);
|
||||||
@@ -252,7 +240,7 @@ export default class DiscussionPage extends Page {
|
|||||||
items.add('controls',
|
items.add('controls',
|
||||||
SplitDropdown.component({
|
SplitDropdown.component({
|
||||||
children: DiscussionControls.controls(this.discussion, this).toArray(),
|
children: DiscussionControls.controls(this.discussion, this).toArray(),
|
||||||
icon: 'fas fa-ellipsis-v',
|
icon: 'ellipsis-v',
|
||||||
className: 'App-primaryControl',
|
className: 'App-primaryControl',
|
||||||
buttonClassName: 'Button--primary'
|
buttonClassName: 'Button--primary'
|
||||||
})
|
})
|
||||||
@@ -290,8 +278,8 @@ export default class DiscussionPage extends Page {
|
|||||||
|
|
||||||
// If the user hasn't read past here before, then we'll update their read
|
// If the user hasn't read past here before, then we'll update their read
|
||||||
// state and redraw.
|
// state and redraw.
|
||||||
if (app.session.user && endNumber > (discussion.lastReadPostNumber() || 0)) {
|
if (app.session.user && endNumber > (discussion.readNumber() || 0)) {
|
||||||
discussion.save({lastReadPostNumber: endNumber});
|
discussion.save({readNumber: endNumber});
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import Notification from './Notification';
|
import Notification from 'flarum/components/Notification';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionRenamedNotification` component displays a notification which
|
* The `DiscussionRenamedNotification` component displays a notification which
|
||||||
@@ -10,7 +10,7 @@ import Notification from './Notification';
|
|||||||
*/
|
*/
|
||||||
export default class DiscussionRenamedNotification extends Notification {
|
export default class DiscussionRenamedNotification extends Notification {
|
||||||
icon() {
|
icon() {
|
||||||
return 'fas fa-pencil-alt';
|
return 'pencil';
|
||||||
}
|
}
|
||||||
|
|
||||||
href() {
|
href() {
|
||||||
@@ -20,6 +20,6 @@ export default class DiscussionRenamedNotification extends Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content() {
|
content() {
|
||||||
return app.translator.trans('core.forum.notifications.discussion_renamed_text', {user: this.props.notification.fromUser()});
|
return app.translator.trans('core.forum.notifications.discussion_renamed_text', {user: this.props.notification.sender()});
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import EventPost from './EventPost';
|
import EventPost from 'flarum/components/EventPost';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionRenamedPost` component displays a discussion event post
|
* The `DiscussionRenamedPost` component displays a discussion event post
|
||||||
@@ -11,7 +11,7 @@ import extractText from '../../common/utils/extractText';
|
|||||||
*/
|
*/
|
||||||
export default class DiscussionRenamedPost extends EventPost {
|
export default class DiscussionRenamedPost extends EventPost {
|
||||||
icon() {
|
icon() {
|
||||||
return 'fas fa-pencil-alt';
|
return 'pencil';
|
||||||
}
|
}
|
||||||
|
|
||||||
description(data) {
|
description(data) {
|
@@ -1,5 +1,5 @@
|
|||||||
import highlight from '../../common/helpers/highlight';
|
import highlight from 'flarum/helpers/highlight';
|
||||||
import LinkButton from '../../common/components/LinkButton';
|
import LinkButton from 'flarum/components/LinkButton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionsSearchSource` finds and displays discussion search results in
|
* The `DiscussionsSearchSource` finds and displays discussion search results in
|
||||||
@@ -20,7 +20,7 @@ export default class DiscussionsSearchSource {
|
|||||||
const params = {
|
const params = {
|
||||||
filter: {q: query},
|
filter: {q: query},
|
||||||
page: {limit: 3},
|
page: {limit: 3},
|
||||||
include: 'mostRelevantPost'
|
include: 'relevantPosts,relevantPosts.discussion,relevantPosts.user'
|
||||||
};
|
};
|
||||||
|
|
||||||
return app.store.find('discussions', params).then(results => this.results[query] = results);
|
return app.store.find('discussions', params).then(results => this.results[query] = results);
|
||||||
@@ -35,19 +35,20 @@ export default class DiscussionsSearchSource {
|
|||||||
<li className="Dropdown-header">{app.translator.trans('core.forum.search.discussions_heading')}</li>,
|
<li className="Dropdown-header">{app.translator.trans('core.forum.search.discussions_heading')}</li>,
|
||||||
<li>
|
<li>
|
||||||
{LinkButton.component({
|
{LinkButton.component({
|
||||||
icon: 'fas fa-search',
|
icon: 'search',
|
||||||
children: app.translator.trans('core.forum.search.all_discussions_button', {query}),
|
children: app.translator.trans('core.forum.search.all_discussions_button', {query}),
|
||||||
href: app.route('index', {q: query})
|
href: app.route('index', {q: query})
|
||||||
})}
|
})}
|
||||||
</li>,
|
</li>,
|
||||||
results.map(discussion => {
|
results.map(discussion => {
|
||||||
const mostRelevantPost = discussion.mostRelevantPost();
|
const relevantPosts = discussion.relevantPosts();
|
||||||
|
const post = relevantPosts && relevantPosts[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
||||||
<a href={app.route.discussion(discussion, mostRelevantPost && mostRelevantPost.number())} config={m.route}>
|
<a href={app.route.discussion(discussion, post && post.number())} config={m.route}>
|
||||||
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
||||||
{mostRelevantPost ? <div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain(), query, 100)}</div> : ''}
|
{post ? <div className="DiscussionSearchResult-excerpt">{highlight(post.contentPlain(), query, 100)}</div> : ''}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
@@ -1,5 +1,5 @@
|
|||||||
import UserPage from './UserPage';
|
import UserPage from 'flarum/components/UserPage';
|
||||||
import DiscussionList from './DiscussionList';
|
import DiscussionList from 'flarum/components/DiscussionList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionsUserPage` component shows a discussion list inside of a user
|
* The `DiscussionsUserPage` component shows a discussion list inside of a user
|
||||||
@@ -17,8 +17,7 @@ export default class DiscussionsUserPage extends UserPage {
|
|||||||
<div className="DiscussionsUserPage">
|
<div className="DiscussionsUserPage">
|
||||||
{DiscussionList.component({
|
{DiscussionList.component({
|
||||||
params: {
|
params: {
|
||||||
q: 'author:' + this.user.username(),
|
q: 'author:' + this.user.username()
|
||||||
sort: 'newest'
|
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
@@ -1,5 +1,5 @@
|
|||||||
import ComposerBody from './ComposerBody';
|
import ComposerBody from 'flarum/components/ComposerBody';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
|
|
||||||
function minimizeComposerIfFullScreen(e) {
|
function minimizeComposerIfFullScreen(e) {
|
||||||
if (app.composer.isFullScreen()) {
|
if (app.composer.isFullScreen()) {
|
||||||
@@ -52,7 +52,7 @@ export default class EditPostComposer extends ComposerBody {
|
|||||||
|
|
||||||
items.add('title', (
|
items.add('title', (
|
||||||
<h3>
|
<h3>
|
||||||
{icon('fas fa-pencil-alt')} {' '}
|
{icon('pencil')} {' '}
|
||||||
<a href={app.route.discussion(post.discussion(), post.number())} config={routeAndMinimize}>
|
<a href={app.route.discussion(post.discussion(), post.number())} config={routeAndMinimize}>
|
||||||
{app.translator.trans('core.forum.composer_edit.post_link', {number: post.number(), discussion: post.discussion().title()})}
|
{app.translator.trans('core.forum.composer_edit.post_link', {number: post.number(), discussion: post.discussion().title()})}
|
||||||
</a>
|
</a>
|
@@ -1,8 +1,8 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import GroupBadge from '../../common/components/GroupBadge';
|
import GroupBadge from 'flarum/components/GroupBadge';
|
||||||
import Group from '../../common/models/Group';
|
import Group from 'flarum/models/Group';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `EditUserModal` component displays a modal dialog with a login form.
|
* The `EditUserModal` component displays a modal dialog with a login form.
|
||||||
@@ -15,7 +15,7 @@ export default class EditUserModal extends Modal {
|
|||||||
|
|
||||||
this.username = m.prop(user.username() || '');
|
this.username = m.prop(user.username() || '');
|
||||||
this.email = m.prop(user.email() || '');
|
this.email = m.prop(user.email() || '');
|
||||||
this.isEmailConfirmed = m.prop(user.isEmailConfirmed() || false);
|
this.isActivated = m.prop(user.isActivated() || false);
|
||||||
this.setPassword = m.prop(false);
|
this.setPassword = m.prop(false);
|
||||||
this.password = m.prop(user.password() || '');
|
this.password = m.prop(user.password() || '');
|
||||||
this.groups = {};
|
this.groups = {};
|
||||||
@@ -50,7 +50,7 @@ export default class EditUserModal extends Modal {
|
|||||||
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.email_label'))}
|
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.email_label'))}
|
||||||
bidi={this.email} />
|
bidi={this.email} />
|
||||||
</div>
|
</div>
|
||||||
{!this.isEmailConfirmed() ? (
|
{!this.isActivated() ? (
|
||||||
<div>
|
<div>
|
||||||
{Button.component({
|
{Button.component({
|
||||||
className: 'Button Button--block',
|
className: 'Button Button--block',
|
||||||
@@ -115,11 +115,11 @@ export default class EditUserModal extends Modal {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
const data = {
|
const data = {
|
||||||
username: this.username(),
|
username: this.username(),
|
||||||
isEmailConfirmed: true,
|
isActivated: true,
|
||||||
};
|
};
|
||||||
this.props.user.save(data, {errorHandler: this.onerror.bind(this)})
|
this.props.user.save(data, {errorHandler: this.onerror.bind(this)})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isEmailConfirmed(true);
|
this.isActivated(true);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
})
|
})
|
@@ -1,7 +1,7 @@
|
|||||||
import Post from './Post';
|
import Post from 'flarum/components/Post';
|
||||||
import { ucfirst } from '../../common/utils/string';
|
import { ucfirst } from 'flarum/utils/string';
|
||||||
import usernameHelper from '../../common/helpers/username';
|
import usernameHelper from 'flarum/helpers/username';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `EventPost` component displays a post which indicating a discussion
|
* The `EventPost` component displays a post which indicating a discussion
|
||||||
@@ -18,7 +18,7 @@ export default class EventPost extends Post {
|
|||||||
attrs() {
|
attrs() {
|
||||||
const attrs = super.attrs();
|
const attrs = super.attrs();
|
||||||
|
|
||||||
attrs.className = (attrs.className || '') + ' EventPost ' + ucfirst(this.props.post.contentType()) + 'Post';
|
attrs.className += ' EventPost ' + ucfirst(this.props.post.contentType()) + 'Post';
|
||||||
|
|
||||||
return attrs;
|
return attrs;
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
import Alert from '../../common/components/Alert';
|
import Alert from 'flarum/components/Alert';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ForgotPasswordModal` component displays a modal which allows the user to
|
* The `ForgotPasswordModal` component displays a modal which allows the user to
|
@@ -1,6 +1,6 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `HeaderPrimary` component displays primary header controls. On the
|
* The `HeaderPrimary` component displays primary header controls. On the
|
@@ -1,12 +1,12 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import LogInModal from './LogInModal';
|
import LogInModal from 'flarum/components/LogInModal';
|
||||||
import SignUpModal from './SignUpModal';
|
import SignUpModal from 'flarum/components/SignUpModal';
|
||||||
import SessionDropdown from './SessionDropdown';
|
import SessionDropdown from 'flarum/components/SessionDropdown';
|
||||||
import SelectDropdown from '../../common/components/SelectDropdown';
|
import SelectDropdown from 'flarum/components/SelectDropdown';
|
||||||
import NotificationsDropdown from './NotificationsDropdown';
|
import NotificationsDropdown from 'flarum/components/NotificationsDropdown';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `HeaderSecondary` component displays secondary header controls, such as
|
* The `HeaderSecondary` component displays secondary header controls, such as
|
||||||
@@ -46,7 +46,7 @@ export default class HeaderSecondary extends Component {
|
|||||||
locales.push(Button.component({
|
locales.push(Button.component({
|
||||||
active: app.data.locale === locale,
|
active: app.data.locale === locale,
|
||||||
children: app.data.locales[locale],
|
children: app.data.locales[locale],
|
||||||
icon: app.data.locale === locale ? 'fas fa-check' : true,
|
icon: app.data.locale === locale ? 'check' : true,
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
if (app.session.user) {
|
if (app.session.user) {
|
||||||
app.session.user.savePreferences({locale}).then(() => window.location.reload());
|
app.session.user.savePreferences({locale}).then(() => window.location.reload());
|
@@ -1,17 +1,17 @@
|
|||||||
import { extend } from '../../common/extend';
|
import { extend } from 'flarum/extend';
|
||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import DiscussionList from './DiscussionList';
|
import DiscussionList from 'flarum/components/DiscussionList';
|
||||||
import WelcomeHero from './WelcomeHero';
|
import WelcomeHero from 'flarum/components/WelcomeHero';
|
||||||
import DiscussionComposer from './DiscussionComposer';
|
import DiscussionComposer from 'flarum/components/DiscussionComposer';
|
||||||
import LogInModal from './LogInModal';
|
import LogInModal from 'flarum/components/LogInModal';
|
||||||
import DiscussionPage from './DiscussionPage';
|
import DiscussionPage from 'flarum/components/DiscussionPage';
|
||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
import LinkButton from '../../common/components/LinkButton';
|
import LinkButton from 'flarum/components/LinkButton';
|
||||||
import SelectDropdown from '../../common/components/SelectDropdown';
|
import SelectDropdown from 'flarum/components/SelectDropdown';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `IndexPage` component displays the index page, including the welcome
|
* The `IndexPage` component displays the index page, including the welcome
|
||||||
@@ -71,7 +71,6 @@ export default class IndexPage extends Page {
|
|||||||
<div className="IndexPage">
|
<div className="IndexPage">
|
||||||
{this.hero()}
|
{this.hero()}
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="sideNavContainer">
|
|
||||||
<nav className="IndexPage-nav sideNav">
|
<nav className="IndexPage-nav sideNav">
|
||||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -84,7 +83,6 @@ export default class IndexPage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,10 +154,10 @@ export default class IndexPage extends Page {
|
|||||||
items.add('newDiscussion',
|
items.add('newDiscussion',
|
||||||
Button.component({
|
Button.component({
|
||||||
children: app.translator.trans(canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button'),
|
children: app.translator.trans(canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button'),
|
||||||
icon: 'fas fa-edit',
|
icon: 'edit',
|
||||||
className: 'Button Button--primary IndexPage-newDiscussion',
|
className: 'Button Button--primary IndexPage-newDiscussion',
|
||||||
itemClassName: 'App-primaryControl',
|
itemClassName: 'App-primaryControl',
|
||||||
onclick: this.newDiscussionAction.bind(this),
|
onclick: this.newDiscussion.bind(this),
|
||||||
disabled: !canStartDiscussion
|
disabled: !canStartDiscussion
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -189,7 +187,7 @@ export default class IndexPage extends Page {
|
|||||||
LinkButton.component({
|
LinkButton.component({
|
||||||
href: app.route('index', params),
|
href: app.route('index', params),
|
||||||
children: app.translator.trans('core.forum.index.all_discussions_link'),
|
children: app.translator.trans('core.forum.index.all_discussions_link'),
|
||||||
icon: 'far fa-comments'
|
icon: 'comments-o'
|
||||||
}),
|
}),
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
@@ -223,7 +221,7 @@ export default class IndexPage extends Page {
|
|||||||
|
|
||||||
return Button.component({
|
return Button.component({
|
||||||
children: label,
|
children: label,
|
||||||
icon: active ? 'fas fa-check' : true,
|
icon: active ? 'check' : true,
|
||||||
onclick: this.changeSort.bind(this, value),
|
onclick: this.changeSort.bind(this, value),
|
||||||
active: active,
|
active: active,
|
||||||
})
|
})
|
||||||
@@ -246,7 +244,7 @@ export default class IndexPage extends Page {
|
|||||||
items.add('refresh',
|
items.add('refresh',
|
||||||
Button.component({
|
Button.component({
|
||||||
title: app.translator.trans('core.forum.index.refresh_tooltip'),
|
title: app.translator.trans('core.forum.index.refresh_tooltip'),
|
||||||
icon: 'fas fa-sync',
|
icon: 'refresh',
|
||||||
className: 'Button Button--icon',
|
className: 'Button Button--icon',
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
app.cache.discussionList.refresh();
|
app.cache.discussionList.refresh();
|
||||||
@@ -262,7 +260,7 @@ export default class IndexPage extends Page {
|
|||||||
items.add('markAllAsRead',
|
items.add('markAllAsRead',
|
||||||
Button.component({
|
Button.component({
|
||||||
title: app.translator.trans('core.forum.index.mark_all_as_read_tooltip'),
|
title: app.translator.trans('core.forum.index.mark_all_as_read_tooltip'),
|
||||||
icon: 'fas fa-check',
|
icon: 'check',
|
||||||
className: 'Button Button--icon',
|
className: 'Button Button--icon',
|
||||||
onclick: this.markAllAsRead.bind(this)
|
onclick: this.markAllAsRead.bind(this)
|
||||||
})
|
})
|
||||||
@@ -339,25 +337,39 @@ export default class IndexPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the composer for a new discussion or prompt the user to login.
|
* Log the user in and then open the composer for a new discussion.
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
newDiscussionAction() {
|
newDiscussion() {
|
||||||
const deferred = m.deferred();
|
const deferred = m.deferred();
|
||||||
|
|
||||||
if (app.session.user) {
|
if (app.session.user) {
|
||||||
|
this.composeNewDiscussion(deferred);
|
||||||
|
} else {
|
||||||
|
app.modal.show(
|
||||||
|
new LogInModal({
|
||||||
|
onlogin: this.composeNewDiscussion.bind(this, deferred)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the composer for a new discussion.
|
||||||
|
*
|
||||||
|
* @param {Deferred} deferred
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
composeNewDiscussion(deferred) {
|
||||||
const component = new DiscussionComposer({user: app.session.user});
|
const component = new DiscussionComposer({user: app.session.user});
|
||||||
|
|
||||||
app.composer.load(component);
|
app.composer.load(component);
|
||||||
app.composer.show();
|
app.composer.show();
|
||||||
|
|
||||||
deferred.resolve(component);
|
deferred.resolve(component);
|
||||||
} else {
|
|
||||||
deferred.reject();
|
|
||||||
|
|
||||||
app.modal.show(new LogInModal());
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
@@ -371,7 +383,7 @@ export default class IndexPage extends Page {
|
|||||||
const confirmation = confirm(app.translator.trans('core.forum.index.mark_all_as_read_confirmation'));
|
const confirmation = confirm(app.translator.trans('core.forum.index.mark_all_as_read_confirmation'));
|
||||||
|
|
||||||
if (confirmation) {
|
if (confirmation) {
|
||||||
app.session.user.save({markedAllAsReadAt: new Date()});
|
app.session.user.save({readTime: new Date()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `LoadingPost` component shows a placeholder that looks like a post,
|
* The `LoadingPost` component shows a placeholder that looks like a post,
|
@@ -1,4 +1,4 @@
|
|||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `LogInButton` component displays a social login button which will open
|
* The `LogInButton` component displays a social login button which will open
|
@@ -1,5 +1,5 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `LogInButtons` component displays a collection of social login buttons.
|
* The `LogInButtons` component displays a collection of social login buttons.
|
@@ -1,10 +1,10 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from 'flarum/components/Modal';
|
||||||
import ForgotPasswordModal from './ForgotPasswordModal';
|
import ForgotPasswordModal from 'flarum/components/ForgotPasswordModal';
|
||||||
import SignUpModal from './SignUpModal';
|
import SignUpModal from 'flarum/components/SignUpModal';
|
||||||
import Button from '../../common/components/Button';
|
import Alert from 'flarum/components/Alert';
|
||||||
import LogInButtons from './LogInButtons';
|
import Button from 'flarum/components/Button';
|
||||||
import extractText from '../../common/utils/extractText';
|
import LogInButtons from 'flarum/components/LogInButtons';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `LogInModal` component displays a modal dialog with a login form.
|
* The `LogInModal` component displays a modal dialog with a login form.
|
||||||
@@ -51,71 +51,51 @@ export default class LogInModal extends Modal {
|
|||||||
content() {
|
content() {
|
||||||
return [
|
return [
|
||||||
<div className="Modal-body">
|
<div className="Modal-body">
|
||||||
{this.body()}
|
<LogInButtons/>
|
||||||
</div>,
|
|
||||||
<div className="Modal-footer">
|
|
||||||
{this.footer()}
|
|
||||||
</div>
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
body() {
|
|
||||||
return [
|
|
||||||
<LogInButtons/>,
|
|
||||||
|
|
||||||
<div className="Form Form--centered">
|
<div className="Form Form--centered">
|
||||||
{this.fields().toArray()}
|
<div className="Form-group">
|
||||||
</div>
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
fields() {
|
|
||||||
const items = new ItemList();
|
|
||||||
|
|
||||||
items.add('identification', <div className="Form-group">
|
|
||||||
<input className="FormControl" name="identification" type="text" placeholder={extractText(app.translator.trans('core.forum.log_in.username_or_email_placeholder'))}
|
<input className="FormControl" name="identification" type="text" placeholder={extractText(app.translator.trans('core.forum.log_in.username_or_email_placeholder'))}
|
||||||
bidi={this.identification}
|
bidi={this.identification}
|
||||||
disabled={this.loading} />
|
disabled={this.loading} />
|
||||||
</div>, 30);
|
</div>
|
||||||
|
|
||||||
items.add('password', <div className="Form-group">
|
<div className="Form-group">
|
||||||
<input className="FormControl" name="password" type="password" placeholder={extractText(app.translator.trans('core.forum.log_in.password_placeholder'))}
|
<input className="FormControl" name="password" type="password" placeholder={extractText(app.translator.trans('core.forum.log_in.password_placeholder'))}
|
||||||
bidi={this.password}
|
bidi={this.password}
|
||||||
disabled={this.loading} />
|
disabled={this.loading} />
|
||||||
</div>, 20);
|
</div>
|
||||||
|
|
||||||
items.add('remember', <div className="Form-group">
|
<div className="Form-group">
|
||||||
<div>
|
<div>
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input type="checkbox" bidi={this.remember} disabled={this.loading} />
|
<input type="checkbox" bidi={this.remember} disabled={this.loading} />
|
||||||
{app.translator.trans('core.forum.log_in.remember_me_label')}
|
{app.translator.trans('core.forum.log_in.remember_me_label')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>, 10);
|
</div>
|
||||||
|
|
||||||
items.add('submit', <div className="Form-group">
|
<div className="Form-group">
|
||||||
{Button.component({
|
{Button.component({
|
||||||
className: 'Button Button--primary Button--block',
|
className: 'Button Button--primary Button--block',
|
||||||
type: 'submit',
|
type: 'submit',
|
||||||
loading: this.loading,
|
loading: this.loading,
|
||||||
children: app.translator.trans('core.forum.log_in.submit_button')
|
children: app.translator.trans('core.forum.log_in.submit_button')
|
||||||
})}
|
})}
|
||||||
</div>, -10);
|
</div>
|
||||||
|
</div>
|
||||||
return items;
|
</div>,
|
||||||
}
|
<div className="Modal-footer">
|
||||||
|
|
||||||
footer() {
|
|
||||||
return [
|
|
||||||
<p className="LogInModal-forgotPassword">
|
<p className="LogInModal-forgotPassword">
|
||||||
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>
|
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>
|
||||||
</p>,
|
</p>
|
||||||
|
|
||||||
app.forum.attribute('allowSignUp') ? (
|
{app.forum.attribute('allowSignUp') ? (
|
||||||
<p className="LogInModal-signUp">
|
<p className="LogInModal-signUp">
|
||||||
{app.translator.trans('core.forum.log_in.sign_up_text', {a: <a onclick={this.signUp.bind(this)}/>})}
|
{app.translator.trans('core.forum.log_in.sign_up_text', {a: <a onclick={this.signUp.bind(this)}/>})}
|
||||||
</p>
|
</p>
|
||||||
) : ''
|
) : ''}
|
||||||
|
</div>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@@ -1,7 +1,7 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import humanTime from '../../common/helpers/humanTime';
|
import humanTime from 'flarum/helpers/humanTime';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Notification` component abstract displays a single notification.
|
* The `Notification` component abstract displays a single notification.
|
||||||
@@ -26,10 +26,10 @@ export default class Notification extends Component {
|
|||||||
|
|
||||||
if (!isInitialized) $(element).click(this.markAsRead.bind(this));
|
if (!isInitialized) $(element).click(this.markAsRead.bind(this));
|
||||||
}}>
|
}}>
|
||||||
{avatar(notification.fromUser())}
|
{avatar(notification.sender())}
|
||||||
{icon(this.icon(), {className: 'Notification-icon'})}
|
{icon(this.icon(), {className: 'Notification-icon'})}
|
||||||
<span className="Notification-content">{this.content()}</span>
|
<span className="Notification-content">{this.content()}</span>
|
||||||
{humanTime(notification.createdAt())}
|
{humanTime(notification.time())}
|
||||||
<div className="Notification-excerpt">
|
<div className="Notification-excerpt">
|
||||||
{this.excerpt()}
|
{this.excerpt()}
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,7 @@ export default class Notification extends Component {
|
|||||||
markAsRead() {
|
markAsRead() {
|
||||||
if (this.props.notification.isRead()) return;
|
if (this.props.notification.isRead()) return;
|
||||||
|
|
||||||
app.session.user.pushAttributes({unreadNotificationCount: app.session.user.unreadNotificationCount() - 1});
|
app.session.user.pushAttributes({unreadNotificationsCount: app.session.user.unreadNotificationsCount() - 1});
|
||||||
|
|
||||||
this.props.notification.save({isRead: true});
|
this.props.notification.save({isRead: true});
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import Checkbox from '../../common/components/Checkbox';
|
import Checkbox from 'flarum/components/Checkbox';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `NotificationGrid` component displays a table of notification types and
|
* The `NotificationGrid` component displays a table of notification types and
|
||||||
@@ -18,7 +18,10 @@ export default class NotificationGrid extends Component {
|
|||||||
*
|
*
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
*/
|
*/
|
||||||
this.methods = this.notificationMethods().toArray();
|
this.methods = [
|
||||||
|
{name: 'alert', icon: 'bell', label: app.translator.trans('core.forum.settings.notify_by_web_heading')},
|
||||||
|
{name: 'email', icon: 'envelope-o', label: app.translator.trans('core.forum.settings.notify_by_email_heading')}
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of notification type-method combinations to the checkbox instances
|
* A map of notification type-method combinations to the checkbox instances
|
||||||
@@ -31,7 +34,7 @@ export default class NotificationGrid extends Component {
|
|||||||
/**
|
/**
|
||||||
* Information about the available notification types.
|
* Information about the available notification types.
|
||||||
*
|
*
|
||||||
* @type {Array}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
this.types = this.notificationTypes().toArray();
|
this.types = this.notificationTypes().toArray();
|
||||||
|
|
||||||
@@ -161,42 +164,12 @@ export default class NotificationGrid extends Component {
|
|||||||
return 'notify_' + type + '_' + method;
|
return 'notify_' + type + '_' + method;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build an item list for the notification methods to display in the grid.
|
|
||||||
*
|
|
||||||
* Each notification method is an object which has the following properties:
|
|
||||||
*
|
|
||||||
* - `name` The name of the notification method.
|
|
||||||
* - `icon` The icon to display in the column header.
|
|
||||||
* - `label` The label to display in the column header.
|
|
||||||
*
|
|
||||||
* @return {ItemList}
|
|
||||||
*/
|
|
||||||
notificationMethods() {
|
|
||||||
const items = new ItemList();
|
|
||||||
|
|
||||||
items.add('alert', {
|
|
||||||
name: 'alert',
|
|
||||||
icon: 'fas fa-bell',
|
|
||||||
label: app.translator.trans('core.forum.settings.notify_by_web_heading'),
|
|
||||||
});
|
|
||||||
|
|
||||||
items.add('email', {
|
|
||||||
name: 'email',
|
|
||||||
icon: 'far fa-envelope',
|
|
||||||
label: app.translator.trans('core.forum.settings.notify_by_email_heading'),
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an item list for the notification types to display in the grid.
|
* Build an item list for the notification types to display in the grid.
|
||||||
*
|
*
|
||||||
* Each notification type is an object which has the following properties:
|
* Each notification type is an object which has the following properties:
|
||||||
*
|
*
|
||||||
* - `name` The name of the notification type.
|
* - `name` The name of the notification type.
|
||||||
* - `icon` The icon to display in the notification grid row.
|
|
||||||
* - `label` The label to display in the notification grid row.
|
* - `label` The label to display in the notification grid row.
|
||||||
*
|
*
|
||||||
* @return {ItemList}
|
* @return {ItemList}
|
||||||
@@ -206,7 +179,7 @@ export default class NotificationGrid extends Component {
|
|||||||
|
|
||||||
items.add('discussionRenamed', {
|
items.add('discussionRenamed', {
|
||||||
name: 'discussionRenamed',
|
name: 'discussionRenamed',
|
||||||
icon: 'fas fa-pencil-alt',
|
icon: 'pencil',
|
||||||
label: app.translator.trans('core.forum.settings.notify_discussion_renamed_label')
|
label: app.translator.trans('core.forum.settings.notify_discussion_renamed_label')
|
||||||
});
|
});
|
||||||
|
|
145
js/forum/src/components/NotificationList.js
Normal file
145
js/forum/src/components/NotificationList.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import Component from 'flarum/Component';
|
||||||
|
import listItems from 'flarum/helpers/listItems';
|
||||||
|
import Button from 'flarum/components/Button';
|
||||||
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||||
|
import Discussion from 'flarum/models/Discussion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `NotificationList` component displays a list of the logged-in user's
|
||||||
|
* notifications, grouped by discussion.
|
||||||
|
*/
|
||||||
|
export default class NotificationList extends Component {
|
||||||
|
init() {
|
||||||
|
/**
|
||||||
|
* Whether or not the notifications are loading.
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
const groups = [];
|
||||||
|
|
||||||
|
if (app.cache.notifications) {
|
||||||
|
const discussions = {};
|
||||||
|
|
||||||
|
// Build an array of discussions which the notifications are related to,
|
||||||
|
// and add the notifications as children.
|
||||||
|
app.cache.notifications.forEach(notification => {
|
||||||
|
const subject = notification.subject();
|
||||||
|
|
||||||
|
if (typeof subject === 'undefined') return;
|
||||||
|
|
||||||
|
// Get the discussion that this notification is related to. If it's not
|
||||||
|
// directly related to a discussion, it may be related to a post or
|
||||||
|
// other entity which is related to a discussion.
|
||||||
|
let discussion = false;
|
||||||
|
if (subject instanceof Discussion) discussion = subject;
|
||||||
|
else if (subject && subject.discussion) discussion = subject.discussion();
|
||||||
|
|
||||||
|
// If the notification is not related to a discussion directly or
|
||||||
|
// indirectly, then we will assign it to a neutral group.
|
||||||
|
const key = discussion ? discussion.id() : 0;
|
||||||
|
discussions[key] = discussions[key] || {discussion: discussion, notifications: []};
|
||||||
|
discussions[key].notifications.push(notification);
|
||||||
|
|
||||||
|
if (groups.indexOf(discussions[key]) === -1) {
|
||||||
|
groups.push(discussions[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="NotificationList">
|
||||||
|
<div className="NotificationList-header">
|
||||||
|
<div className="App-primaryControl">
|
||||||
|
{Button.component({
|
||||||
|
className: 'Button Button--icon Button--link',
|
||||||
|
icon: 'check',
|
||||||
|
title: app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip'),
|
||||||
|
onclick: this.markAllAsRead.bind(this)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 className="App-titleControl App-titleControl--text">{app.translator.trans('core.forum.notifications.title')}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="NotificationList-content">
|
||||||
|
{groups.length
|
||||||
|
? groups.map(group => {
|
||||||
|
const badges = group.discussion && group.discussion.badges().toArray();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="NotificationGroup">
|
||||||
|
{group.discussion
|
||||||
|
? (
|
||||||
|
<a className="NotificationGroup-header"
|
||||||
|
href={app.route.discussion(group.discussion)}
|
||||||
|
config={m.route}>
|
||||||
|
{badges && badges.length ? <ul className="NotificationGroup-badges badges">{listItems(badges)}</ul> : ''}
|
||||||
|
{group.discussion.title()}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="NotificationGroup-header">
|
||||||
|
{app.forum.attribute('title')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ul className="NotificationGroup-content">
|
||||||
|
{group.notifications.map(notification => {
|
||||||
|
const NotificationComponent = app.notificationComponents[notification.contentType()];
|
||||||
|
return NotificationComponent ? <li>{NotificationComponent.component({notification})}</li> : '';
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: !this.loading
|
||||||
|
? <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>
|
||||||
|
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load notifications into the application's cache if they haven't already
|
||||||
|
* been loaded.
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
if (app.cache.notifications && !app.session.user.newNotificationsCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
m.redraw();
|
||||||
|
|
||||||
|
app.store.find('notifications')
|
||||||
|
.then(notifications => {
|
||||||
|
app.session.user.pushAttributes({newNotificationsCount: 0});
|
||||||
|
app.cache.notifications = notifications.sort((a, b) => b.time() - a.time());
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.then(() => {
|
||||||
|
this.loading = false;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all of the notifications as read.
|
||||||
|
*/
|
||||||
|
markAllAsRead() {
|
||||||
|
if (!app.cache.notifications) return;
|
||||||
|
|
||||||
|
app.session.user.pushAttributes({unreadNotificationsCount: 0});
|
||||||
|
|
||||||
|
app.cache.notifications.forEach(notification => notification.pushAttributes({isRead: true}));
|
||||||
|
|
||||||
|
app.request({
|
||||||
|
url: app.forum.attribute('apiUrl') + '/notifications/read',
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import NotificationList from './NotificationList';
|
import NotificationList from 'flarum/components/NotificationList';
|
||||||
|
|
||||||
export default class NotificationsDropdown extends Dropdown {
|
export default class NotificationsDropdown extends Dropdown {
|
||||||
static initProps(props) {
|
static initProps(props) {
|
||||||
@@ -8,7 +8,7 @@ export default class NotificationsDropdown extends Dropdown {
|
|||||||
props.buttonClassName = props.buttonClassName || 'Button Button--flat';
|
props.buttonClassName = props.buttonClassName || 'Button Button--flat';
|
||||||
props.menuClassName = props.menuClassName || 'Dropdown-menu--right';
|
props.menuClassName = props.menuClassName || 'Dropdown-menu--right';
|
||||||
props.label = props.label || app.translator.trans('core.forum.notifications.tooltip');
|
props.label = props.label || app.translator.trans('core.forum.notifications.tooltip');
|
||||||
props.icon = props.icon || 'fas fa-bell';
|
props.icon = props.icon || 'bell';
|
||||||
|
|
||||||
super.initProps(props);
|
super.initProps(props);
|
||||||
}
|
}
|
||||||
@@ -62,11 +62,11 @@ export default class NotificationsDropdown extends Dropdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUnreadCount() {
|
getUnreadCount() {
|
||||||
return app.session.user.unreadNotificationCount();
|
return app.session.user.unreadNotificationsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
getNewCount() {
|
getNewCount() {
|
||||||
return app.session.user.newNotificationCount();
|
return app.session.user.newNotificationsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
menuClick(e) {
|
menuClick(e) {
|
@@ -1,5 +1,5 @@
|
|||||||
import Page from './Page';
|
import Page from 'flarum/components/Page';
|
||||||
import NotificationList from './NotificationList';
|
import NotificationList from 'flarum/components/NotificationList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `NotificationsPage` component shows the notifications list. It is only
|
* The `NotificationsPage` component shows the notifications list. It is only
|
@@ -1,4 +1,4 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Page` component
|
* The `Page` component
|
@@ -1,9 +1,9 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
|
import SubtreeRetainer from 'flarum/utils/SubtreeRetainer';
|
||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
import PostControls from '../utils/PostControls';
|
import PostControls from 'flarum/utils/PostControls';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from 'flarum/helpers/listItems';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Post` component displays a single post. The basic post template just
|
* The `Post` component displays a single post. The basic post template just
|
||||||
@@ -57,7 +57,7 @@ export default class Post extends Component {
|
|||||||
className="Post-controls"
|
className="Post-controls"
|
||||||
buttonClassName="Button Button--icon Button--flat"
|
buttonClassName="Button Button--icon Button--flat"
|
||||||
menuClassName="Dropdown-menu--right"
|
menuClassName="Dropdown-menu--right"
|
||||||
icon="fas fa-ellipsis-h"
|
icon="ellipsis-h"
|
||||||
onshow={() => this.$('.Post-actions').addClass('open')}
|
onshow={() => this.$('.Post-actions').addClass('open')}
|
||||||
onhide={() => this.$('.Post-actions').removeClass('open')}>
|
onhide={() => this.$('.Post-actions').removeClass('open')}>
|
||||||
{controls}
|
{controls}
|
@@ -1,6 +1,6 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import humanTime from '../../common/utils/humanTime';
|
import humanTime from 'flarum/utils/humanTime';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `PostEdited` component displays information about when and by whom a post
|
* The `PostEdited` component displays information about when and by whom a post
|
||||||
@@ -18,10 +18,10 @@ export default class PostEdited extends Component {
|
|||||||
|
|
||||||
view() {
|
view() {
|
||||||
const post = this.props.post;
|
const post = this.props.post;
|
||||||
const editedUser = post.editedUser();
|
const editUser = post.editUser();
|
||||||
const editedInfo = extractText(app.translator.trans(
|
const editedInfo = extractText(app.translator.trans(
|
||||||
'core.forum.post.edited_tooltip',
|
'core.forum.post.edited_tooltip',
|
||||||
{user: editedUser, ago: humanTime(post.editedAt())}
|
{user: editUser, ago: humanTime(post.editTime())}
|
||||||
));
|
));
|
||||||
if (editedInfo !== this.oldEditedInfo) {
|
if (editedInfo !== this.oldEditedInfo) {
|
||||||
this.shouldUpdateTooltip = true;
|
this.shouldUpdateTooltip = true;
|
@@ -1,6 +1,6 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import humanTime from '../../common/helpers/humanTime';
|
import humanTime from 'flarum/helpers/humanTime';
|
||||||
import fullTime from '../../common/helpers/fullTime';
|
import fullTime from 'flarum/helpers/fullTime';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `PostMeta` component displays the time of a post, and when clicked, shows
|
* The `PostMeta` component displays the time of a post, and when clicked, shows
|
||||||
@@ -14,7 +14,7 @@ import fullTime from '../../common/helpers/fullTime';
|
|||||||
export default class PostMeta extends Component {
|
export default class PostMeta extends Component {
|
||||||
view() {
|
view() {
|
||||||
const post = this.props.post;
|
const post = this.props.post;
|
||||||
const time = post.createdAt();
|
const time = post.time();
|
||||||
const permalink = this.getPermalink(post);
|
const permalink = this.getPermalink(post);
|
||||||
const touch = 'ontouchstart' in document.documentElement;
|
const touch = 'ontouchstart' in document.documentElement;
|
||||||
|
|
@@ -1,7 +1,7 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import avatar from '../../common/helpers/avatar';
|
import avatar from 'flarum/helpers/avatar';
|
||||||
import username from '../../common/helpers/username';
|
import username from 'flarum/helpers/username';
|
||||||
import highlight from '../../common/helpers/highlight';
|
import highlight from 'flarum/helpers/highlight';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `PostPreview` component shows a link to a post containing the avatar and
|
* The `PostPreview` component shows a link to a post containing the avatar and
|
@@ -1,11 +1,11 @@
|
|||||||
import Component from '../../common/Component';
|
import Component from 'flarum/Component';
|
||||||
import ScrollListener from '../../common/utils/ScrollListener';
|
import ScrollListener from 'flarum/utils/ScrollListener';
|
||||||
import PostLoading from './LoadingPost';
|
import PostLoading from 'flarum/components/LoadingPost';
|
||||||
import anchorScroll from '../../common/utils/anchorScroll';
|
import anchorScroll from 'flarum/utils/anchorScroll';
|
||||||
import mixin from '../../common/utils/mixin';
|
import mixin from 'flarum/utils/mixin';
|
||||||
import evented from '../../common/utils/evented';
|
import evented from 'flarum/utils/evented';
|
||||||
import ReplyPlaceholder from './ReplyPlaceholder';
|
import ReplyPlaceholder from 'flarum/components/ReplyPlaceholder';
|
||||||
import Button from '../../common/components/Button';
|
import Button from 'flarum/components/Button';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `PostStream` component displays an infinitely-scrollable wall of posts in
|
* The `PostStream` component displays an infinitely-scrollable wall of posts in
|
||||||
@@ -122,11 +122,11 @@ class PostStream extends Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
update() {
|
update() {
|
||||||
if (!this.viewingEnd) return m.deferred().resolve().promise;
|
if (!this.viewingEnd) return;
|
||||||
|
|
||||||
this.visibleEnd = this.count();
|
this.visibleEnd = this.count();
|
||||||
|
|
||||||
return this.loadRange(this.visibleStart, this.visibleEnd).then(() => m.redraw());
|
this.loadRange(this.visibleStart, this.visibleEnd).then(() => m.redraw());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,7 +205,7 @@ class PostStream extends Component {
|
|||||||
const attrs = {'data-index': this.visibleStart + i};
|
const attrs = {'data-index': this.visibleStart + i};
|
||||||
|
|
||||||
if (post) {
|
if (post) {
|
||||||
const time = post.createdAt();
|
const time = post.time();
|
||||||
const PostComponent = app.postComponents[post.contentType()];
|
const PostComponent = app.postComponents[post.contentType()];
|
||||||
content = PostComponent ? PostComponent.component({post}) : '';
|
content = PostComponent ? PostComponent.component({post}) : '';
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user