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

Compare commits

...

993 Commits

Author SHA1 Message Date
Toby Zerner
5bcf72dd49 Bump version 2018-11-09 21:22:11 +10:30
Toby Zerner
0536b208e1 Fix leak of private information when updating users 2018-11-09 21:21:21 +10:30
Clark Winkelmann
c6aeeeb3c1 Always apply attributes from token when registering
The change introduced in #1033 transformed any identification attribute returned from an OAuth provider to just a default value.

When the identification attribute used by the provider is the email or username, this allowed the user to supply a different email or username and still getting an already-enabled account with the credentials he entered.

Skipping attributes with an existing value makes no sense here because it's a always a fresh user and values from AbstractOAuth2Controller::getIdentification() should always be enforced.
2018-01-06 20:04:42 +10:30
Toby Zerner
ce8a5b3e0f v0.1.0-beta.7 2017-07-22 12:48:58 +09:30
Toby Zerner
5faf0fcde5 And remove unused import 2017-07-22 12:31:23 +09:30
Toby Zerner
65c0b436c0 Fix missed instance of back button tooltip 2017-07-22 12:30:55 +09:30
Toby Zerner
8d76168bd4 Oops, forgot to correct test code 2017-07-22 12:29:03 +09:30
Toby Zerner
d16f4dbefa Recompile JS 2017-07-22 12:08:17 +09:30
Toby Zerner
e3e4786391 Simplify global back button
The behaviour is not overly intuitive, and the icon wasn't helping
(hamburger icon usually means "menu"). Now the back button always goes
back to the index, no matter where you are, and there's a tooltip that
says "Back to discussion list".
2017-07-22 12:08:09 +09:30
Toby Zerner
c1c7d4c73a Only display "show language selector" toggle if there is more than one language 2017-07-22 11:47:50 +09:30
Toby Zerner
8da8c9ac7d Clean up appearance of Rename Discussion modal
Also fix infinite loading if there's a validation error.
2017-07-22 11:47:04 +09:30
Toby Zerner
fb68aa88db Use default dropdown appearance for sort dropdown 2017-07-22 11:46:16 +09:30
Toby Zerner
afc597c189 Remove fa-fw class from all icons
Often it is desirable to NOT have this class applied, and it is easier
to apply its styles if needed rather than un-apply it.
2017-07-22 11:45:42 +09:30
Toby Zerner
4f3e67714e Fix incorrect migration notes for extensions without any migrations
When running migrations for an extension without any migrations (eg.
BBCode), the migration notes for the previous extension were being
displayed, because the Migrator never had a chance to clear them.
2017-07-22 11:43:50 +09:30
Toby Zerner
54be3ad3c8 Define the default moderator group ID
This allows extensions to add default permissions for moderators,
without having to hardcode in the default moderator group ID.
2017-07-22 11:41:20 +09:30
Toby Zerner
0b00d56416 Add a new migration helper for adding default permissions 2017-07-22 11:40:06 +09:30
Toby Zerner
89d4a1e849 Remove MySQL port field from visual installer
Port can still be specified by suffixing the host with a :

closes #825
2017-07-22 11:32:07 +09:30
Daniël Klabbers
43ee7b59a4 Update Client.php (#1198)
* Update Client.php

Now forwarding exceptions from client to page in case debug mode is on. Fixes #1120.

* Update Client.php

Satisfying .. the unsatisfiable.

* Update Client.php

Satisfying again.
2017-07-19 22:14:00 +02:00
David Sevilla Martín
d052f6b639 Use dropdown for discussion list order input (#1191)
* Use dropdown menu for index select input

* Fix space before `:`
2017-07-17 13:40:35 +09:30
Toby Zerner
4b47adabcf Oops, that should be in seconds not minutes 2017-07-08 22:35:11 +09:30
Toby Zerner
93140b8fa4 Remember users forever (5 years) rather than 2 weeks 2017-07-08 22:29:26 +09:30
Toby Zerner
ade2166310 Revise Remember Me checkbox appearance
Use a generic checkbox instead of a switch — it's more familiar and accessible.

Signed-off-by: Toby Zerner <toby.zerner@gmail.com>
2017-07-08 22:25:24 +09:30
Toby Zerner
a9969119d2 Merge branch 'master' of https://github.com/flarum/core 2017-07-08 21:52:01 +09:30
Toby Zerner
94a8eaec64 Update dependencies 2017-07-08 21:50:54 +09:30
Franz Liedke
8ea13dc826 Flatten implementation of SelfDemotionGuard listener
Refs #736 and #1195.
2017-07-06 21:57:47 +02:00
David Sevilla Martín
99d42372c3 Prevent yourself from locking yourself out of admin group (#1195) 2017-07-06 21:43:01 +02:00
Franz Liedke
01b56eecdb Merge pull request #1201 from Luceos/patch-3
Update AbstractSerializer.php
2017-06-23 00:02:16 +02:00
Franz Liedke
bcdcb8c20e Merge pull request #1202 from Luceos/patch-4
Update WebAppView.php
2017-06-23 00:02:02 +02:00
Daniël Klabbers
d6c99eccdb Update WebAppView.php
Added argument type hinting where absent.
2017-06-22 16:27:10 +02:00
Daniël Klabbers
01cb8ab79d Update AbstractSerializer.php
Fixes missing argument in method. Verified it has to be a string.
2017-06-22 14:28:51 +02:00
Franz Liedke
877aed215b Merge pull request #1199 from Luceos/patch-2
Update StartSession.php
2017-06-20 08:17:39 +02:00
Daniël Klabbers
57570d960e Update StartSession.php
Fixed CookieFactory typo in phpdoc.
2017-06-19 16:47:20 +02:00
Daniël Klabbers
04c4806f6f making posts and discussions private (#1153)
* flagrow/byobu#11 making posts and discussions private

* tested migrations and tested setting is_private on discussion and post manually

* added phpdoc for Post and Discussion and added the casting for these attributes

* satisfying styleci

* fixes for review

* added new private discussion event and included it in the access policy

* added new private post event and included it in the access policy
2017-05-27 14:19:15 +09:30
Davis
4c0339c30e Allow JSON to be used for Install Command (#1193)
* Allow JSON to be used for Install Command

* Return configuration as array instead of object.

* Update InstallCommand.php
2017-05-27 14:18:09 +09:30
David Sevilla Martín
e64dc4ea45 Add viewUserList permission (#1190) 2017-05-24 22:06:56 +09:30
Franz Liedke
305076814f Merge pull request #1189 from datitisev/762-exclude-files-from-distribution
Update .gitattributes to exclude files for distribution
2017-05-20 13:42:34 +02:00
David Sevilla Martin
e31edd29d2 Exclude files for distribution using .gitignore 2017-05-18 18:46:55 -04:00
David Sevilla Martín
23b423c6ce #1184 Fix /api/posts returning 500 (#1188)
* Fix ListPostsController::applyFilters not receiving array if argument not present

* Whoops! Use `[]` instead of `array()`

* Update AbstractSerializeController.php

* Update ListPostsController.php
2017-05-18 22:04:00 +02:00
Franz Liedke
1af1f472f9 Recompile dist JavaScript 2017-05-18 09:14:06 +02:00
Franz Liedke
7c86f7a34c Merge pull request #1186 from flarum/Luceos-patch-1
Update UserControls.js
2017-05-18 09:13:28 +02:00
Daniël Klabbers
14e49269d6 Update UserControls.js
Possibly c/p mistake with argument name. UserControls using argument discussion in controls method.
2017-05-17 14:07:38 +02:00
Zeokat
7837fff107 Support PNG avatars with transparent backgrounds and fix EXIF rotation (#1168)
As `orientate` requires the EXIF extension, we can only call it if the extension is installed.

Fixes #1161 and #1163.
2017-05-10 21:23:08 +02:00
Franz Liedke
3dfa6bc8cb Merge pull request #1179 from flarum/Luceos-patch-1
Update mixin.js
2017-05-09 23:07:16 +02:00
Daniël Klabbers
e47fe288fa Update mixin.js
Typo fixed
2017-05-09 13:58:07 +02:00
Franz Liedke
03e30d7d4f Merge pull request #1178 from flarum/Luceos-patch-1
Update HandleErrors.php
2017-05-09 08:57:26 +02:00
Daniël Klabbers
9836ff6c54 Update HandleErrors.php
@franzliedke forgot to make variables available to the method, just triggered this but got a warning that all three variables are undefined.
2017-05-08 16:45:58 +02:00
issyrocks12
bb1c655c90 Change to switch to fit style 2017-05-04 22:36:37 +02:00
Franz Liedke
bf20fe595a Fix incorrect sort field name
Closes #1175.
2017-05-04 21:37:03 +02:00
Franz Liedke
b5db57156b Merge pull request #1172 from tpokorra/fixDefaultLanguageSelection
Admin: fix default language selector
2017-05-02 08:37:32 +02:00
Timotheus Pokorra
a7d3bdf244 Admin: fix default language selector
the binding of the control to the value was missing
fixes #1164
2017-05-01 18:46:12 +02:00
Franz Liedke
c82f0bde61 Merge pull request #1155 from ssfinney/feature/mediumtext_for_post_content_column
Change content column from TEXT to MEDIUMTEXT
2017-04-13 08:04:05 +02:00
Stephen Finney
986102c1d3 Change content column from TEXT to MEDIUMTEXT
Fixes #1044
2017-04-09 16:12:34 -04:00
Franz Liedke
2140619c0b Prevent reverting editable user bio on click
Turns out the click handler was bound to the surrounding element
rather than the one that wraps the rendered bio when it is not
being edited.

Fixes #1145.
2017-03-17 22:14:51 +01:00
Franz Liedke
2f714a01ed Cookies: Set expires flag for remember cookies
Without this, session remembering would not work in Internet
Explorer (and Edge?).

Fixes #1127.
2017-03-14 22:25:20 +01:00
Franz Liedke
231d018de5 Add link() and setCanonicalUrl() methods to the WebAppView
These make it easier for controllers to define relationships from
the current to other pages, which is important for SEO mostly.
2017-03-13 18:08:32 +01:00
Franz Liedke
5d62231004 Fix comment typo 2017-03-13 12:52:25 +01:00
Franz Liedke
123c3a93f5 Fix indentation 2017-03-12 23:05:04 +01:00
Franz Liedke
c1eec2b261 Fix indentation 2017-03-12 23:03:46 +01:00
David Sevilla Martín
60d3d6ef99 Add option to hide the language selector (#1106)
* Added option to hide the language selector in the header
* Added `hide_language_selector` Switch to BasicsPage
* Added `hideLanguageSelector` property to ForumSerializer
* Apparently fixed the "Add Extension" button locale.... someone must not have compiled their changes :P

* Changed hideLanguageSelector (and such) to showLanguageSelector

* Change `core.admin.basics.show_language_selector_heading` to be `_label`

* Change showLanguageSelector in ForumSerializer to be boolean, default: true

* Ooops! Remove console.log 🤦‍♂️
2017-03-07 10:04:44 +10:30
Toby Zerner
7862bd32dd Merge pull request #1141 from sijad/fix-logout-redirect
prevent unsafe redirect via logout controller
2017-03-04 22:10:18 +10:30
Sajjad Hashemian
92b555a246 prevent unsafe redirect via logout controller 2017-03-04 14:51:21 +03:30
Toby Zerner
687ec6a199 Merge pull request #1131 from flarum/866/affixSidebar-resize
Affix sidebar when window is resized
2017-03-03 15:24:13 +10:30
Franz Liedke
f788a0a972 Fix nesting of rename_discussion translations
Refs flarum/flarum-ext-english#98.
2017-02-28 22:42:41 +01:00
Franz Liedke
e7bec9fe29 Merge pull request #1135 from sijad/add-noindex-header
Prevent crawlers to index nojs pages
2017-02-27 18:13:38 +01:00
Franz Liedke
57da4e24cb Rename translation key 2017-02-26 23:47:20 +01:00
Franz Liedke
7d1a22bcb5 Rename modal component 2017-02-26 23:44:57 +01:00
Sajjad Hashemian
8cc117d89d Prevent crawlers to index nojs pages 2017-02-26 19:48:33 +03:30
Franz Liedke
31ef02dc2c Compile dist JS 2017-02-17 00:19:33 +01:00
Franz Liedke
95c9ff9243 Affix sidebar when window is resized
Fixes #866.
2017-02-17 00:08:20 +01:00
Franz Liedke
9718f54683 Merge pull request #1130 from clarkwinkelmann/patch-1
Fix asset path when unpublishing
2017-02-16 09:17:32 +01:00
Clark Winkelmann
bb1e3278de Fix asset path when unpublishing 2017-02-16 01:51:33 +01:00
Franz Liedke
bbcc33b5b5 Turn a few setters/getters into public attributes
There were no type hints etc. going on, and we would have needed
the getters anyway.

See https://github.com/flarum/core/pull/1105#issuecomment-279310998.
2017-02-14 22:56:17 +01:00
Franz Liedke
30076547e5 Merge pull request #1126 from janga1997/hideCompiledDiff
Hide diff for compiled diff files
2017-02-12 17:46:12 +01:00
janga
7b710d5898 Hide diff for compiled diff files 2017-02-12 10:26:10 +05:30
Franz Liedke
d02b5c9db7 Merge pull request #1124 from Luceos/master
fixed issues with $extension visibility and typehinting
2017-02-12 00:53:50 +01:00
Daniël Klabbers
cd70819fd5 fixed issues with $extension visibility and typehinting 2017-02-11 21:14:44 +01:00
Franz Liedke
20b4619e75 Fix Stratigility deprecation, for real this time 2017-02-07 20:52:06 +01:00
Franz Liedke
fdec2fd094 Recompile dist JS 2017-02-04 00:08:16 +01:00
Franz Liedke
d7e4ae09b3 Merge pull request #1113 from oanhnn/patch-1
Correct image orientation according to Exif data
2017-02-03 23:50:04 +01:00
David Sevilla Martín
fcfc1b2a37 Add more attributes in app.blade.php and add a setDescription method. (#1105)
* Added `language` and `direction` properties to WebAppView

* Use properties `language` and `direction` in app.blade.php

* Added WebAppView::setDescription to set the meta description

* Whoops! Changed "ltr" to \'ltr\'. Thanks StyleCI :)

* Removed unnecessary `= null` for

* Changed `.. ? .. : ..` to `.. ?: ..`. Useful thing right there ;)
2017-02-03 23:09:22 +01:00
Franz Liedke
01eba18164 Merge pull request #1100 from flarum/stratigility-update
Update to Zend Stratigility 1.3
2017-02-03 22:03:55 +01:00
Franz Liedke
0dcf7d6aa9 Travis: Run tests on PHP 7.1 as well 2017-02-03 21:53:39 +01:00
Franz Liedke
015967a76c Require PHP 5.6 2017-02-03 21:53:20 +01:00
renyuneyun
3cd59e12f5 Allow to manually activate users (#1093)
* Allow to manually activate users

* Use resources instead of hard-coded strings
2017-02-03 21:13:32 +01:00
David Sevilla Martín
26d07699e9 Turn "Rename Discussion" dialog into a modal, closes #616 (#1083)
* Changed "Rename Discussion" prompt into a modal.
* Added DiscussionRenameModal component (Modal)
* Changed DiscussionControls.renameAction to use the modal (I may have removed the ability to return a promise)

* Added punycode.js back to js/forum dist

* Fixed some formatting, removed some unnecessary variables
2017-02-03 20:56:28 +01:00
Franz Liedke
b7d6ba4893 Trim lines 2017-02-03 20:28:04 +01:00
Franz Liedke
d3753d94ae Throw HTTP 403 on extension validation error
The way I read it, HTTP 405 is a generic statement about the
resource. Once a language pack is not the default, this is not
true anymore, so I figured 403 is more correct.
2017-02-03 20:25:21 +01:00
Franz Liedke
9349ed13fc Make event attributes public
Without this, reading the extension information would not be
possible for the validator.
2017-02-03 20:23:24 +01:00
Franz Liedke
91ace15f6d Merge pull request #1032 from dav-is/patch-1
Prevent deletion of default/all locale(s)
2017-02-03 20:21:19 +01:00
Franz Liedke
7c1b0bfcf2 Clarify condition
I want to make it a little more clear that we are checking exactly
for these two values. That may also help preventing further confusion
as to why we are not using empty() here.

Amendment to PR #1033.
2017-02-03 18:53:21 +01:00
Franz Liedke
542bae6277 Merge pull request #1033 from dav-is/patch-2
Prevent Overwriting of User's Attributes on Register (#897)
2017-02-03 18:51:11 +01:00
Franz Liedke
275c14ee7f Merge pull request #1009 2017-02-03 18:44:03 +01:00
Franz Liedke
bccc970231 Try to extract port from host when installing in console
The very last suggestion broght up in pull request #989.
2017-02-03 18:15:52 +01:00
Franz Liedke
da6f79b34a Ask for database port when installing via console 2017-02-03 18:15:15 +01:00
Franz Liedke
a3cbec25db Make MySQL port field optional
Last fix for pull request #989.
2017-02-03 17:56:02 +01:00
Franz Liedke
2225fdec72 Merge pull request #989 from nielstholenaar/master
Fixes #825
2017-02-03 17:51:07 +01:00
Oanh Nguyen
6a532ec14e Correct image orientation according to Exif data
When using mobile, take a photo and upload it as avatar, it's orientation is incorrect.
This commit will fix this problem.
2017-01-25 11:49:14 +07:00
Franz Liedke
9416d16ebb Clean up gambit 2017-01-03 21:22:19 +01:00
Li Ji
d6857b0fe5 Add group gambit to support search user by group name (#1073)
Add group gambit to support search user by group name

/api/users?filter[q]=group:admin
/api/users?filter[q]=group:admin,mod

refer to #256
2017-01-03 21:13:58 +01:00
Franz Liedke
2c7e7f5b39 Merge pull request #1094 from milescellar/patch-1
Make Add Extension modal's title translatable
2017-01-03 21:02:38 +01:00
Franz Liedke
b5b18dd436 Update to Zend Stratigility 1.3
* Fix dependency version constraint. (Reverts #1066.)
* Allow exceptions to be raised when dispatching middleware.
* Fix our error handler middleware (do not implement Stratigility's
  error handler interface, catch exceptions instead).

See https://docs.zendframework.com/zend-stratigility/migration/to-v2/.

Closes #1069.
2017-01-02 22:57:09 +01:00
Daniël Klabbers
4778ae5f74 Merge pull request #1099 from milescellar/patch-3
Update year
2017-01-02 08:22:43 +01:00
Miles Cellar
0936a630ef Update year 2017-01-01 22:41:35 +01:00
Franz Liedke
ec8ae6e03b Remove unnecessary method call
This is already the default value in the base SetCookie class.
2016-12-29 11:17:27 +01:00
Franz Liedke
9ffdeff608 Make StyleCI happy 2016-12-29 11:07:23 +01:00
Franz Liedke
8540932638 Clean up and document code 2016-12-28 23:01:49 +01:00
Franz Liedke
974f45e4e8 Remove unnecessary parameters 2016-12-28 23:01:27 +01:00
Miles Cellar
32ac48c6a9 Make Add Extension modal's title translatable 2016-12-27 01:52:54 +01:00
Toby Zerner
af5b86806a Merge pull request #1092 from sijad/cookie-helper
Add cookie helper
2016-12-22 20:30:46 +10:30
Sajjad Hashemian
aeef45b3cd Add cookie factory 2016-12-22 12:00:56 +03:30
Toby Zerner
8aa70de765 Merge pull request #1085 from krnch/krnch-patch-2
Cookies set with Secure flag in HTTPS mode #1084
2016-12-11 18:16:04 +10:30
karan
076a71c621 Update StartSession.php 2016-12-10 02:46:07 -05:00
Sajjad Hashemian
06c32b668d Remember checkbox (#1075)
* Add session option to Rememberer class

* Update session login function to allow send additional data

* Add Remember me checkbox

* Cleanup login modal
2016-11-29 18:02:12 +10:30
Toby Zerner
7af4b8d45f Merge pull request #1049 from JoshyPHP/TextFormatter-0.8.0
Updated s9e\TextFormatter to 0.8.1
2016-11-29 16:36:10 +10:30
Toby Zerner
cbba325a87 Add punycode. ref #1049 2016-11-29 16:35:45 +10:30
Toby Zerner
b7d7e8b18a Merge pull request #1077 from flarum/analysis-Xan0ZZ
Apply fixes from StyleCI
2016-11-29 15:46:14 +10:30
Toby Zerner
1031826a3d Apply fixes from StyleCI
[ci skip] [skip ci]
2016-11-29 05:03:53 +00:00
Toby Zerner
3612ca7aca Allow accessing the session via the actor
This is a bit sloppy (might come up with a better solution yet), but since most events provide access to the actor but not the request, this was the easiest/quickest way to allow extensions to access the session.
2016-11-28 11:45:55 +10:30
Toby Zerner
c2ee84a115 Don't rely on a successful forum API call to enable debug mode 2016-11-28 11:45:55 +10:30
Toby Zerner
060745ecb7 Support module prefixing of locale resources
In preparation for upcoming changes, allow locale resources to have a module prefix added when they are loaded from a file.
2016-11-28 11:45:55 +10:30
Toby Zerner
dd209b1747 Eager load discussion relationships
Since extensions may add nested includes, we need to make sure they are eager-loaded to avoid excessive queries. For example, when the tags extension adds "tags" and "tags.state".
2016-11-28 11:45:55 +10:30
Toby Zerner
aeb0a411b9 Add specific message for username validation 2016-11-28 11:45:55 +10:30
Toby Zerner
1ebb8bf39a Merge pull request #1074 from datitisev/specify-text-inputs-type
Added search input types to 3 inputs total, closes #726
2016-11-28 11:13:39 +10:30
David Sevilla Martin
fcdf36b3d0 Added search input types to 3 inputs total, closes #726
* Added type search to search bar (forum)
* Added CSS `box-sizing: inherit` to search <input> because bootstrap styles mess up the search box
* Added type color to both color settings in appearance (admin)
2016-11-27 19:39:47 -05:00
Toby Zerner
ab912ba1ad Update StyleCI rules 2016-11-16 15:47:12 +10:30
Toby Zerner
4b8eb5d6e4 Make reset password form look slightly nicer
(Still needs a proper makeover!)
2016-11-13 09:01:38 +10:30
Toby Zerner
0e20949eb0 Prevent notice if bootstrapping app in command line environment 2016-11-13 08:57:39 +10:30
Toby Zerner
b2c691a03d Improve password reset validation/error handling 2016-11-13 08:51:38 +10:30
Toby Zerner
dde0de046a Merge pull request #1066 from Luceos/patch-1
Update composer.json
2016-11-12 12:44:07 +10:30
Daniël Klabbers
7a9795fbc3 Update composer.json
fixes #1065 , this is a temporary fix until compatibility with 1.3.0 is guaranteed by refactoring
2016-11-12 01:20:10 +01:00
Toby Zerner
f30fac6a94 Merge pull request #1063 from sijad/default-export
Fix syntax errors when compiling js files
2016-11-08 08:03:26 +10:30
Sajjad Hashemian
1fb8092987 Fix syntax errors when compiling js files 2016-11-08 00:32:44 +03:30
Toby Zerner
ea6b943dbd Make getApp available to the public 2016-11-07 21:23:31 +10:30
Toby Zerner
b9918e6c40 Add missing parameter 2016-11-07 18:22:20 +10:30
Toby Zerner
b3e1a023c2 Add event to allow custom user password validation 2016-11-07 18:03:49 +10:30
Toby Zerner
46bb66dd94 v0.1.0-beta.6 2016-10-19 21:11:30 +10:30
JoshyPHP
96926a180a Updated s9e\TextFormatter to 0.8.1 2016-10-10 01:58:40 +02:00
Daniël Klabbers
e58ff71f93 remove unnecessary gitignore 2016-10-08 13:03:10 +02:00
Franz Liedke
2d5090ef12 Merge pull request #1046 from sijad/remove-extension-generate
Remove extension generator
2016-10-06 00:29:02 +02:00
Davis
f3bdc163fa $extension was undefined 2016-10-05 12:46:14 -05:00
Davis
0df6eee10f Change exception message 2016-10-04 15:09:43 -05:00
Sajjad Hashemian
971b4c121c Remove extension generator 2016-10-04 23:26:03 +03:30
Daniël Klabbers
9bb7ca5d80 issue template (#1028) 2016-09-24 17:09:09 +02:00
Davis
258a4b352d Change == to === 2016-09-13 14:48:21 -05:00
Davis
24580ced7a Wish it was automatic :/ 2016-09-13 05:56:13 -05:00
Davis
8e90d9f9e2 Anything for Stylecl's green check 2016-09-13 05:55:00 -05:00
Davis
af36ef3fa9 StyleCl and the space after <?php is annoying 2016-09-13 05:53:46 -05:00
Davis
eef63745e6 Prevent overwriting of user's attributes on register 2016-09-13 02:22:09 -05:00
Davis
c702e911b3 StyleCl is making me hate myself 2016-09-12 22:31:55 -05:00
Davis
73d2ee825b Forgot to subscribe 2016-09-12 22:31:03 -05:00
Davis
9f99610542 StyleCl FINALLY! 2016-09-12 22:28:50 -05:00
Davis
1192867c4f StyleCl 2016-09-12 22:28:03 -05:00
Davis
b048498b84 StyleCl 2016-09-12 22:27:18 -05:00
Davis
81f7a39a31 StyleCl 2016-09-12 22:26:22 -05:00
Davis
ea12bbaf48 StyleCL 2016-09-12 22:24:57 -05:00
Davis
c8122a7879 Make StyleCL Happy 2016-09-12 22:23:31 -05:00
Davis
1a5d7a337d Remove useless code 2016-09-12 22:19:47 -05:00
Davis
c29ea98d48 Add WillBe Modifiers 2016-09-12 22:17:54 -05:00
Davis
3702ffa998 Create ExtensionValidator.php 2016-09-12 19:14:30 -05:00
Davis
58f9c22375 Create ExtensionWillBeEnabled.php 2016-09-12 17:07:00 -05:00
Davis
939a1e9ca8 Forgot the extension :/ 2016-09-12 17:05:41 -05:00
Davis
736f22a31a Create ExtensionWillBeDisabled 2016-09-12 16:57:24 -05:00
Toby Zerner
34f3d93ce5 Remove duplicated code 2016-09-03 23:14:17 +09:30
Toby Zerner
cba278611a Prevent page zoom on input focus in iOS 10. fixes flarum/core#1023 2016-09-03 23:12:59 +09:30
Sheldon Fernandes
f20b35080b Automatically focus on composer textarea on iOS. closes flarum/core#995 2016-09-03 23:11:56 +09:30
Toby Zerner
df247925d4 Fix locale JS files not being added; add (temporary?) API to add locale CSS files
fixes flarum/core#970
2016-09-03 22:22:36 +09:30
Toby Zerner
44726633ce Extract new method to filter a list of post IDs by visibility 2016-09-03 21:46:22 +09:30
Toby Zerner
0d8c8c3be3 Add missing property declaration 2016-09-03 21:45:45 +09:30
David Sevilla Martin
592dd6a927 Fixed error when user is not logged in; fixed notification count not updating when clicking home link (added m.redraw) 2016-08-30 10:35:31 -04:00
Toby Zerner
882d22191f Make search dropdown filtering case-insensitive. closes flarum/core#961 2016-08-28 00:00:20 +09:30
Toby Zerner
0d99f75a6d Disallow svg images to be erroneously uploaded
Laravel's `image` validation rule allows svg files to pass validation, but we can't handle svgs so it would result in an unspecified 500 error which isn't nice.
2016-08-27 23:54:18 +09:30
Toby Zerner
d5797dae79 Remove temporary file after avatar upload failure. closes flarum/core#999 2016-08-27 23:53:02 +09:30
Toby Zerner
82be1cea5d Fix post header items sometimes getting out of order. closes flarum/core#975
Interesting bug. Turns out that the JSX for the post header item list was producing m('ul', null, [children]), as you would expect. But Mithril 0.1.x interprets the null as another child rather than an attributes splat. This results in an empty text node being added to the DOM, which mucks up Mithril's diffing algorithm when it tries to add/move the items that we provide in the children array. The workaround is to not use JSX so we can get rid of that null/empty text node. This behaviour has been fixed in Mithril 1.0 so we will be able to remove the workaround.
2016-08-27 23:41:54 +09:30
Toby Zerner
e1b3642453 Update bower deps 2016-08-27 23:37:16 +09:30
Toby Zerner
7031ef7ef7 Avoid JSX to workaround Mithril 0.1.x weirdness. closes flarum/core#975 2016-08-27 23:33:34 +09:30
Toby Zerner
371e2ef759 Merge pull request #1011 from zcodes/master
Fixed fontawesome relative path
2016-07-30 16:08:46 +10:00
zcodes
203358a796 reduce fontawesome relative path in less files. 2016-07-30 13:17:17 +08:00
Toby Zerner
ff68c104a6 Merge pull request #1012 from JoshyPHP/TextFormatter-0.6.0
Updated s9e\TextFormatter to 0.6.1
2016-07-30 12:43:27 +10:00
JoshyPHP
7b3ac18c14 Updated s9e\TextFormatter to 0.6.1 2016-07-30 03:53:32 +02:00
zcodes
1ced0456ca Fixed fontawesome relative path
If install flarum in the site's root directory, it seems nothing
is wrong because the server software will trim the path, but if
install flarum in a sub directory, the font file of fontawesome
will not load correctly.
2016-07-29 22:02:50 +08:00
Toby Zerner
c0407ab016 Merge pull request #1007 from datitisev/1002-break-words-in-post-with-excesive-width
#1002 Handle word wrapping properly in posts
2016-07-29 21:23:31 +10:00
David Sevilla Martin
2c5aa138cd Bringing back those "use strict";. Sorry 'bout that 2016-07-28 10:47:09 -04:00
David Sevilla Martin
53fd7b66b4 Commiting dist/app.js 2016-07-26 11:34:47 -04:00
David Sevilla Martin
a1a22aa4ce Refresh notifications with discussion list refresh
* When clicking "refresh" button for discussion list (on homepage) refresh notifications
* When clicking forum title (on homepage) refresh notifications
2016-07-26 11:25:05 -04:00
David Sevilla Martin
cde5d20c4c Handle word wrapping properly in posts
* Added overflow-wrap break-word to .Post-body
* Added overflow-wrap normal to pre in .Post-body for code blocks (may change)
2016-07-24 12:16:00 -04:00
Niels Tholenaar
05c9ce335e Fixes #825 2016-06-20 12:34:41 +02:00
Franz Liedke
08aaba6426 Recompile dist file 2016-06-14 23:09:02 +09:00
Franz Liedke
2819fd63aa Remove unused import 2016-06-13 21:09:16 +09:00
Franz Liedke
cc23430a9e Make StyleCI happy 2016-06-13 21:08:17 +09:00
Franz Liedke
1a2174d614 Log exceptions in error handler middleware 2016-06-12 17:22:28 +09:00
Toby Zerner
85bd82eab1 Fix updater 2016-06-05 15:07:15 +09:30
Toby Zerner
d06a834238 Give posts with likes/replies a little more bottom padding 2016-06-05 12:10:22 +09:30
Toby Zerner
32aa3f0cba Clean up unnecessary alias 2016-06-05 09:53:23 +09:30
Toby Zerner
998bb5708e Display header HTML in layout rather than app wrapper
This is so a custom layout can opt-out of displaying header HTML (e.g. embed extension)
2016-06-05 09:53:08 +09:30
Toby Zerner
5f7291db39 Actually test IlluminateValidationExceptionHandler 2016-06-05 09:25:47 +09:30
Toby Zerner
f5988bae23 Distinguish between attributes/relationships in ValidationException
This exception could be a candidate for inclusion in tobscure/json-api...
2016-06-05 09:25:26 +09:30
Toby Zerner
0b3cc0c18f Make alert control color consistent on focus 2016-06-05 09:23:36 +09:30
Toby Zerner
6db27dff4f Move phpunit.xml into root to make PHPUnit easier to run
This seems to be pretty standard. Can just run `vendor/bin/phpunit` without any arguments. Removes the need for `composer test` (which is not ideal anyway as it removes colours from the output).
2016-06-05 09:16:29 +09:30
Toby Zerner
58d7be95c1 Increase muted text contrast 2016-06-04 18:10:22 +09:30
Toby Zerner
feffe53a86 Add ability to upload a logo + favicon, and add custom header HTML
Closes #268. Not going to bother with a preview SVG or anything fancy for now – we can think about that as part of #746. Right now it's just good to finally get this functionality in!

Also need to think about apple-touch-icon, msTile stuff, and social sharing image. Not sure if this is all too much for core, but it's definitely too much for the current Appearance page layout. Again, something to think about as part of #746.

Code is a bit rough around the edges, but figured there's not much point in using the command bus properly since #870.
2016-06-04 18:05:46 +09:30
Dominion
01a6dccb83 Add newline to locale switch (#974)
- Added to match format of other sections and increase readability.
2016-06-04 13:43:47 +09:30
Toby Zerner
786c2fcfa5 Convert EditCustomCssModal into a SettingsModal 2016-06-03 14:51:51 +09:30
Toby Zerner
27556fea38 Increase text contrast in dark mode 2016-06-03 14:51:00 +09:30
Toby Zerner
8600d81a5e Recompile dist JS using latest version of babel 2016-06-03 10:57:42 +09:30
Toby Zerner
1ce6afaaeb Add option to write the config file to a different path 2016-06-03 10:55:50 +09:30
Toby Zerner
4bd05ee561 Fix up some references to old classes 2016-05-29 16:01:58 +09:30
Toby Zerner
8328c446b0 Use smaller FontAwesome shim repo instead of original
components/font-awesome is ~8 MB smaller than fortawesome/font-awesome because it excludes all examples/docs. Reducing dependency filesize will be important when we want to package up a .zip for distribution.
2016-05-29 10:21:37 +09:30
Toby Zerner
31997b8fdf Make Post component subclasses build on parent content
Extensions may wish to add attributes/content to all posts, regardless of type, by extending methods on the Post component. Now the subclasses will not overwrite, but rather append to, these additions.
2016-05-28 09:44:44 +09:30
Toby Zerner
2d5a7ce064 Make discussion "hidden" state more explicit
Previously a discussion was classified on the front-end as "hidden" if it had zero posts. This was technically a correct statement as the discussion would not be visible to the public... but it also meant that a discussion with zero posts (like one awaiting approval) was impossible for the OP to delete/hide (i.e. indicate that they made a mistake and they don't want the discussion to be approved).
2016-05-28 09:43:21 +09:30
Toby Zerner
a380424de4 Remove space characters from in-between list items
They were causing some weirdness with spacing between redraws.
2016-05-28 09:38:58 +09:30
Toby Zerner
c3dfa3560a Allow extensions to add default model attributes
Extensions can add default column values in their migrations, but Eloquent doesn't know about this when it first saves a model to the database.

This is useful in flarum-ext-approval where the default value for is_approved on the posts table is true.
2016-05-28 09:37:43 +09:30
Toby Zerner
40a78d302e Fix permission logic priorities
This helps to fix a bug in flarum-ext-tags where a user could not rename or edit the tags of their own discussion if it was in a restricted tag. This was due to the order of GetPermission event listeners – the logic that determines that a user *can't* perform an action because of a restrictive tag was running before (and thus instead of) the logic that determines that a user *can* edit their own stuff.

The solution is to change the "catch-all" methods on Policies to "after" instead of "before" – that is, they will run only if the per-ability methods return null.

We also simplify the GetPermission event by passing the model as a sole "argument", as I can't imagine any cases where we'll need more than one argument.
2016-05-28 09:35:08 +09:30
Toby Zerner
7c0a72047a Make sure deprecated ConfigureClientView event still works 2016-05-27 14:57:27 +09:30
Toby Zerner
15adfc528f Fix installer/updater 2016-05-27 14:53:22 +09:30
Toby Zerner
be08c32c96 Simplify deleted post toggle CSS
(The animation was buggy anyway)
2016-05-27 13:56:56 +09:30
Toby Zerner
a9199ad9d9 Only check for reply permission for actual replies. fixes #917 2016-05-27 13:56:04 +09:30
Toby Zerner
fd44db407c Fix notifications dropdown not showing 2016-05-27 13:53:50 +09:30
Toby Zerner
240aa9e83b Improve permissions page
- Introduce the concept of "required permissions" - basically a permission dependency tree. In order for a group to be granted one permission, they must also have another.
- Improve redraw performance by not building dropdown menu contents until dropdown is opened

ref #904
2016-05-27 12:42:19 +09:30
Toby Zerner
1177880483 Give PostEdited default cursor; make info slightly more compact 2016-05-27 12:39:15 +09:30
Toby Zerner
607b3a66ae Fix PostEdited tooltip not updating 2016-05-27 12:38:28 +09:30
Toby Zerner
96eda5cfeb Fix detection of whether or not an asset file is "empty"
We can't rely on files/strings for this, since the Locale JsCompiler doesn't use either, but still has content.
2016-05-27 12:20:14 +09:30
Franz Liedke
0b0c1055d6 Make StyleCI happy and fix some docblocks 2016-05-27 09:07:49 +09:00
Davis
2b9ec71a81 Trim spaces in getPlainContent, prevent images from loading
fixes #834 closes #963
2016-05-27 07:34:57 +09:30
Toby Zerner
6aa017659f Recompile dist JS 2016-05-27 07:32:36 +09:30
Davis
f0f668fb93 Fix Permission Name (#965) 2016-05-26 23:54:25 +09:00
Toby Zerner
b322cf669a Bring post controls dropdown in front of the composer again
The underlying problem is fixed in flarum-ext-mentions.
2016-05-26 22:25:39 +09:30
Toby Zerner
8e99059f62 Don't write/serve empty asset files
The new locale-specific CSS file doesn't have any content by default, so it's a waste to write it and serve it to the user.
2016-05-26 22:24:56 +09:30
Toby Zerner
1b7a0ecb33 Rename Server register API to extend
More consistent with how extensions work
2016-05-26 19:12:32 +09:30
Toby Zerner
9bfb797fdc Refactor the web app bootstrapping code
- All custom JS variables are now preloaded into the `app.data` object, rather than directly on the `app` object. This means that admin settings are available in `app.data.settings` rather than `app.settings`, etc.
- Cleaner route handler generation
- Renamed ConfigureClientView to ConfigureWebApp, though the former still exists and is deprecated
- Partial fix for #881 (strips ?nojs=1 from URL if possible, so that refreshing will attempt to load JS version again)
2016-05-26 19:04:24 +09:30
Franz Liedke
2525e3e7ad Recompile dist JS 2016-05-23 22:54:48 +09:00
Dominion
a14562b100 Pluralize Turkish (#967)
- Changes the pluralization category of Turkish.
- See: https://discuss.flarum.org/d/2937-pluralization-problems
2016-05-23 22:53:33 +09:00
Franz Liedke
d3606b7f7e Update TextFormatter to next release
This fixes some minor issues with emojis working correctly in the dropdown,
but not when rendered in the live preview or from the server.
2016-05-23 22:48:59 +09:00
Dominion
ac096926af Clean up translation keys (#964)
- Corrects the suffix on a key in the Change Email modal.
- Extracts the title of the Edit User modal.
2016-05-21 22:03:57 +09:30
Davis
909f52522b Show post IP address in meta dropdown. closes #956 closes #657 2016-05-21 22:02:42 +09:30
Toby Zerner
38c15c5a08 Correct order of compiled JavaScript modules 2016-05-21 20:32:06 +09:30
Toby Zerner
1f5764e5e3 Add API to allow skeleton to customise the Application instance 2016-05-21 20:31:42 +09:30
Toby Zerner
28f72d5648 Fix crash on reset password page 2016-05-21 20:29:38 +09:30
Toby Zerner
9f69b7b846 Fix wonky avatar display when expanding a deleted post 2016-05-21 20:29:04 +09:30
Toby Zerner
c05f8b732d Move details from post edited indicator back into a tooltip
Showing the username and time of edit is TMI (too much information). This commit changes the visible text to "Edited", and shows the full edit information in a tooltip.

ref #446
2016-05-21 20:27:56 +09:30
Toby Zerner
58c9a6164a Automatically support basic HTML tags in translations
This allows front-end translations to use basic (attributeless) HTML tags freely, without the need for the translator call to supply a matching vdom element. Translations can thus make use of styling (<em>, <code>, etc.) as they see fit. The translator call can still optionally supply a vdom element to substitute in more complex tags where necessary (e.g. hyperlinks).

/cc @dcsjapan
2016-05-21 20:25:32 +09:30
Franz Liedke
60e50713e7 Customize behavior for various navigation callbacks, pass KeyboardEvent instance to callback
Refs #264.
2016-05-15 23:09:59 +09:00
Franz Liedke
513c586be3 Make key callback registration more concise
Refs #264.
2016-05-15 22:58:41 +09:00
Franz Liedke
9f8c2ed458 First shot at extracting keyboard navigation code to separate util class
Refs #264.
2016-05-15 22:34:16 +09:00
Franz Liedke
0cc75be55e Add a heading to the admin mail page 2016-05-15 20:46:58 +09:00
Franz Liedke
fa7871cc16 Admin menu: Move email tab closer to the top 2016-05-15 20:31:23 +09:00
Franz Liedke
a884a3592b Tweak structure of mail settings page
Refs #258 and #933.
2016-05-15 20:28:05 +09:00
Franz Liedke
9637d27b56 Fix translation keys 2016-05-15 12:08:11 +09:00
Franz Liedke
3866e518fa Fix post controls not being clickable in some circumstances
Closes #908.
2016-05-14 18:07:43 +09:00
Franz Liedke
9d2d302f2d Cleanup code (unused import, variable name) 2016-05-14 17:19:01 +09:00
David Sevilla Martín
99dbea4524 Replace pencil & tooltip w/ just text; Fixes #446 (#954) 2016-05-14 17:06:02 +09:00
Niels Tholenaar
77837ef7d1 Fixed LESS compile error (#958) 2016-05-13 23:15:33 +09:00
Franz Liedke
3f9fe7d33e Fix font-awesome LESS source not being found in some cases 2016-05-13 23:04:14 +09:00
Franz Liedke
dd0dc44dd8 Merge branch 'pr/933'
Closes #258.
2016-05-13 00:25:27 +09:00
Franz Liedke
18ee8578e8 Remove unused import 2016-05-13 00:25:11 +09:00
Franz Liedke
7256122a43 Compile dist JS for 3c6429a 2016-05-12 23:58:09 +09:00
Franz Liedke
696f562b0e Merge branch 'pr/936'
Closes #814.
2016-05-12 23:56:47 +09:00
Franz Liedke
aca497e7be Final tweaks to inline code styling
Refs #814.
2016-05-12 23:56:12 +09:00
Maxim Chistyakov
bc34b858a2 SQL Performance tuning (#952)
MySQL has problems with executing this subquery efficiently.
2016-05-12 23:07:41 +09:00
Franz Liedke
251862222c Add a comment 2016-05-12 23:03:37 +09:00
Lyntor Paul Figueroa
006ea02227 Fix avatar upload on Windows servers - Issue #893 (#927)
* Fix avatar upload on Windows servers - Issue #893

* Remove @ to show errors if any
2016-05-12 23:01:51 +09:00
Augustus D'Souza
3c6429aba8 Fixes #945 - Incorrect Scrubber count value (#946)
* Fixes #945 - Incorrect Scrubber count value

Clicking and dragging the Scrubber beyond the final post causes the counter to exceed the total post count. This commit fixes that issue.

* Updated post calculating logic
2016-05-10 10:52:20 +09:00
Franz Liedke
619561cf56 Install FontAwesome via Composer.
This also updates the asset publishing and LESS paths accordingly.

Refs #891.
2016-05-09 21:28:38 +09:00
David Sevilla Martín
805f86b249 Added variables for code color & background; Dark mode & light mode 2016-05-08 17:08:32 -04:00
Franz Liedke
eba782d48f Merge pull request #943 from poush/fix_#937
Validation on password change
2016-04-29 20:27:53 +09:00
JoshyPHP
6d809cb023 Updated s9e\TextFormatter to 0.5.0 (#947) 2016-04-29 15:11:30 +09:30
Piyush Agrawal
77a5b59a10 Validation on password change 2016-04-28 15:48:25 +05:30
Toby Zerner
7192c4391b Fix console installer not working
Some commands have dependencies which causes errors when there's no config/database access, so they shouldn't be instantiated.
2016-04-25 09:17:11 +09:30
Toby Zerner
3d812c287f Lazily initialise the Application instance
See f403feb3b1
2016-04-24 11:10:39 +09:30
Toby Zerner
7bd3fa82b1 Allow setting storage path, clean up docblocks 2016-04-24 11:00:25 +09:30
David Sevilla Martín
6b108d99cb Changed "Advanced" to "Mail"
+ Added labels above inputs
-  Removed Placeholders
2016-04-23 09:19:02 -04:00
Buhnici Alexandru
777579e146 Public and base directory can be separated (#938)
* Public and base directory can be separated

* Standards compliance for folders separation implementation
2016-04-23 11:55:53 +09:30
Toby Zerner
d8b043dacb Damn you chrome update 2016-04-20 20:53:15 +09:30
Toby Zerner
645a908dff Fix more avatar positioning regressions 2016-04-20 20:44:57 +09:30
Toby Zerner
bf79383204 Fix incorrect avatar position if online indicator is present 2016-04-20 20:42:14 +09:30
Toby Zerner
db53103396 oops 2016-04-20 20:25:11 +09:30
Toby Zerner
b5a9d0183e Fix avatars not displaying in Chrome 50 2016-04-20 20:21:32 +09:30
David Sevilla Martín
8065dc1806 Updated code color from blue-ish to more grey-ish 2016-04-18 21:26:32 -04:00
Franz Liedke
694f5ad2e8 Update Whoops middleware 2016-04-18 10:55:40 +09:00
David Sevilla Martín
821bce38be Updated code style to be same as Scotch.IO's 2016-04-17 19:01:19 -04:00
David Sevilla Martín
45045a2ac1 Added "Advanced" page on admin & SMTP settings
Refs #258
2016-04-17 09:54:41 -04:00
Franz Liedke
3000ec695d Add a tooltip to the avatar icon
Refs #249.
2016-04-17 22:34:03 +09:00
Franz Liedke
323339190c Use a slightly different icon for suggesting upload functionality
Refs #249.
2016-04-17 22:24:09 +09:00
Franz Liedke
5f60297eb1 Merge pull request #934 from datitisev/249-avatar-editor-big-upload-button
#249 AvatarEditor should show an upload icon instead of a user's default avatar
2016-04-17 22:13:26 +09:00
David Sevilla Martín
af5f47bb90 If no avatar uploaded user will see the upload button directly on "avatar" without full opacity
Refs #249
2016-04-16 13:09:09 -04:00
Franz Liedke
02b110e545 Implement a server class that composes the other servers
Useful for local development using PHP-PM. :)
2016-04-16 23:19:10 +09:00
Franz Liedke
f177c0d8a0 Fix doc block 2016-04-16 14:44:48 +09:00
Franz Liedke
a12b5591c3 srsly?
StyleCI was complaining.
2016-04-16 14:43:52 +09:00
Franz Liedke
5293117c80 Http\AbstractServer: Use middleware functionality when listening 2016-04-16 13:11:33 +09:00
Franz Liedke
181c19eac7 Http\AbstractServer: Allow usage as PSR-7 middleware 2016-04-16 13:10:11 +09:00
Franz Liedke
f403feb3b1 AbstractServer: Store app instance as class property
This will make it easier to reuse the instance in an asynchronous
setting (e.g. ReactPHP), where one application instance is preloaded
and reused for each incoming request.
2016-04-16 13:07:52 +09:00
Franz Liedke
b5fc7b9bf5 AbstractServer: Allow omitting base path parameter
It will be inferred from the current directory instead.
2016-04-16 13:06:58 +09:00
Franz Liedke
cd16adfa69 Remove unused imports 2016-04-14 23:22:33 +09:00
Toby Zerner
e11401b551 Fix some random docblocks 2016-04-08 13:52:50 +09:30
Toby Zerner
c9112624c0 Tweak post composer alignment with page content 2016-04-08 13:52:30 +09:30
Toby Zerner
603537c3d1 Scroll to reply preview initially 2016-04-08 13:51:23 +09:30
Toby Zerner
0f975da403 Don't show username/email fields when editing own account. fixes #903 2016-04-08 13:40:37 +09:30
Toby Zerner
66a39bbbf5 Fix buttons being given incorrect title 2016-04-08 13:38:50 +09:30
Toby Zerner
6dd190114d Retain global page components between routes 2016-04-08 13:38:16 +09:30
Toby Zerner
01c0cf443b Merge pull request #921 from sijad/update-js
Update app.js
2016-04-08 06:11:52 +09:30
Sajjad Hasehmian
ffaafb92d4 Update app.js 2016-04-07 22:59:01 +04:30
Toby Zerner
8673a0bc2d Merge pull request #919 from dcsjapan/fix-deleted-username
Fix translation key for deleted username
2016-04-07 21:29:03 +09:30
dcsjapan
b068536dbd Fix translation key for deleted username 2016-04-07 20:50:35 +09:00
Toby Zerner
4dc9e7741c Merge pull request #918 from dcsjapan/extract-views
Extract translations from core blade files.
2016-04-07 19:36:30 +09:30
dcsjapan
6d5582e4ac Extract translations from core blade files.
- Extracts translations from `reset.blade.php`.
- Adjusts namespacing of translations in other files.
- Fixes one direct reference to a `core.ref` key.
2016-04-07 17:45:58 +09:00
Franz Liedke
e30f8f261b Use existing ClientController classes to remove compiled assets
Refs #837.
2016-04-04 21:00:04 +09:00
Franz Liedke
729103c519 Move cache:clear command to other namespace and actually flush the cache, too
Refs #837.
2016-04-03 22:22:29 +09:00
Franz Liedke
ee8f4f04de We don't need to make the cache adapter configurable like this 2016-04-03 22:21:41 +09:00
Franz Liedke
8e35afe204 First basic version of cache:clear command
Refs #837.
2016-04-02 21:23:32 +09:00
Franz Liedke
37d7f315d3 Fix locale key 2016-04-01 10:49:11 +09:00
Toby Zerner
b799039c29 Fix commas in new DiscussionRenamedPost tooltip. ref #428 2016-04-01 12:02:51 +10:30
Franz Liedke
b74ca9979f Remove old styling for DiscussionRenamedPost
New title should now appear as bold.

Refs #428.
2016-04-01 10:08:22 +09:00
Franz Liedke
895281acb2 DiscussionRenamedPost: Always show new title, show old title in tooltip
Refs #428.
2016-04-01 10:07:34 +09:00
Franz Liedke
42c9086c32 Improve Button component to only show tooltip if textual content is available 2016-04-01 09:51:18 +09:00
David Sevilla Martín
1fbce0db33 Add validation to forgot password email field. closes #776 2016-04-01 09:20:26 +10:30
Toby Zerner
bd50a23966 Update json-api dependency 2016-03-31 20:02:42 +10:30
Toby Zerner
210bbc800a Fix settings not automatically showing when an extension is enabled 2016-03-31 17:11:23 +10:30
Franz Liedke
f97ebfcbc0 Remove leftover function 2016-03-31 15:12:07 +09:00
Franz Liedke
9e79470603 Update fig-cookies dependency and use new shortcut 2016-03-31 09:43:17 +09:00
Franz Liedke
328a244f92 Fix DiscussionRenamedPost not being redrawn when toggling
Closes #428.
2016-03-31 09:35:19 +09:00
Franz Liedke
d6c6e78193 Make DiscussionRenamedPost spread across three lines
This also adds a little button for expanding / collapsing this additional information.
It is expanded by default right now because I could not get the toggling to work yet.

Refs #428.
2016-03-30 22:13:08 +09:00
Franz Liedke
bc11ec68dd Extract EventPost description into separate method
This makes it easier to override in subclasses.
In preparation for #428.
2016-03-30 20:41:15 +09:00
Franz Liedke
9ae189bb9f Only update human time objects every ten seconds 2016-03-30 19:47:40 +09:00
Franz Liedke
179fcfb3ca Show full button content as tooltip
Refs #494.
2016-03-30 10:04:08 +09:00
Franz Liedke
51da153592 extractText: Avoid unnecessary variable 2016-03-30 09:59:54 +09:00
Franz Liedke
25d18d79fb Revert "Use GroupBadge component since we already have that"
This reverts commit eb76767e70.
2016-03-29 22:46:23 +09:00
Franz Liedke
eb76767e70 Use GroupBadge component since we already have that 2016-03-29 22:39:26 +09:00
Franz Liedke
98c4883cfd Remove unused mixin imports 2016-03-29 22:32:15 +09:00
Franz Liedke
dbbbc689bb Prevent humanTime helper to generate future times
Fixes #592.
2016-03-29 22:23:11 +09:00
Franz Liedke
16b229649a Use group ID instead of name in generated class names
This fixes #847.
2016-03-29 22:08:44 +09:00
Toby Zerner
d1c25a4bad Fix regression with full-screen composer being obscured by header/side pane
This is not ideal as dropdowns appear above the header, but it will probably be resolved when we redo the composer's full screen mode soon enough.
2016-03-29 18:24:23 +10:30
Toby Zerner
4b2f0c2d1a v0.1.0-beta.5 2016-03-29 18:02:12 +10:30
Toby Zerner
48be5ac2eb Prevent unapproved discussions from dropping to the bottom of the discussion list 2016-03-29 17:53:07 +10:30
Toby Zerner
0b3a4264a3 Use more precise regex to prevent some translations being compiled unnecessarily 2016-03-29 17:31:13 +10:30
Toby Zerner
76ea6f3695 Clean up unused code 2016-03-28 15:46:52 +10:30
Toby Zerner
7120ba2050 Add specific error message when an email address is not found in forgot password modal 2016-03-28 15:46:20 +10:30
Toby Zerner
ff77912dc6 Reconfigure z-index hierarchy: show dropdowns above post composer 2016-03-28 13:32:23 +10:30
Toby Zerner
53b32eda12 Tweak badge shadow radius 2016-03-28 10:25:47 +10:30
Toby Zerner
6d69e90662 Prevent long forum title in mobile drawer from entering viewport 2016-03-28 10:23:58 +10:30
Toby Zerner
589e903c71 Fix search box overlapping forum title in some cases. closes #697
- Fix jank in shrinking animation when search box loses focus after overlapping forum title.
- Use solid colors instead of transparent whites/blacks for colored header controls so that search box isn't transparent when it does overlap forum title.
- This also simplifies colored header variables, making them more analogous to the non-colored header variables, and allowing for the removal of some conditional CSS in the notifications dropdown button.

Some more radical changes to header layout (flexbox?) may be made when we implement the new mobile design (#867), but for now this is an acceptable fix.
2016-03-28 10:23:49 +10:30
Franz Liedke
4fe7acfddf Revert "Add a middleware for authentication with CGI wrap"
This reverts commit 685d5f1517.

This will now be dealt with at the Stratigility level.
2016-03-26 18:56:31 +09:00
Franz Liedke
685d5f1517 Add a middleware for authentication with CGI wrap
If the authorization header is stripped by CGI wrap,
the server can be configured to send the value along
in an environment variable. If the server admin sticks
to this convention, Flarum can now use this variable.

This is supposed to take care of #384.
2016-03-24 21:53:11 +09:00
Toby Zerner
a5c8ef0566 Tweak user email confirmation alert
- Make sure is_activated is serialized to a bool (otherwise "0" will evaluate to true)
- Remove "error" class from message so it's more friendly
- Make the alert more prominent by mounting it into a new div at the top of the page
- Add loading UX to the resend button
2016-03-23 22:17:42 +10:30
Franz Liedke
cb428f1e4a Make StyleCI happy 2016-03-23 19:54:04 +09:00
Toby Zerner
3d11309b35 Merge pull request #862 from sijad/confirm-msg
Show alert for unverified User
2016-03-23 21:19:02 +10:30
Sajjad Hasehmian
b13adfec84 Show alert for unverified User 2016-03-22 18:52:32 +04:30
Franz Liedke
b2b5789c25 info: Show commit hashes for Flarum core and extensions 2016-03-22 00:55:10 +09:00
Franz Liedke
673a78a203 info: Show loaded PHP extensions 2016-03-22 00:33:39 +09:00
Franz Liedke
31caced04c info: Show installation path 2016-03-22 00:29:58 +09:00
Franz Liedke
5d88ad2431 info: Show base URL 2016-03-22 00:28:02 +09:00
Franz Liedke
96a40fd6ea info: Print PHP version, too 2016-03-22 00:22:40 +09:00
Franz Liedke
77086c9be6 Travis: Do not run PhpUnit through Composer
We need to run PhpUnit with xDebug enabled in order to collect
code coverage information. It is disabled for performance reasons,
though: https://github.com/travis-ci/travis-ci/issues/5780.
2016-03-21 23:12:09 +09:00
Franz Liedke
3c629f091d Travis: Generate code coverage report when running tests 2016-03-21 20:21:51 +09:00
Toby Zerner
820752f61c Oops, back to Mithril 0.2.3! 2016-03-21 21:25:00 +10:30
Toby Zerner
67f3a4a5bf Merge pull request #844 from Luceos/codecov
added integration with codecov to track coverage of tests
2016-03-21 20:19:15 +10:30
Franz Liedke
cd4d669127 Make console command descriptions consistent 2016-03-20 23:16:08 +09:00
Franz Liedke
238f2fca73 Get rid of some repetition 2016-03-20 23:15:26 +09:00
Franz Liedke
7e33690660 Add first, basic version of info command
This will hopefully help in debugging some problems.
2016-03-20 23:12:20 +09:00
Franz Liedke
eef895c16f Composer: Sort dependencies alphabetically 2016-03-20 22:27:43 +09:00
Franz Liedke
2be964f8e2 Update to latest version of text-formatter 2016-03-20 22:25:45 +09:00
Toby Zerner
2f05a2d80b Merge pull request #882 from flarum/analysis-8PxnZR
Applied fixes from StyleCI
2016-03-20 20:37:03 +10:30
Toby Zerner
e6a001335d Applied fixes from StyleCI 2016-03-20 06:06:43 -04:00
Franz Liedke
4c03f13fef AbstractOAuth2Controller: Store provider and token in class properties
This way, they are available for subclasses to access them in one of
the template methods.

Refs #673.
2016-03-18 22:22:35 +09:00
Franz Liedke
588dd7b213 Fix JSON serialization error on PHP 7
Closes #685.

Thanks to @sijad.
2016-03-18 21:11:54 +09:00
Toby Zerner
1ca1639139 Extract sortMap variable
Also revert previous uncommitted change in dist file
2016-03-18 10:06:58 +10:30
Toby Zerner
476c1a5691 Prevent users from being incorrectly able to delete their own discussions 2016-03-18 09:39:41 +10:30
Toby Zerner
3b19fe3a33 Lighten discussion list hover color
When the list is shown in the side-pane, the background of the currently-selected discussion is the @control-bg. The hover color shouldn't be quite as strong as that.
2016-03-18 09:38:37 +10:30
Toby Zerner
65f2d84d55 Fix "sort by" dropdown being empty
Must be something in the latest version of Chrome that caused this to start being a problem, because @franzliedke started experiencing it a few days ago, and I only just experienced it for the first time yesterday.
2016-03-18 09:37:25 +10:30
Toby Zerner
cf63e063ba Fix regression with maintenance of scroll position when jumping between discussion list filters 2016-03-18 09:36:09 +10:30
Toby Zerner
cd6e6addf7 Remove unmaintained changelog
We will look at reintroducing once out of beta.
2016-03-18 09:34:48 +10:30
Toby Zerner
1395ce6c30 Upgrade to flarum-gulp 0.2.0 / Babel 6 2016-03-18 09:31:01 +10:30
Franz Liedke
05732be929 Merge pull request #858 from sijad/update-mithril
Update Mithril
2016-03-16 10:11:05 +09:00
Sajjad Hasehmian
5097d7f9a4 Update Mithril 2016-03-16 00:48:01 +03:30
Toby Zerner
0b3bc9f2ba Increase avatar upload max file size 2016-03-14 09:25:02 +10:30
Toby Zerner
8087d9ea47 Add missing super.init calls 2016-03-11 13:45:38 +10:30
Toby Zerner
d1c436c4d5 Dramatically improve performance when typing in a modal
Since Mithril doesn't really offer granular redraw control, typing in a text input on a modal would trigger a redraw for the whole page (including the page content behind the modal) on every keystroke. This commit allows components to be "paused" so that their vdom subtree will be retained instead of reconstructed on subsequent redraws. When a modal is opened, we pause the main page component, and when it's closed, we unpause it. This means that while a modal is visible, only the content inside of the modal will be redrawn, dramatically improving performance.
2016-03-11 13:18:16 +10:30
Toby Zerner
e37c7a9b06 Remove sudo mode and add password confirmation when changing email address
closes #674
2016-03-11 12:44:18 +10:30
Toby Zerner
dc757fae5f Remove white border from badges, decrease overlap 2016-03-11 12:01:47 +10:30
Toby Zerner
3b236dd66e Add padding between items in fieldsets on the settings page 2016-03-10 17:56:18 +10:30
Toby Zerner
e2e5ac8c0c Fix browser back button losing scroll position. ref #835 2016-03-10 17:55:35 +10:30
Toby Zerner
beb2f91fef Fix posts being incorrectly visible on user page. closes #680
- When no discussions are visible, the query that filters posts by discussion visibility was incorrectly making all posts visible.
- Also hide user profiles altogether if discussions are not visible.
2016-03-10 17:50:29 +10:30
Toby Zerner
2391471937 Clean up linting stuff. closes #852 2016-03-10 17:13:30 +10:30
Franz Liedke
f631b98df6 Whoopsie, fix syntax error 2016-03-08 00:05:53 +09:00
Franz Liedke
01cb5c4478 Add another migration shortcut for defining default settings 2016-03-08 00:02:33 +09:00
Toby Zerner
fc517ca94d Merge pull request #846 from sijad/extension-path
Remove 'extensions' path for writable check
2016-03-04 17:04:01 +10:30
Sajjad Hasehmian
393fa67d2d Remove 'extensions' path for writable check 2016-03-04 09:55:40 +03:30
Daniel Klabbers
cb6ac9e9e2 added integration with codecov to track coverage of tests 2016-03-03 11:50:09 +01:00
Toby Zerner
7d2f24bb47 Merge pull request #843 from Luceos/add-tests
adding new tests to cover api handlers
2016-03-03 20:53:15 +10:30
Daniel Klabbers
5a7b57df96 adding new tests to cover api handlers, part 1 of #245 and #74 2016-03-03 11:00:11 +01:00
Toby Zerner
a75a76e95b Fix fatal error when deleting a discussion forever. closes #842 2016-03-03 12:52:53 +10:30
Toby Zerner
639f5c0114 Merge pull request #841 from Luceos/drop-ext-dir
Refactoring to drop extensions dir, see #774
2016-03-02 18:46:08 +10:30
Daniel Klabbers
15c0a8c2db Refactoring to drop extensions dir, see #774
satisfy nitpick
2016-03-02 09:04:10 +01:00
Toby Zerner
1b5b91c85b Merge pull request #840 from flarum/analysis-z4xEVE
Applied fixes from StyleCI
2016-03-01 14:21:22 +10:30
Toby Zerner
5d5f47aab2 Applied fixes from StyleCI 2016-02-29 22:51:13 -05:00
Toby Zerner
24713733fc Don't require a previous Post when saving event posts
A bit of an edge-case since it shouldn't really be possible to have a discussion with zero posts anymore, but when renaming an empty discussion (or taking any action that will create an "event post"), Flarum would crash. This is due to the MergeableInterface requiring these posts to be saved after a previous post.
2016-02-29 18:50:27 +10:30
Toby Zerner
56b39f9fba Fix crash when sending notification to non-existent user
When renaming a discussion, an attempt is made to send a notification to the discussion's author. However, there is no check to see if the user account still exists - this can lead to a crash. While the check should technically be in the initiating code, it will probably slip through the cracks in other scenarios/extensions, so it's probably best that we safe-guard against this in the NotificationSyncer itself.
2016-02-29 18:48:02 +10:30
Toby Zerner
cdbc4b9717 Fix regressions related to deleting posts
- On the front-end, correct the check to see if the discussion has no more posts
- On the back-end, run a query to count the posts instead of using the comments_count, because the comments_count does not include other deleted posts
2016-02-29 18:41:59 +10:30
Franz Liedke
594a2ba8cc More indentation cleanup 2016-02-26 13:10:32 +09:00
Toby Zerner
445517ee84 Use regex for username validation
Laravel's alpha_dash rule allows unicode letters including those with inflections, leading to issues like #832. As per discussion in #557, we are sticking with ASCII-only usernames for now.
2016-02-26 13:59:05 +10:30
Franz Liedke
b4cf197cc6 Improve alignment of string 2016-02-26 12:20:37 +09:00
Toby Zerner
102db3c913 Simplify StyleCI config 2016-02-26 13:47:17 +10:30
Toby Zerner
0ccfad3931 Merge pull request #831 from flarum/analysis-qvQMPx
Applied fixes from StyleCI
2016-02-26 13:40:39 +10:30
Toby Zerner
a6cf10f854 Applied fixes from StyleCI 2016-02-25 22:09:39 -05:00
Toby Zerner
83c22d73a4 Fix StyleCI misconfiguration error
> The provided fixer 'unalign_double_arrow' cannot be disabled unless it was already enabled by your preset.
2016-02-26 13:36:19 +10:30
Toby Zerner
952b4693da Add StyleCI config 2016-02-26 13:35:09 +10:30
Toby Zerner
c7b6426fd4 Delete a discussion when its last post is deleted. fixes #823 2016-02-26 13:26:09 +10:30
Toby Zerner
acdb1ff749 Revert #687 + #197. fixes #785
Unfortunately we have no way to calculate the number of comment posts that are previous to the current viewing position of the discussion, without loading all of the posts which is going to be too expensive (even if we do it selectively somehow).
2016-02-26 13:11:52 +10:30
Toby Zerner
50e56ac0a1 Recompile admin JS 2016-02-26 12:50:03 +10:30
Toby Zerner
82fc4dd483 Refactor Composer rendering for smoother animations
Also fixes a couple of miscellaneous bugs:
- Minimise the Composer when clicking the preview button in full-screen mode on desktop.
- Minimise the Composer when clicking the link to the discussion/post in the header on mobile/full-screen mode.
2016-02-26 12:49:49 +10:30
Franz Liedke
5390187a4f Just a tad of cleanup 2016-02-25 23:29:55 +09:00
Daniel Klabbers
e4412178b1 refactoring to support array closures migrations and fixed issues with previous pr for extension rewriting 2016-02-25 23:26:10 +09:00
Franz Liedke
2b5dab73f9 Use the new migration shortcuts in most of core's migrations 2016-02-25 00:50:54 +09:00
Franz Liedke
db7a03fbe5 Add some handy shortcuts for typical migration tasks
This will make it much easier for extension developers (and also less
error-prone) to create migrations for things like creating tables,
renaming columns and so on...
2016-02-25 00:50:03 +09:00
Franz Liedke
ad95a44e7d Remove obsolete AbstractMigration class 2016-02-24 23:22:52 +09:00
Franz Liedke
59613910b1 Update generate:migration command to deal with new migration structure 2016-02-24 23:20:33 +09:00
Franz Liedke
13fe162db3 Add two missing copyright headers 2016-02-24 22:25:09 +09:00
Franz Liedke
51955504aa Revamp migration structure
They are now simply files that return an array of closures, for
running the named "up" and "down" actions, respectively.

Related to #732.
2016-02-24 22:23:49 +09:00
Toby Zerner
05fe4446bf Fix crash when displaying a discussion with no posts. closes #823 2016-02-22 22:22:49 +10:30
Toby Zerner
71d2e71908 Condense into value/oninput into bidi 2016-02-22 21:22:18 +10:30
Toby Zerner
93f3f22623 Merge pull request #811 from sijad/firefox-fix
Fix login box autocomplete in firefox
2016-02-22 21:09:18 +10:30
Franz Liedke
ff69dade15 Merge pull request #817 from flarum/revert-813-typehint
Revert "typehint fix, opening for implementation"
2016-02-18 17:35:17 +01:00
Franz Liedke
17851c4dfe Revert "typehint fix, opening for implementation" 2016-02-18 17:33:34 +01:00
Franz Liedke
46dfdf2deb Merge pull request #813 from Luceos/typehint
typehint fix, opening for implementation
2016-02-17 16:12:15 +01:00
Daniel Klabbers
d944a9e618 typehint fix, opening for implementation 2016-02-17 13:34:13 +01:00
Sajjad Hasehmian
2143a96c19 Fix login box autocomplete 2016-02-16 21:08:45 +03:30
Toby Zerner
d7fe3ca35b Merge pull request #787 from sijad/401-page
401 for unauthorised request to settings, notifications page
2016-02-15 21:04:39 +10:30
Daniël Klabbers
48e29ed168 Merge pull request #801 from Luceos/extension_fix
Extension fix
2016-02-14 22:18:08 +01:00
Daniel Klabbers
0ad4c0ac61 fixes #800, forgot these controllers 2016-02-13 20:33:33 +01:00
Daniel Klabbers
458f4f811c fixes #799, now properly assigning a id 2016-02-13 20:32:46 +01:00
Sajjad Hasehmian
e90dfe04fd 401 for unauthorised request to settings, notifications page fixes #714 2016-02-11 09:59:01 +03:30
Daniel Klabbers
191589e2b1 Implemented extensions as an object, usable by backend and frontend. 2016-02-10 15:13:51 +01:00
Franz Liedke
96c4e6b147 Merge pull request #786 from Luceos/imports
reordering and removing unused imports
2016-02-10 15:02:37 +01:00
Franz Liedke
d15a9dc0f0 Avoid use of model class in migration
See commit 0831256
2016-02-10 14:17:38 +01:00
Franz Liedke
08312568ba Installer: Fix models not being ready for use when running migrations
This was a regression after the recent introduction of a new migration that actually uses models.
Maybe we should change this.

See https://discuss.flarum.org/d/2023-can-t-manage-to-install-the-development-version-503-service-unavailable/8
2016-02-10 14:07:29 +01:00
Daniel Klabbers
31be2f8f86 reordering and removing unused imports 2016-02-10 11:00:37 +01:00
Toby Zerner
89598646c1 Merge pull request #784 from sijad/no-confirm
Remove "Mark as Read" confirmation fixes #782
2016-02-10 18:13:38 +10:30
Sajjad Hasehmian
b3035c18b6 Remove "Mark as Read" confirmation fixes #782 2016-02-10 10:50:24 +03:30
Toby Zerner
235c265c06 Merge pull request #779 from sijad/uri-fix
Correct redirect uri in OAuth2 Controller
2016-02-10 07:06:14 +10:30
Sajjad Hasehmian
f1a1a7a806 Correct redirect uri in OAuth2 Controller (fixes #778) 2016-02-09 18:01:59 +03:30
Toby Zerner
dfef3c1ff1 Slightly widen index sidebar, overflow buttons properly
First half of #349 fix. Supersedes #734 (190px wide instead of 200px, correctly modify margin-left of .sideNavOffset, more descriptive commit message)
2016-02-07 12:10:02 +10:30
Toby Zerner
fb09cef540 Merge pull request #748 from JoshyPHP/Minifiers
Added support for new minifiers
2016-02-07 11:37:15 +10:30
Toby Zerner
24ed2c0d8f Update Mithril 2016-02-06 18:58:34 +10:30
Toby Zerner
173f88da92 Better post scrubber size calculations. fixes #109 2016-02-06 18:47:09 +10:30
Franz Liedke
9ecb5f437a Use stored slug for generating server-rendered link to discussion
Fixes #646.
2016-02-04 11:47:03 +01:00
Franz Liedke
97979b2189 Store discussion slug in database table
In preparation for #646.
2016-02-04 11:46:30 +01:00
Toby Zerner
efff4c1801 Add priorities to user page sidebar items 2016-01-31 17:11:13 +10:30
Toby Zerner
2018e424ec Refactor ListPostsController, make filtering extensible
It became apparent in https://github.com/flarum/core/issues/319#issuecomment-170558573 that there was no way for extensions to add filter parameters to the /api/posts endpoint (e.g. /api/posts?filter[mentioned]=1). Simply adding an event to modify the `$where` array severely limits how much can be done with the query. This commit refactors the controller so that filters are applied directly to the query Builder, and exposes the Builder in a new `ConfigurePostsQuery` event.
2016-01-31 17:06:38 +10:30
Toby Zerner
36ad4a8554 Fix fatal error
"PHP Fatal error:  Cannot use Symfony\Component\Translation\Translator as Translator because the name is already in use"
2016-01-31 15:35:53 +10:30
Franz Liedke
3581fe8d1e No sudo 2016-01-28 08:06:33 +01:00
Franz Liedke
90ce0fa521 Travis: Make sure Composer is up-to-date. 2016-01-28 08:06:20 +01:00
Franz Liedke
63b5cd0812 Travis: Update Xdebug removal code 2016-01-28 07:59:04 +01:00
Franz Liedke
2a3240b9d1 Travis: Use pre-installed Composer
I also disabled the XDebug extension for the PHP runtime, which should
improve Composer runtime considerably. This is what Composer itself does.
2016-01-20 22:22:13 +01:00
Franz Liedke
e0790de2e5 Update extension skeleton
Closes #743.
2016-01-20 22:14:08 +01:00
Franz Liedke
c99c83435b Fix path to extension stub directory
Refs #743.
2016-01-20 22:01:01 +01:00
Franz Liedke
c8f2d94558 Fix obsolete import 2016-01-20 21:38:14 +01:00
Franz Liedke
c842fa0184 Hardcode primary keys during installation
This avoids misleading assumptions about automatically generated primary keys
in some cases.

Fixes #566.
2016-01-20 21:36:50 +01:00
Toby Zerner
ad2bbdd115 Tweak padding on user dropdown button so avatar is flush with border radius 2016-01-19 19:19:16 +10:30
Toby Zerner
db06b8c71a Fix mistake in previous commit 2016-01-19 19:07:06 +10:30
Toby Zerner
3cec7e8b46 Patch Mithril bug causing redraws to fail
Turns out there's a little more to the regression in e5a7013. First, we need to give the spaces in between list items a key too. Second, there's a bug in the latest Mithril code where using string keys can break the diffing algorithm. I've patched it manually in our dist JS files for now, and reported the issue: https://github.com/lhorie/mithril.js/issues/934
2016-01-19 18:55:57 +10:30
Toby Zerner
60d78cedef Update bower dependencies, fix redraw regression
- In Mithril, `finally` has been removed from promise objects as it is not part of the ES spec. See https://gist.github.com/jish/e9bcd75e391a2b21206b for info on the substitute.
- Fix a regression introduced in e5a7013 which broke some redraws
2016-01-19 17:59:19 +10:30
Toby Zerner
2980c94247 Add Composer branch-alias
This allows installations to require version 0.1.0 with minimum-stability=dev, and they will get the latest from master.

See #727
2016-01-19 17:00:10 +10:30
Toby Zerner
9b5ec9d7ba Commit latest dist files
See https://github.com/flarum/core/issues/727#issuecomment-172384020
2016-01-19 16:52:01 +10:30
Toby Zerner
f17f0b5278 Merge pull request #752 from dcsjapan/ext-instructions
Extract translations for the Add Extension modal
2016-01-19 12:20:54 +10:30
dcsjapan
be924c4fa0 Extract translations for the Add Extension modal
- Extracts three translations for this placeholder dialog.
- Adds a forum link to one of the translations.
2016-01-19 10:16:07 +09:00
Toby Zerner
285e397d05 Remove hack to make tag permissions work
Since we now grant these global permissions if the user has the respective permission for any individual tags.
2016-01-16 14:07:13 +10:30
Toby Zerner
2e27d5938a Merge branch 'master' of https://github.com/flarum/core 2016-01-16 13:57:17 +10:30
Toby Zerner
be013c6db0 Check permission through the gate rather than directly on the actor 2016-01-16 13:57:05 +10:30
Toby Zerner
dfc0cf53b0 Give GetPermission event priority when determining permissions 2016-01-16 13:56:37 +10:30
JoshyPHP
09ad4a180b Added support for new minifiers 2016-01-15 16:59:56 +01:00
Franz Liedke
194f304752 Merge pull request #720 from Albert221/permission-denied-fix
#719 Fixed PermissionDeniedException
2016-01-13 12:31:38 +01:00
Toby Zerner
aaab2cc86e Clear search when input is empty and enter is pressed. fixes #650 2016-01-13 10:06:04 +10:30
Toby Zerner
ba7fba9015 Fix/clean up created gambit
$matches indices were incorrect.
2016-01-13 10:03:26 +10:30
Toby Zerner
4ec108f28a Merge branch 'created-gambit' of https://github.com/Albert221/core 2016-01-13 09:53:24 +10:30
Toby Zerner
e5a7013c2c Key item lists to maintain identity across redraws
Fixes #667. This issue was due to the fact that Mithril would change the "Lock" badge into a "Sticky" badge, but the tooltip initialization would not be triggered because it was using the same element. By maintaining element identity, the "Lock" badge will remain untouched, and a new element for the "Sticky" badge will be inserted before it. See https://lhorie.github.io/mithril/mithril.html#dealing-with-focus for more information.
2016-01-13 09:34:12 +10:30
Toby Zerner
df2a199b48 Merge pull request #741 from Albert221/prefix-fix
UrlGenerator prefix fix.
2016-01-13 08:17:10 +10:30
Albert221
b123e435ff Unified two URL prefix variables into one 2016-01-12 22:07:47 +01:00
Albert
17da649d0a Merge pull request #2 from flarum/master
Update
2016-01-12 22:04:03 +01:00
Toby Zerner
1e33ca4111 Merge branch 'replay-animation' of https://github.com/sijad/core 2016-01-12 19:14:07 +10:30
Toby Zerner
8506d095db Use correct directory in loadLanguagePackFrom API 2016-01-12 18:35:37 +10:30
Toby Zerner
94a62293eb Extract Google font import to a head string, make overideable
Allowing headStrings to be named is a bit of a stopgap solution. Really ClientView needs to be given much more power with headStrings and footStrings as separate objects, similar to the ItemList in the JS app.
2016-01-12 18:29:21 +10:30
Sajjad Hasehmian
02bcb0f898 Add flash animation when scrolling to post preview fixes #666 🤘 2016-01-12 10:58:19 +03:30
Toby Zerner
98ea4d1e71 Merge pull request #735 from bogdanteodoru/master
#679 Ask for confirmation before "Mark all as Read"
2016-01-12 17:09:26 +10:30
Bogdan Teodoru
5120d9577e #679 Ask for confirmation before "Mark all as Read" 2016-01-12 08:23:02 +02:00
Franz Liedke
23eaee6b16 Merge pull request #731 from sijad/bio-nofollow
Add rel="nofollow" to bio links (fixes #449)
2016-01-11 11:09:52 +01:00
Sajjad Hasehmian
15398fcc6d Add rel="nofollow" to bio links (fixes #449) 2016-01-11 13:29:01 +03:30
Franz Liedke
bd1d05ee2c #717: Implement helper for registering a language pack 2016-01-11 08:46:20 +01:00
Franz Liedke
4a6137fdb1 Remove Studio hack 2016-01-11 08:38:30 +01:00
Franz Liedke
537ab6e41f Remove empty line 2016-01-11 08:15:14 +01:00
Franz Liedke
ace4bcf7d8 Merge pull request #730 from Luceos/remove_path_forum
removed patch from api routes, fixes #725
2016-01-11 08:14:18 +01:00
Daniel Klabbers
159810c335 removed patch from api routes, fixes #725 2016-01-11 08:09:01 +01:00
Franz Liedke
b7120fb176 Merge pull request #729 from bogdanteodoru/master
#679 Ask for confirmation before "Mark all as Read"
2016-01-10 20:58:54 +01:00
Bogdan Teodoru
1f5219f2a2 #679 Ask for confirmation before "Mark all as Read" 2016-01-10 17:20:01 +02:00
Albert221
e8a6fe2f7b #719 Fixed PermissionDeniedException
...causing Whoops on debug and 500 HTTP error
instead of 403 Forbidden error page.
2016-01-07 19:09:57 +01:00
Albert
1a2cc6a603 Merge pull request #1 from flarum/master
Update
2016-01-07 19:05:41 +01:00
Franz Liedke
417b7f7972 Clarify console option 2016-01-07 16:32:01 +01:00
Franz Liedke
9e3771cac3 Clean up code in FileDataProvider 2016-01-07 16:31:21 +01:00
Franz Liedke
819728d8dd Merge pull request #718 from opi/install-from-config-file
Add configuration file installation method.
2016-01-07 16:29:34 +01:00
opi
e3c7f5379b Add configuration file installation method. 2016-01-07 15:20:41 +01:00
Toby Zerner
41ccade385 Merge pull request #706 from Albert221/prefixes
#696 Added support for prefixes in AbstractUrlGenerator.
2016-01-07 12:43:47 +10:30
Albert221
6d42bcb5ce 256 Added created gambit 2016-01-05 17:04:41 +01:00
Albert221
096aae7919 #696 Added support for prefixes in AbstractUrlGenerator. 2016-01-04 15:28:55 +01:00
Toby Zerner
5bbcba6332 Allow existing user to be activated via API 2016-01-04 15:43:23 +10:30
Toby Zerner
b671c3ccfa Merge pull request #703 from Albert221/master
#256 Added multiple author search gambit
2016-01-04 11:40:16 +10:30
Albert221
9d89d8a127 Fixed code style 2016-01-03 14:30:35 +01:00
Albert221
6dfe455fd6 #256 Added multiple author search gambit 2016-01-03 14:26:41 +01:00
Toby Zerner
1f2eaea960 Merge pull request #701 from maelsoucaze/patch-1
Update year range in LICENSE
2016-01-02 17:04:54 +10:30
Maël Soucaze
b2ec380d4c Update year range in LICENSE
Because some changes have been done on that year.
2016-01-02 07:08:58 +01:00
Toby Zerner
08dbc246dd Clean up 2016-01-02 15:26:05 +10:30
Toby Zerner
3767ee4bf6 Allow admins to set a time when creating a post via the API
Again, the use-case for this is to allow the API to be used to import data from an old forum.
2016-01-02 15:25:48 +10:30
Toby Zerner
248de34242 Don't automatically activate users created by admins - require an attribute to be set 2016-01-02 15:24:35 +10:30
Toby Zerner
8d671f4de4 Make sure GetPermission event arguments array is empty if there is no model 2016-01-02 15:23:48 +10:30
Toby Zerner
6de7038f83 Allow setting the token lifetime 2016-01-02 15:22:53 +10:30
Toby Zerner
07a20a10fd Move flood control from core to API layer
This means that flood control can be disabled depending on the nature of the request (i.e. when authenticated using a master API key). The particular use case for this is to allow using the API to migrate data from an old forum.
2016-01-02 15:22:16 +10:30
Toby Zerner
c8027d344a Add admin-only email: gambit to look up users by email 2016-01-02 15:09:56 +10:30
Toby Zerner
f7709aff95 Allow custom redirection after logging out 2016-01-02 15:08:50 +10:30
Toby Zerner
46818ccd94 Extend access token lifetime when remembering a login 2016-01-02 15:08:28 +10:30
Toby Zerner
f6f9e45085 Disable session (and thus enable sudo mode) when authenticating with API token 2016-01-02 15:07:33 +10:30
Toby Zerner
ff0ce09620 Ensure routes are only populated after extensions have registered listeners
Because extensions can have dependencies injected, a RouteCollection could potentially be instantiated, and thus the ConfigureRoutes event would be called before extensions have had a chance to subscribe to it. Instead, we instantiate the RouteCollection on demand, but only populate it when the application boots.
2016-01-02 15:03:11 +10:30
Toby Zerner
e86cc39f5b API: Add an event to configure server middleware 2016-01-02 15:00:07 +10:30
Toby Zerner
a719d4109f Ensure a new asset revision identifier is generated if there is none 2016-01-02 14:59:09 +10:30
Toby Zerner
1aaf588341 Merge branch 'scrubber-display-only-comments' of https://github.com/ahsanity/core 2016-01-02 12:04:04 +10:30
Toby Zerner
0fcc8dca46 Merge pull request #676 from petermein/user-online-indicators
User online indicators
2016-01-02 09:34:11 +10:30
Toby Zerner
5a4e3b09cf Allow extensions to modify text/XML prior to formatting 2015-12-30 15:27:34 +10:30
Toby Zerner
bf87518161 Use username helper when displaying user search results 2015-12-30 15:26:54 +10:30
Toby Zerner
08dae7b530 Add getters 2015-12-30 15:26:24 +10:30
Toby Zerner
aa516fb5c3 Extract method 2015-12-30 15:26:11 +10:30
Toby Zerner
1cac48f90a Always grant master API keys sudo mode 2015-12-30 15:26:07 +10:30
Toby Zerner
5e476fae16 Merge branch 'oauth2-controller' 2015-12-29 11:13:00 +10:30
Toby Zerner
341ffaced5 Bypass email activation when admin creates user via API 2015-12-29 11:02:07 +10:30
Franz Liedke
595d715b1d Installer: Loosen restrictions on MySQL connection details
Closes #602.
2015-12-27 17:31:42 +01:00
Peter Mein
8c8de8eb22 Fixed name to camel case 2015-12-26 13:06:58 +01:00
Peter Mein
5431a90dbd Changed case on helper function
Stub for renaming case of file
2015-12-26 13:06:31 +01:00
Ahsanul Bari
7a8c7518bd Issue #197: Make PostStreamScrubber display numbers relating to only comment posts 2015-12-25 13:01:42 +06:00
Toby Zerner
08f0425c43 Merge pull request #690 from Luceos/phpdoc
fixes flarum/core#678 phpdoc for ip_address on Post model
2015-12-24 10:11:23 +10:30
Daniel Klabbers
ffb76715f6 fixes flarum/core#678 phpdoc for ip_address on Post model 2015-12-23 13:54:58 +01:00
Toby Zerner
9cb45c98d8 Extract notification settings into an item list 2015-12-21 10:38:15 +10:30
Franz Liedke
e0db5823ee Merge pull request #684 from ahsanity/settings-migration
Converted 'settings' table 'value' column from BLOB to TEXT
2015-12-18 13:45:20 +01:00
Ahsanul Bari
46f7f6b3fe Issue#669: Convert 'settings' table 'value' column to TEXT instead of BLOB 2015-12-18 02:25:50 +06:00
Peter Mein
fbcd2cf88c Added missing import 2015-12-16 13:48:38 +01:00
Peter Mein
e55b7a14e5 Added user online indicator to post 2015-12-16 13:43:46 +01:00
Franz Liedke
32601d2c98 Don't return from inside a finally block
This is not supported in HHVM:
https://github.com/facebook/hhvm/issues/5162

Reported on the forum:
https://discuss.flarum.org/d/1390-migrating-from-php-5-6-x-to-php-7-0-x/7
2015-12-10 11:35:51 +01:00
Toby Zerner
d9d52dab3c Fix admin login 2015-12-06 08:47:51 +10:30
Toby Zerner
d743e56bc1 Fix tests and CS 2015-12-05 22:31:33 +10:30
Toby Zerner
0cf000122f Allow username capitalisation to be changed
See https://discuss.flarum.org/d/1573-uppercase-lowercase-username-flagged-as-taken
2015-12-05 15:43:40 +10:30
Toby Zerner
973ca16eee Add base OAuth2 controller 2015-12-05 15:25:10 +10:30
Toby Zerner
262dc70fe1 Garbage-collect email/password/auth tokens. closes #217 2015-12-05 15:24:05 +10:30
Toby Zerner
3efd5fbcb0 Clean up some method arguments 2015-12-05 15:22:42 +10:30
Toby Zerner
c97b01a445 Log in immediately after registration
Newly-created accounts are allowed to log in straight away, but they still have the permissions of a guest until they've confirmed their email address. Instead of showing a success message after registration, we reload the page since they're already logged in.

Still todo: show a message explaining that they need to verify their email address to do anything, and allow it to be resent.
2015-12-05 15:22:25 +10:30
Toby Zerner
b0b3af0305 Improve LoginButton styles, make popup window smaller 2015-12-05 15:19:24 +10:30
Toby Zerner
387109002e Rework sessions, remember cookies, and auth again
- Use Symfony's Session component to work with sessions, instead of a custom database model. Separate the concept of access tokens from sessions once again.
- Extract common session/remember cookie logic into SessionAuthenticator and Rememberer classes.
- Extract AuthenticateUserTrait into a new AuthenticationResponseFactory class.
- Fix forgot password process.
2015-12-05 15:11:25 +10:30
Toby Zerner
1d9e7b0262 Fix case-sensitive class names 2015-12-03 18:29:00 +10:30
Toby Zerner
094ad74abc Allow forum to be taken offline via config 2015-12-03 17:56:27 +10:30
Toby Zerner
67e9e23df1 Fix previous commit 2015-12-03 17:56:04 +10:30
Toby Zerner
1cfae4ad14 Merge branch 'sudo-mode'
# Conflicts:
#	CHANGELOG.md
2015-12-03 15:12:51 +10:30
Toby Zerner
9896378b59 Overhaul sessions, tokens, and authentication
- Use cookies + CSRF token for API authentication in the default client. This mitigates potential XSS attacks by making the token unavailable to JavaScript. The Authorization header is still supported, but not used by default.
- Make sensitive/destructive actions (editing a user, permanently deleting anything, visiting the admin CP) require the user to re-enter their password if they haven't entered it in the last 30 minutes.
- Refactor and clean up the authentication middleware.
- Add an `onhide` hook to the Modal component. (+1 squashed commit)
2015-12-03 15:11:57 +10:30
Toby Zerner
287ce2fddd Fix crash when loading notifications in some instances
Specifically, the crash would occur when the first notification had a subject without a discussion relationship (e.g. the Subscriptions extension's newPost notification, where the subject itself was a discussion). Instead of simply eager loading the nested subject.discussion relationship, we load discussions manually instead.
2015-12-03 15:10:05 +10:30
Toby Zerner
cea1cbc2d6 Fuzzy-match global forum permissions
This means that the "Start a Discussion" button will still be enabled if the user is not allowed to start globally, but only in certain tags.

Also add some other stuff to the changelog.

closes #640
2015-12-03 15:08:28 +10:30
Toby Zerner
b9148364fa Various user interface tweaks 2015-12-03 15:02:52 +10:30
Toby Zerner
2ba890c239 Fix notifications icon/badge color for dark header 2015-12-03 15:02:29 +10:30
Toby Zerner
55e80f135d Tweak admin side-pane styles
Position the side-pane absolutely when scrolled to the top so that it does not disjoin from the header in Safari.
2015-12-03 15:02:07 +10:30
Toby Zerner
81a1c0955b Fix some issues with dropdown positioning 2015-12-03 14:51:55 +10:30
Toby Zerner
05386b1259 Clean up 2015-12-03 14:51:35 +10:30
Toby Zerner
d96e57eabb Truncate long title controls on mobile 2015-12-01 11:48:54 +10:30
Toby Zerner
173de809b8 Merge pull request #648 from dcsjapan/adjust-key
Add third-level namespacing to deleted_user_text
2015-11-30 15:28:39 +10:30
dcsjapan
c432ed7d5c Add third-level namespacing to deleted_user_text 2015-11-30 11:17:11 +09:00
Toby Zerner
172fffd1ed Merge pull request #645 from dcsjapan/leftover-translations
Extract leftover strings
2015-11-28 18:54:27 +10:30
dcsjapan
4bfbf68bca Extract leftover strings
Extracts strings that were missed previously in:
- Dashboard page of admin interface.
- Edit Custom CSS modal of admin interface.
- Settings modal of admin interface.
- Post activity list on user page of forum UI.
Hopefully there aren't any more!
2015-11-28 17:14:22 +09:00
Toby Zerner
cd411a0c6b Merge pull request #644 from dcsjapan/update-locale-template
Update locale file template
2015-11-28 17:33:53 +10:30
dcsjapan
7f05d9dce3 Update locale file template
Adjusts comments to match current english locale files.
2015-11-28 15:55:21 +09:00
Franz Liedke
b3a5822ddb Rename HTTP method override header
This is the name recommended by the JSON-API spec:
http://jsonapi.org/recommendations/#patchless-clients
2015-11-26 17:43:32 +01:00
Toby Zerner
a1e1635019 Update changelog 2015-11-26 10:43:48 +10:30
Toby Zerner
1cc5e1cb26 Merge pull request #642 from binaryoung/master
Fixed #627
2015-11-26 10:32:36 +10:30
young
a80d72d165 Fix #627 2015-11-26 02:03:00 +08:00
Toby Zerner
153a82e937 cs fix 2015-11-23 14:18:56 +10:30
Toby Zerner
262a934747 Prevent error if no input is given in create actions 2015-11-23 14:15:30 +10:30
Toby Zerner
a61929730e Validate avatar URL
Still needs refactor
2015-11-23 14:14:53 +10:30
Toby Zerner
ce02387ee4 Prevent crash if logged in user has been deleted 2015-11-23 11:54:30 +10:30
Toby Zerner
2c4fae60bc Allow provision of an avatar URL to upload during sign up
This can be used by authentication extensions (i.e. mirror Facebook/Twitter profile picture). Rough implementation, needs refactoring.
2015-11-23 11:53:57 +10:30
Toby Zerner
7eab206f91 Don't pad the body when the composer is positioned absolutely (on mobile) 2015-11-23 10:07:23 +10:30
Toby Zerner
599958354c Refactor composer preview logic 2015-11-23 08:47:16 +10:30
Toby Zerner
2088fceb8b Truncate long dropdown menu items (e.g. tags in the sidebar)
ref #391
2015-11-21 14:01:07 +10:30
Toby Zerner
5b25a77e82 Improve spacing of drawer elements 2015-11-21 13:21:27 +10:30
Toby Zerner
59c534a882 Tweak mobile drawer appearance 2015-11-21 13:16:46 +10:30
Toby Zerner
c79bda6279 Fix composer preview button on mobile. closes #196 2015-11-21 13:16:25 +10:30
Toby Zerner
6374f92676 Improve composer appearance/usability on mobile
On mobile:
- Move submit button to right side of toolbar
- Move first header item to toolbar
- Size textarea correctly
2015-11-21 13:16:05 +10:30
Toby Zerner
1f4e03d1fa Make sure dropdowns stay within the viewport horizontally too 2015-11-20 12:35:07 +10:30
Toby Zerner
acf67ca416 Add a "load more" button to the end of the post stream
This is necessary if the page is viewed in a context with no scrolling, i.e. an auto-resizing iframe
2015-11-20 12:35:07 +10:30
Toby Zerner
bd750ca154 Show "reply" action in discussion menu on mobile 2015-11-20 12:35:07 +10:30
Franz Liedke
61b09ac982 Update text-formatter dependency 2015-11-19 13:00:32 +01:00
Franz Liedke
6d895e6d77 Inject hardcoded prerequisite parameters
This affects version numbers, extensions and paths, which might be
skeleton-specific. This commit moves those hardcoded values out of
the classes and instead injects them through the constructor. This
way, all prerequisites can be configured in the service provider.
2015-11-11 19:30:35 +01:00
Franz Liedke
e199997231 Merge pull request #628 from binaryoung/patch-1
[beta4]Fixed login input fields have different style
2015-11-09 10:37:02 +01:00
young
095e8164e8 Update LogInModal.js 2015-11-06 15:54:06 +08:00
Franz Liedke
0bdf873e65 Fix another error handling regression 2015-11-05 14:17:48 +01:00
Franz Liedke
439b867dde Update version number 2015-11-05 09:58:05 +01:00
Toby Zerner
4734dbf46d Release 0.1.0-beta.4 2015-11-05 16:29:13 +10:30
Toby Zerner
783a14610a Revert back button behaviour
As of 25932cf, the back button was no longer shown if the user came in directly to a discussion. This caused problems on mobile where it was kind of hard to get back home without the button.
2015-11-05 16:24:04 +10:30
Toby Zerner
63d00e8b34 WIP sudo mode, better error responses 2015-11-05 16:17:00 +10:30
Toby Zerner
351d2d1366 Merge pull request #625 from dcsjapan/en-template
Add locale file template
2015-11-05 09:50:39 +10:30
dcsjapan
e7b417121a Add locale file template
- Adds a more detailed template for the en.yml file provided with the extension skeleton.
- I've left the sample keys uncommented, but added a comment advising that they be removed.
2015-11-05 08:11:59 +09:00
Toby Zerner
9e3ecd528e Parse fallback catalogues for => references too 2015-11-04 18:34:41 +10:30
Toby Zerner
3518fb2299 Align checkboxes and textareas to the left in centered forms 2015-11-04 12:54:17 +10:30
Toby Zerner
a6eff9383b Show loading indicator for admin client too 2015-11-04 11:51:25 +10:30
Toby Zerner
d806c4491d Fix regression in permission logic: make sure admins can do everything 2015-11-04 09:27:06 +10:30
Toby Zerner
c9a878d49c Make sure all locale JS files are flushed
Even when no language packs are enabled, a forum-en-xxx.js (or whatever the default locale is) file is still generated because other extensions may contain translations. But when enabling the English language pack, since no locales are registered with the LocaleManager, that file doesn't get flushed and therefore doesn't get regenerated with the English translations. This fix always registers the default locale with the LocaleManager so that's not the case.
2015-11-04 09:27:06 +10:30
Toby Zerner
30856a8e2b Merge branch 'loading-indicator' 2015-11-03 18:13:11 +10:30
Toby Zerner
3d3be6983a Apply split dropdown styles on mobiles too
Normal split dropdowns, not in the context of the app's primary control (top-right toolbar button), may be used by extensions (e.g. embed)
2015-11-03 18:09:49 +10:30
Toby Zerner
96b85f1330 Use icon instead of "Discussions" text, fix bugs
- Fix admin page crash
- Only show invisible pin button on desktop; hide it completely on ≤ tablet
2015-11-03 15:54:06 +10:30
Toby Zerner
25932cf7c4 Add label to back button, change behaviour
The back button longer shows if the user hasn't actually navigated anywhere. e.g. if they come in directly to a discussion, it will be hidden.
2015-11-03 15:54:05 +10:30
Toby Zerner
d497782f65 Release 0.1.0-beta.3 2015-11-03 10:01:52 +10:30
Toby Zerner
98ccfdcee5 Improve performance of translation reference parsing 2015-11-02 23:22:00 +10:30
Toby Zerner
b4439dc6b3 Automatically include the appropriate translations from extensions 2015-11-02 18:51:12 +10:30
Toby Zerner
72a2749943 Fall back to en after the forum's default locale
Since we'll be encouraging (requiring?) people to include an "en" translation in extensions they want to put on the Marketplace, we should have a further fallback to "en" if it can't find translations in the forum's default language. That way if people only use extensions from the Marketplace, they'll hardly ever see any key names.

Thanks to @dcsjapan for the suggestion.
2015-11-02 17:55:31 +10:30
Toby Zerner
f13ded1255 Fix error when renaming discussion
Discussion/user info is needed when serialising posts (checking permissions, etc.) so we can't just use the ID.
2015-11-02 17:53:26 +10:30
Franz Liedke
c719cc6d8a Travis: Test on PHP 7 and HHVM, too 2015-11-02 08:11:22 +01:00
Toby Zerner
7dcb99621d Display "Loading..." text while JS is loading 2015-11-02 16:47:36 +10:30
Toby Zerner
4e047bae6a Update readme/contributing 2015-11-01 16:00:25 +10:30
Toby Zerner
90def3f0db Fix permissions being incorrectly granted 2015-11-01 09:38:25 +10:30
Franz Liedke
17619843b5 Update to newest version of Whoops middleware 2015-10-31 12:56:38 +01:00
Toby Zerner
d46316e979 Use relative path for core migrations 2015-10-31 18:22:03 +10:30
Toby Zerner
b44ffd9f8d Only attempt to get default locale if db is up to date 2015-10-31 18:21:39 +10:30
Toby Zerner
953f81176b Fix check for whether there is a translation for a group name 2015-10-31 18:20:55 +10:30
Toby Zerner
119d1721e0 Revert e1315d2; always attempt to parse JSON
This way if there is a PHP error which outputs a 200 OK text/html response, Flarum will correctly show an error message.
2015-10-31 14:49:14 +10:30
Toby Zerner
7d4a04760c Use stable version of tobscure/json-api 2015-10-31 10:08:37 +10:30
Toby Zerner
73c44adb96 Merge pull request #615 from oldskool/ip-logging
Minor changes:
- Rename/restyle migration, fix namespace
- Make IP address optional on PostReply command
2015-10-31 10:04:06 +10:30
Toby Zerner
eb571c5595 Change ItemList API 2015-10-30 22:45:58 +10:30
Toby Zerner
95e3ff8fa8 Update for new tobscure/json-api relationship handling 2015-10-30 11:03:38 +10:30
Toby Zerner
e1315d27a4 Only parse as JSON if appropriate content type 2015-10-29 17:52:52 +10:30
Toby Zerner
7127bea15e Solidify ItemList API 2015-10-29 17:52:52 +10:30
Toby Zerner
a3a5d0a351 Disable extensions that require credentials by default 2015-10-29 17:52:52 +10:30
Toby Zerner
aa7b4dd754 Merge pull request #611 from kirkbushell/master
Tests for all the exception handlers
2015-10-28 23:40:23 +10:30
Kirk Bushell
409a63d77a Added validation handler tests 2015-10-28 12:46:49 +00:00
Kirk Bushell
78f6249b24 Added tests for permission denied exception handler 2015-10-28 12:41:28 +00:00
Kirk Bushell
2edda9baaa Added model not found error handler tests 2015-10-28 12:39:38 +00:00
Jan Dorsman
49fddbd450 WIP IP Logging 2015-10-27 21:53:21 +01:00
Kirk Bushell
1b3d674c39 Added tests for invalid confirmation token handling 2015-10-27 14:47:03 +00:00
Kirk Bushell
400aa4fef9 Added more tests 2015-10-27 13:22:30 +00:00
Kirk Bushell
a4ef9e7cf4 Added output test for flooding exception handler 2015-10-27 12:54:10 +00:00
Kirk Bushell
f230c72ebb Fied broken test 2015-10-27 12:50:11 +00:00
Kirk Bushell
f0883471ef Updated tests namespace to be ps4-valid. Added tests for flooding exception, fixed broken code 2015-10-27 12:48:27 +00:00
Toby Zerner
5e2f659f54 Extract method to get the permalink to a post
Necessary so that the embed extension can override it.
2015-10-27 12:09:24 +10:30
Toby Zerner
bb250baddf Merge pull request #606 from flarum/error-handling
Use exception handlers instead of JsonApiSerializableInterface
2015-10-27 11:43:07 +10:30
Toby Zerner
68498cedae Use exception handlers instead of JsonApiSerializableInterface 2015-10-26 11:14:48 +10:30
Toby Zerner
f3612261ec Improve logic to see if post has been fully loaded
Should fix #295 once and for all.
2015-10-24 13:17:25 +10:30
Toby Zerner
0a65d2bb0d i18n: Make cross-file translation references work 2015-10-24 13:16:26 +10:30
Toby Zerner
59fa623f11 Don't let users view discussions without permission
closes #599
2015-10-22 21:52:31 +10:30
Franz Liedke
e95cb09caa Recompile JavaScript 2015-10-22 10:16:08 +02:00
Toby Zerner
9836fa64ed Allow hyphens in referenced key names 2015-10-22 17:01:21 +10:30
Toby Zerner
415b68f84f Add flood control
closes #271
2015-10-22 16:57:48 +10:30
Toby Zerner
c0364cbc9d Clean up some old code 2015-10-22 12:25:22 +10:30
Toby Zerner
1cd6908dbb Merge pull request #601 from dcsjapan/badge-key-rename
Rename the key for the "Hidden" badge tooltip
2015-10-22 11:11:36 +10:30
dcsjapan
323ced8b00 Rename the key for the "Hidden" badge tooltip
- Shortens the key name for consistency with `badge:` namespace.
- Revised YAML to follow.
2015-10-22 09:31:13 +09:00
Toby Zerner
ea98e4bda9 More helpful avatar upload error messages
ref #165, #118
2015-10-22 10:40:38 +10:30
Toby Zerner
a471a44ca6 Merge pull request #598 from dcsjapan/badge-namespace
Add namespacing for badges
2015-10-21 18:11:02 +10:30
dcsjapan
2903a7068c Add namespacing for badges
- Adds a `lib.badge` namespace to match extension handling.
2015-10-21 16:30:53 +09:00
Toby Zerner
fc7db914db Translate basic HTML views
app('view') call to set translator is temporary. See #189
2015-10-21 11:36:49 +10:30
Toby Zerner
a7c2a7a2d3 Increase username max length
Not sure why it was at 8!
2015-10-21 11:13:55 +10:30
Toby Zerner
2a5c0c1c7a Improve request error debug output 2015-10-21 10:47:07 +10:30
Toby Zerner
14af6c0e8b Remove app.trans shortcut
Use `app.translator.trans` instead.
2015-10-21 10:31:28 +10:30
Toby Zerner
c2f802878a Fix translation keys 2015-10-21 10:27:09 +10:30
Toby Zerner
b148c9d7da Merge branch 'dcsjapan-key-adjustment' 2015-10-21 10:02:22 +10:30
Toby Zerner
b23e821013 Merge branch 'key-adjustment' of https://github.com/dcsjapan/flarum-core into dcsjapan-key-adjustment
# Conflicts:
#	js/forum/src/components/LogInModal.js
2015-10-21 10:02:07 +10:30
Toby Zerner
9aeaccf9a4 Improve client boot failure error message/appearance 2015-10-21 09:59:22 +10:30
Toby Zerner
12830265d9 Change back to 401 error on invalid login
See 26a821e3e2 (commitcomment-13866552)
2015-10-21 09:04:58 +10:30
Toby Zerner
6d7b826133 Fix bad filename 2015-10-20 22:52:47 +10:30
Toby Zerner
9b0aa574f0 Update broken bower resource paths 2015-10-20 22:48:32 +10:30
Toby Zerner
845daf1ab6 Don't use array_filter flag (PHP 5.6 only) 2015-10-20 22:25:20 +10:30
Toby Zerner
22ffb76cb5 Fix installation 2015-10-20 22:07:35 +10:30
Toby Zerner
067552efe5 Revert error handling regression 2015-10-20 22:07:08 +10:30
dcsjapan
659cfb72ad Fix the double correction
- Fixes one mangled app.translator call.
2015-10-20 15:44:58 +09:00
dcsjapan
49d59089e4 Add third tier to key namespacing
- Changes all `app.trans` calls to `app.translator.trans` calls.
- Changes existing keys to [three-tier namespace structure](https://github.com/flarum/english/pull/12).
- Extracts additional strings for `lib:` namespace.
- Extracts two previously missed strings for EditGroupModal.js.
2015-10-20 13:04:43 +09:00
Toby Zerner
a9eb62880e Remove behaviour where backspace re-focuses on title field 2015-10-20 12:49:19 +10:30
Toby Zerner
26a821e3e2 Improve client XHR error handling
The default XHR error handler produce an alert which is appropriate to the response status code. It can be overridden per-request (by specifying the `errorHandler` option) so that the alert can be suppressed or displayed in a different position (e.g. inside a modal).

ref #118
2015-10-20 12:48:26 +10:30
Toby Zerner
7490709af8 Fix migrate command and generated migration namespace 2015-10-19 16:48:16 +10:30
Toby Zerner
96c42ed337 Translate group names during serialization
closes #564
2015-10-19 15:44:28 +10:30
Toby Zerner
5cd2d6a79f Add error handling to edit group modal 2015-10-19 15:44:00 +10:30
Toby Zerner
a3a64749c5 Only decrease unread count if notification is unread
closes #590
2015-10-19 15:27:46 +10:30
Toby Zerner
1242fa79af Implement proper update process
If the version in the settings table mismatches the code version, then we return a 503 error for all requests coming through index.php and api.php, while admin.php serves up a form prompting for the database password which will run outstanding migrations.
2015-10-19 15:09:54 +10:30
Toby Zerner
ddfedcb4dd Add Interface suffix to SettingsRepository 2015-10-19 14:58:47 +10:30
Toby Zerner
43c44efe3d Make emails translatable
closes #267
2015-10-19 11:23:39 +10:30
Toby Zerner
7e763ec22b Gracefully fail for LESS compilation errors, not just parsing ones 2015-10-17 15:55:03 +10:30
Toby Zerner
e0b6aacc9e Prevent crash when no locales are enabled 2015-10-16 14:02:09 +10:30
Toby Zerner
d8eed9d171 Allow pluralisation of event post description 2015-10-16 12:50:12 +10:30
Toby Zerner
46ba8a3b8d cs fix 2015-10-15 22:51:26 +10:30
Toby Zerner
c08b62af80 Refactor translation and validation
We now use Symfony's Translation component. Yay! We get more powerful pluralisation and better a fallback mechanism. Will want to implement the caching mechanism at some point too. The API is replicated in JavaScript, which could definitely use some testing.

Validators have been refactored so that they are decoupled from models completely (i.e. they simply validate arrays of user input). Language packs should include Laravel's validation messages.

ref #267
2015-10-15 22:30:45 +10:30
Franz Liedke
a23180f279 Revert previous commit 2015-10-14 13:52:52 +02:00
Franz Liedke
cc68c6f503 Fix version constraint for tobscure/json-api 2015-10-14 13:42:18 +02:00
Franz Liedke
d0a188bc42 Require version 0.1 of Whoops middleware 2015-10-14 13:40:02 +02:00
Toby Zerner
dfb9f23eee Fix default forum route controller 2015-10-14 17:03:29 +10:30
Toby Zerner
044d730480 Potential fix for #381
Have not yet been able to test in situ.
2015-10-14 16:35:48 +10:30
Toby Zerner
e145873d59 Fix appearance of event posts on mobile 2015-10-14 16:35:06 +10:30
Franz Liedke
7b49f3c24c Clean up 2015-10-14 07:50:42 +02:00
Toby Zerner
4b3e1b16d9 Remove forum. prefix from permissions
closes #425
2015-10-14 16:11:00 +10:30
Toby Zerner
cde8dd0dc4 Store temporary files in storage dir
closes #482
2015-10-14 15:49:31 +10:30
Toby Zerner
bdf626b552 Basic parsing of HTML tags in translations
This allows text to be wrapped with a virtual element:

	key: "This is a <test>Test</test>"

	app.trans('key', {test: <a href="#"/>});

closes #574
2015-10-14 15:24:28 +10:30
Toby Zerner
68a7886cec Merge pull request #577 from dcsjapan/leftover-string-extraction
Extract leftover core strings
2015-10-14 14:48:02 +10:30
Toby Zerner
8fc43cac9e Merge branch 'dcsjapan-admin-string-extraction' 2015-10-14 14:46:05 +10:30
Toby Zerner
a3c11587b7 Merge pull request #575 from dcsjapan/admin-string-extraction 2015-10-14 14:45:29 +10:30
Toby Zerner
c7c2d9a755 Fake PATCH/PUT/DELETE requests
closes #502
2015-10-14 12:46:59 +10:30
Toby Zerner
b928cb523a CS fix 2015-10-14 12:26:48 +10:30
Toby Zerner
60bdbe6e52 Show 404 errors as the "pretty" page even in debug mode
closes #503
2015-10-14 12:23:20 +10:30
Toby Zerner
9772e398f6 Evaluate <script> tags in TextFormatter output
This allows us to rely on TextFormatter's highlight.js loading code instead of implementing our own.

closes #532
2015-10-14 11:45:33 +10:30
Toby Zerner
b83c81c06e Clean up 2015-10-14 11:14:36 +10:30
Toby Zerner
e3569d39cc Clean up, don't use mixin
PhpStorm/WebStorm doesn't like the mixin syntax, and it's clearer to just use Object.assign.
2015-10-13 16:57:18 +10:30
Toby Zerner
33dd5fff36 Initialise component state in init() instead of constructor
This allows component state to be overridden via monkey-patch. ref #246
2015-10-13 16:55:56 +10:30
Toby Zerner
2ae7392dea Publish core/extension assets
Core assets are copied into the root/assets directory on installation.

The contents of an "assets" directory within any extension is copied into root/assets/extensions/{name}/ whenever the extension is enabled, and deleted whenever the extension is uninstalled.

Still needs to be refactored
2015-10-13 16:52:45 +10:30
Toby Zerner
6df48b04c2 Fix installation regressions 2015-10-13 15:55:18 +10:30
Toby Zerner
6f7cce5adf Further refinements to admin extensions page 2015-10-13 12:27:10 +10:30
Toby Zerner
4c2ff6e82d Revamp admin extensions page
- New look
- Groups extensions by keywords
2015-10-12 15:02:59 +10:30
Toby Zerner
b53e612007 Fix failing tests + CS 2015-10-11 23:37:51 +10:30
Toby Zerner
208d90293d Fix extension settings 2015-10-11 23:18:57 +10:30
Toby Zerner
1c3fda4a71 Update some APIs
- Rename DiscussionSearchWillBePerformed to ConfigureDiscussionSearch, same with users
- Add some handy methods
2015-10-11 22:31:06 +10:30
Toby Zerner
663de42917 Fix extension uninstallation 2015-10-11 22:29:25 +10:30
Toby Zerner
cde7dd3ce1 Make sure activation status/email is returned when creating a user 2015-10-11 22:29:14 +10:30
Toby Zerner
4580ebe100 Show posts even if they don't have a user 2015-10-11 22:28:23 +10:30
Toby Zerner
60483b2c62 Fix ConfigureNotificationTypes API 2015-10-11 13:08:57 +10:30
Toby Zerner
cf42765513 External helpers are included by default now 2015-10-11 11:37:23 +10:30
Toby Zerner
8e5b13903e Add more info to composer.json 2015-10-11 11:18:30 +10:30
Toby Zerner
7387dfb7da Concatenate items in {second}, not {first} 2015-10-11 10:09:22 +10:30
Toby Zerner
0b888ea342 Fix installation 2015-10-11 10:05:40 +10:30
Toby Zerner
6f1c46819e Minify each JS file individually, caching the result
This means that the expensive minification process will only be run for a file if it hasn't before. Greatly speeds up extension enabling/disabling.

Also:
- Don't check file last modification times in production for a bit of extra perf.
- Only flush CSS when theme settings are changed. This speeds up the page reload a bit.
2015-10-09 01:52:51 +10:30
Toby Zerner
18def302d6 Bundle unminified JS; minify via ClosureCompilerService when in production
Falls back to a less effective minification library if ClosureCompilerService errors or is unavailable. Minification takes a while (20 seconds or so), but it only happens when assets are modified. Still, this means enabling/disabling extensions is taking far too long. Possible solutions:

- Don't minify initially; set a process running in the background to do minification, and server unminified assets in the meantime.
- Refactor compiler to send each JS file to CCS individually, only if that particular file has been modified.

flarum/gulp has also been updated to no longer support uglification.

closes #582
2015-10-09 00:33:53 +10:30
Toby Zerner
bddbf24055 Make punctuate translatable, rename to punctuateSeries 2015-10-08 22:42:03 +10:30
Toby Zerner
0ce014b3bb Flush forum assets when extensions are enabled/disabled 2015-10-08 17:46:03 +10:30
Toby Zerner
c3cf5fe074 Only show restore button for comment posts 2015-10-08 17:45:44 +10:30
Toby Zerner
72a3582287 Update various event APIs 2015-10-08 16:49:11 +10:30
Toby Zerner
dd67291ce0 Major refactor and improvements
- Reorganised all namespaces and class names for consistency and structure. Following PSR bylaws (Abstract prefix, Interface/Trait suffix).
  - Move models into root of Core, because writing `use Flarum\Core\Discussion` is nice. Namespace the rest by type. (Namespacing by entity was too arbitrary.)
  - Moved some non-domain stuff out of Core: Database, Formatter, Settings.
  - Renamed config table and all references to "settings" for consistency.
  - Remove Core class and add url()/isInstalled()/inDebugMode() as instance methods of Foundation\Application.
  - Cleanup, docblocking, etc.

- Improvements to HTTP architecture
  - API and forum/admin Actions are now actually all the same thing (simple PSR-7 Request handlers), renamed to Controllers.
  - Upgrade to tobscure/json-api 0.2 branch.
  - Where possible, moved generic functionality to tobscure/json-api (e.g. pagination links). I'm quite happy with the backend balance now re: #262

- Improvements to other architecture
  - Use Illuminate's Auth\Access\Gate interface/implementation instead of our old Locked trait. We still use events to actually determine the permissions though. Our Policy classes are actually glorified event subscribers.
  - Extract model validation into Core\Validator classes.
  - Make post visibility permission stuff much more efficient and DRY.

- Renamed Flarum\Event classes for consistency. ref #246
  - `Configure` prefix for events dedicated to configuring an object.
  - `Get` prefix for events whose listeners should return something.
  - `Prepare` prefix when a variable is passed by reference so it can be modified.
  - `Scope` prefix when a query builder is passed.

- Miscellaneous improvements/bug-fixes. I'm easily distracted!
  - Increase default height of post composer.
  - Improve post stream redraw flickering in Safari by keying loading post placeholders with their IDs. ref #451
  - Use a PHP JavaScript minification library for minifying TextFormatter's JavaScript, instead of ClosureCompilerService (can't rely on external service!)
  - Use UrlGenerator properly in various places. closes #123
  - Make Api\Client return Response object. closes #128
  - Allow extensions to specify custom icon images.
  - Allow external API/admin URLs to be optionally specified in config.php. If the value or "url" is an array, we look for the corresponding path inside. Otherwise, we append the path to the base URL, using the corresponding value in "paths" if present. closes #244
2015-10-08 14:28:02 +10:30
dcsjapan
1a3e085a9c Fixes namespace errors in keys for the two extracted strings. 2015-10-06 09:56:12 +09:00
dcsjapan
78cd35d93c Extract leftover core strings
Adds app.trans calls for a couple strings in core:
- The "there are no discussions" message in DiscussionList.js
- The user deletion confirmation message in UserControls.js
- Also adds new HTML-style tags to LogInModal.js and SignUpModal.js
2015-10-06 05:52:03 +09:00
dcsjapan
4725ac4131 Extract admin strings
Adds app.trans calls for strings used by the admin UI.
- Strings for AddExtensionModal.js not included.
- Corresponding YAML will be sent later w/ more extracted strings.
2015-10-05 19:06:41 +09:00
Toby Zerner
8c7cdb184f Fix installation 2015-10-03 17:38:23 +09:30
Toby Zerner
2223e1a13c Add compiled JS 2015-10-03 16:59:53 +09:30
Toby Zerner
296b822636 Merge branch 'master' into composer 2015-10-03 16:41:23 +09:30
Toby Zerner
232f3b6bc6 API: Reverse splitting of BuildClientView event, but add checker methods 2015-10-03 16:40:41 +09:30
Toby Zerner
0c065520e4 Merge pull request #571 from mikechristopher/patch-1
Read documentation link broken
2015-10-03 07:03:40 +09:30
Mike Nolan
56e10ce6ba Read documentation link broken
This fixes #510 by putting in the link to the themes documentation
2015-10-02 17:24:01 +01:00
Toby Zerner
03f862fe8c Merge branch 'master' into composer 2015-10-02 17:57:24 +09:30
Toby Zerner
b4cb5a11da Allow extension icon styles to reference assets
Example usage:

"icon": {
    "backgroundImage": "url('{$assets}/icon.svg')"
}
2015-10-02 17:55:42 +09:30
Toby Zerner
ef2cc9b0cd Remove ability for extensions to register a service provider
The concept of returning a bootstrapper function is simpler and the use of service providers had no advantage over it.
2015-10-02 17:54:53 +09:30
Toby Zerner
2a17590412 Change migration namespace format 2015-10-02 17:49:43 +09:30
Toby Zerner
e251cf34c4 Use composer.json for extension metadata 2015-10-02 17:49:16 +09:30
Toby Zerner
0142b01cc5 Add server 2015-10-02 17:47:12 +09:30
Toby Zerner
8a5eb9cd42 Merge branch 'master' of https://github.com/flarum/core 2015-10-02 17:44:30 +09:30
Toby Zerner
89338290a4 Only include namespaced translations 2015-10-02 17:43:41 +09:30
Toby Zerner
58eaf79a98 API: Split BuildClientView into two separate events
Much easier to work with. Extension stub hasn't been updated yet.
2015-10-02 17:42:34 +09:30
Toby Zerner
f255d318ef Add multiple UrlGenerator classes for forum/api/admin
Spent quite a while looking into the best solution here and ended up going with three separate classes. Thanks to @Luceos for the PR that got this rolling (#518). My reasoning is:

- The task of routing and URL generation is independent for each section of the app. Take Flarum\Api\Users\IndexAction for example. I don't want to generate a URL to a Flarum route... I specifically want to generate a URL to an API route. So there should be a class with that specific responsibility.
- In fact, each URL generator is slightly different, because we need to add a certain prefix to the start (e.g. /api)
- This also allows us to get rid of the "flarum.api" prefix on each route's name.
- It's still DRY, because they all extend a base class.

At the same time, I could see no reason this needed to be "interfaced", so all of the classes are concrete.

Goes a long way to fixing #123 - still just a few places left remaining with hardcoded URLs.
2015-10-02 17:35:29 +09:30
Toby Zerner
9e91ada4a8 Add asset compilation script 2015-10-02 17:23:08 +09:30
Toby Zerner
06de5c430b Merge pull request #563 from dcsjapan/namespace-fix
Add "forum" namespacing to previously renamed core keys
2015-10-02 17:10:13 +09:30
dcsjapan
a590150698 Fixes core.deleted_username as well. 2015-10-02 16:37:09 +09:00
dcsjapan
0a66229169 Add "forum" namespacing to previously renamed core keys
- Does not affect "core.deleted_user" global string.
- Corresponding YAML will be sent later w/ more extracted strings.
2015-10-02 15:54:39 +09:00
Toby Zerner
4e5b3099f8 Fix scrubber dragging division by zero
closes #64
2015-09-29 17:46:05 +09:30
Toby Zerner
aa203de6e9 Update docblocks 2015-09-29 16:41:34 +09:30
Toby Zerner
e0aa99fabb Properly mark all notifications as read
Previously, clicking the "mark all notifications as read" button would individually mark each of the visible notifications as read. Since we now always show a badge with the number of unread notifications, we need to make sure that all notifications (not just the visible ones) can be marked as read. Otherwise it would be possible to get stuck with an unread badge there.

This commit adds a new API endpoint which marks *all* of a user's notifications as read. The JSON-API spec doesn't cover this kind of thing (updating all instances of a certain resource type), so I'm a bit unsure regarding what the endpoint should actually be. For now I've gone with POST /notifications/read, but I'm open to suggestions.

ref #500
2015-09-29 16:41:05 +09:30
Toby Zerner
6463d912a9 Properly handle errors in change email modal 2015-09-29 15:19:06 +09:30
Toby Zerner
b39a991940 Remove "go to email provider" buttons
closes #541
2015-09-29 15:18:55 +09:30
Toby Zerner
0db4708ef9 Add missing semicolon 2015-09-29 14:29:40 +09:30
Franz Liedke
5382d0ce1a Remove unused import 2015-09-29 01:31:34 +02:00
Franz Liedke
295f29e53e Make linter happy 2015-09-29 01:31:09 +02:00
Franz Liedke
ce094be83e Sync notification count when clicking on them or marking all as read
Refs #500.
2015-09-29 01:28:47 +02:00
Franz Liedke
f5b5d9ca5c Use correct method for notification drawer on mobile
Refs #500.
2015-09-29 01:28:47 +02:00
Franz Liedke
040ce52724 Return both unread and new notification count from the API
Related to #500.
2015-09-29 01:28:47 +02:00
Franz Liedke
56f9016ff7 Merge pull request #554 from kirkbushell/feature/admin-tests
Admin tests
2015-09-29 00:59:09 +02:00
Franz Liedke
1f7afb3e4a Implement third state for notification list dropdown
Related to #500.
2015-09-29 00:50:28 +02:00
kirkbushell
b179ca1c48 Added tests for admin login/cookie checks 2015-09-28 16:02:37 +01:00
kirkbushell
c3374197d1 Added zend-stragility (missing), removed some redundant code. 2015-09-28 15:59:07 +01:00
Franz Liedke
9529ce9ba2 Merge pull request #553 from kirkbushell/feature/settings-tests
Tests for core settings code
2015-09-28 16:47:04 +02:00
kirkbushell
bac3fe84da Moved psr-4 loading for tests out of the autoload 2015-09-28 15:44:35 +01:00
kirkbushell
a00226c05a Added some tests for the database setting repository 2015-09-28 15:34:32 +01:00
kirkbushell
7706714ad9 Removed phpsec as the testing library, added phpunit and converted the first spec test to phpunit format. Also added mockery. 2015-09-28 15:09:13 +01:00
Toby Zerner
538a3e5e98 Prevent infinite redraw loop in IE
Welp, this is probably the most subtle bug I've ever tracked down and fixed.

Turns out that IE has this bug where the "oninput" event will be triggered whenever the "placeholder" attribute is changed. Most placeholders get their value from app.trans. The app.trans method returns a VirtualElement – which is an array, not a string! That means when Mithril's diffing algorithm was comparing the old value to the new value, it was comparing two different array instances, and thus deciding the value was dirty and the placeholder attribute needed to be updated. Due to the IE bug, that was leading to the "oninput" event being triggered... and then through Mithril's auto-redraw mechanism, a redraw would be triggered, and so the cycle continued.

Since the inputs in the LogInModal (among others) only update the component state on the "onchange" event (i.e. when the input loses focus), the intermittent redraws would cause the input's value to be cleared continuously. That's what was causing #464. Could've been easily and superficially patched by changing them to use "oninput" events, but luckily I dived a little deeper!

Glad that's over. Running IE11's buggy dev tools in an underpowered VM isn't fun. Would not recommend.

closes #464
2015-09-25 23:44:15 +09:30
Toby Zerner
f1c40eeccc Prevent empty beforeunload dialog on Internet Explorer 2015-09-25 23:21:10 +09:30
Toby Zerner
3efbffdcec Extract English translations into a language pack
To make this work, we add support for the client working without any locale.

Also fixes #412.
2015-09-25 16:12:09 +09:30
Toby Zerner
02e40f7c47 Allow extensions to return a callback instead of a provider name
This is useful for very simple extensions like language packs, because it means no Composer/namespacing and thus bootstrap.php doesn't have to be changed at all.
2015-09-25 16:05:01 +09:30
Toby Zerner
26143272bd Condense discussion list last reply icons 2015-09-25 13:55:32 +09:30
Toby Zerner
eabd8842ed Merge pull request #545 from dcsjapan/dashboard-update-retry
Revises the dashboard links to emphasize beta testing procedure.
2015-09-25 13:27:57 +09:30
dcsjapan
4851596c78 Revises the dashboard links to emphasize beta testing procedure.
Closes flarum/core#542
- Includes a disclaimer stating that the software is provided mainly
for testing.
- Directs bug reports to the Support tag in the forums instead of the
issue tracker
- Directs feedback to the Features tag in the forums
2015-09-25 12:55:13 +09:00
Franz Liedke
de216af08d Change name of header for faking HTTP methods
Refs #502.
2015-09-25 00:35:57 +02:00
Franz Liedke
418b1b9bac Implement middleware for faking HTTP methods
Refs #502.
2015-09-25 00:31:31 +02:00
Daniel Klabbers
68369ac5bb heavier validation for username 2015-09-24 23:07:30 +02:00
Franz Liedke
7404debb21 Clean up unused variable
Closes #501.
2015-09-24 16:27:00 +02:00
Toby Zerner
88372640aa Remove core key reorganization comments 2015-09-24 14:22:32 +09:30
Toby Zerner
fdb598187f Revert to "go to email provider" button 2015-09-24 14:22:14 +09:30
Toby Zerner
753808c3f1 Indent block comments 2015-09-24 14:20:45 +09:30
Toby Zerner
dbef2a4c1f Add comments about intentional spaces 2015-09-24 14:20:37 +09:30
Toby Zerner
35360b690c Temporary solution to resolve translation references
Just implemented this roughly so I can keep working :D /cc @franzliedke
2015-09-24 09:27:47 +09:30
Toby Zerner
d2c4569112 Update discussion list "last reply" translations 2015-09-24 09:07:46 +09:30
Toby Zerner
b9bda2d443 Compile all core translations for now
May need to be specific again once we have admin translations, or it
may be better to just put admin translations under a different
namespace...
2015-09-24 09:06:44 +09:30
Toby Zerner
b126055611 Add "last reply" icon to discussion list 2015-09-24 09:05:52 +09:30
Toby Zerner
9b3b87e4db Merge pull request #536 from dcsjapan/core-key-reorganization
Core key reorganization
2015-09-24 08:59:16 +09:30
Toby Zerner
91fb24f7a3 Fix is:unread gambit
closes #485
2015-09-24 08:31:56 +09:30
Franz Liedke
393f2de146 Fix last commit 2015-09-23 18:30:28 +02:00
Franz Liedke
6f47f4a86f Fix infinite redirect on some nojs pages. 2015-09-23 17:55:16 +02:00
Franz Liedke
4c6e03a692 Update TextFormatter
Fixes #532.
2015-09-23 09:03:24 +02:00
dcsjapan
c2ad1181b1 Merge remote-tracking branch 'flarum/master' into core-key-reorganization 2015-09-23 14:58:34 +09:00
dcsjapan
d5d7185794 Primary key renaming
Improved consistency for existing core translation key names.

See flarum/core#265
- Completely overhauled core en.yml
- Replaced existing key names in all core JS files to match
- Extracted a hardcoded string in IndexPage.js
- Combined two app.trans calls in DiscussionControls.js
- Removed hardcoded spaces from LogInModal.js and SignUpModal.js
- Added two new keys from DiscussionControls.js (soft delete)
- Created two new “reused keys” to YML to accommodate same
2015-09-23 14:58:33 +09:00
Toby Zerner
a0267d9515 Add extra check to make sure post has been fully loaded
Ref #295
2015-09-23 12:22:37 +09:30
Toby Zerner
69a50565bb Don't catch JS error in debug mode 2015-09-23 10:52:26 +09:30
Toby Zerner
273461040c Update local copy of notification when marking as read 2015-09-23 10:52:26 +09:30
Toby Zerner
858feb5ac0 Vendor prefix badge shadow 2015-09-23 10:52:26 +09:30
Franz Liedke
ee9862004d Make sure JSON request bodies are parsed as array.
Refs #533.
2015-09-22 15:19:54 +02:00
Toby Zerner
0b0f1bc142 Reduce font size/padding on discussion list 2015-09-22 19:29:40 +09:30
Toby Zerner
153655f1f1 Update changelog for bundled extensions 2015-09-22 18:20:32 +09:30
Toby Zerner
3020710959 Move post Restore control into same section as Delete Forever 2015-09-22 17:58:19 +09:30
Toby Zerner
db067c7d87 Refresh discussion metadata to make sure it's correct
The new Approval extension may hide new posts, in which case we don't
want to increment the comments count/set the last post.
2015-09-22 17:57:20 +09:30
Toby Zerner
7a0299d246 Relax self edit/rename restrictions
- Fixes the last post not being self-editable if it's hidden
- Fixes the discussion not being self-renameable its only post is hidden
2015-09-22 17:56:09 +09:30
Toby Zerner
5598e885b7 Improve admin permissions page with icons, visual tweaks 2015-09-22 17:52:16 +09:30
Toby Zerner
264725d872 Allow discussions to be hidden and restored 2015-09-22 17:48:21 +09:30
Toby Zerner
c7ed189cf3 Use ES6 syntax 2015-09-22 17:23:47 +09:30
Toby Zerner
ab6e3351b4 Redraw old data after unsuccessful save 2015-09-22 17:23:28 +09:30
Toby Zerner
8e19312534 Add API to run callback after a model instance is saved 2015-09-22 17:22:25 +09:30
Toby Zerner
ed602c6032 Remove importer for the time being 2015-09-22 17:14:01 +09:30
Toby Zerner
d6ed04ffce Fix incorrect version requirement in extension generator 2015-09-22 17:13:41 +09:30
Toby Zerner
bd02e4307a Tweak alignment/width of reply composer 2015-09-22 17:12:50 +09:30
Toby Zerner
3eafed0ae3 Update to FontAwesome 4.4.0 2015-09-22 17:11:51 +09:30
Toby Zerner
f591851cb2 Patch Mithril with a route shortcut attribute
Instead of:

<a href={app.route.user(user)} config={m.route}>

We can use:

<a route={app.route.user(user)}>
2015-09-22 17:09:38 +09:30
Toby Zerner
f55d95c9b7 Select contents of search input on focus 2015-09-22 17:06:06 +09:30
Toby Zerner
d610ea663f Keep post actions visible when controls dropdown is open
Also show without hover on touch devices
2015-09-22 17:05:14 +09:30
Toby Zerner
8ab0686666 Properly hide loading spinner on unsuccessful post edit 2015-09-22 16:57:06 +09:30
Toby Zerner
8937050aed Rename column for consistency 2015-09-22 16:54:32 +09:30
Toby Zerner
efca923d30 Add "Debug" button to inspect the response of a failed AJAX request
Related to #118
2015-09-18 16:46:46 +09:30
Toby Zerner
80665450fc Distinguish links in alerts 2015-09-18 14:39:20 +09:30
Toby Zerner
4041c18014 Further tweaks to post layout
Move footer after actions so that we'll be able to have larger things
in the footer (e.g. Answers) without pushing down the controls.
2015-09-18 14:38:57 +09:30
Toby Zerner
514eec7466 Clean up 2015-09-18 13:29:50 +09:30
Toby Zerner
8f387bbd52 Allow formatter to be used for things other than post formatting 2015-09-18 13:29:43 +09:30
Toby Zerner
c4dc1a5ee2 Allow settings to be deleted using LIKE
Also give migrations access to the SettingsRepository
2015-09-18 13:28:38 +09:30
Toby Zerner
ca09e834b1 Add events for serializing/unserializing config values 2015-09-18 13:16:35 +09:30
Toby Zerner
4752142c11 Reflect composer's focus state in a property 2015-09-18 13:15:58 +09:30
Toby Zerner
6582c5fcf0 Smooth out initial composer slide animation 2015-09-18 13:14:12 +09:30
Toby Zerner
6fff3cc0dc Add abstract SettingsModal component in admin app
Makes building settings modals (that update basic config values) a
whole lot quicker/easier.
2015-09-18 13:13:25 +09:30
Toby Zerner
0b406a06a1 Patch Mithril with a bidi attribute
Enables quick bidirectional bindings. So instead of this:

<input value={prop()} oninput={m.withAttr('value', prop)}/>

... we can do this:

<input bidi={prop}/>
2015-09-18 13:06:37 +09:30
Toby Zerner
1fc369c59e Cleanup, update changelog 2015-09-18 13:01:31 +09:30
Toby Zerner
f4a4ed8b49 Extend social login access token expiry 2015-09-17 12:57:22 +09:30
Toby Zerner
dbd33f687c Remove "custom" home page input
Also add an API to let extensions define additional default route
options.

Allowing default routes with parameters (e.g. /d/123) is very difficult
because of the way Mithril routing works, and it doesn't have a
convincing use-case to justify the trouble. So I've removed the custom
input altogether.

closes #427
2015-09-17 12:56:39 +09:30
Toby Zerner
e038c5c9d9 Add migration generator 2015-09-17 12:16:38 +09:30
Toby Zerner
974d301bed Update changelog 2015-09-17 09:11:48 +09:30
Toby Zerner
7fb582e8d7 Namespace migrations to avoid potential conflicts
Core migrations are under the Flarum\Migrations\Core namespace.
Extension migrations must be under the
Flarum\Migrations\{ExtensionName} namespace.

closes #422
2015-09-17 08:54:31 +09:30
Franz Liedke
633f84bbe5 Merge pull request #506 from Luceos/fixed_meta_description
missing meta description from admin area
2015-09-16 21:52:58 +02:00
Daniel Klabbers
84e670082b fixed flarum/core#489 missing meta description from admin area 2015-09-16 21:43:53 +02:00
Toby Zerner
8b06a6c282 Merge pull request #499 from Luceos/php_version_fix
version constraint must match laravel dependancies at least
2015-09-16 17:33:23 +09:30
Daniel Klabbers
a2b43f6f78 version constraint must match laravel dependancies at least 2015-09-16 09:56:19 +02:00
Toby Zerner
66510d6887 Use LESS variable 2015-09-16 17:04:35 +09:30
Franz Liedke
9767bce1e3 Move dropdown mouseover to correct location
Related to #496.
2015-09-16 09:00:33 +02:00
Franz Liedke
ad060126ae Small cleanup in extension manager 2015-09-16 08:56:27 +02:00
Franz Liedke
ffcba1f173 Always use label as tooltip for header icons
Closes #496.
2015-09-16 08:45:45 +02:00
Toby Zerner
c019ed6fb0 Make composer full-width by default
closes #398
2015-09-16 16:04:15 +09:30
Toby Zerner
b0da51309e Copy properties when monkey-patching 2015-09-16 16:03:25 +09:30
Toby Zerner
92437edd1b Revert "Revert "Scroll overflowing post content""
This reverts commit b695f4d063.
2015-09-16 14:10:30 +09:30
Toby Zerner
b695f4d063 Revert "Scroll overflowing post content"
This reverts commit 64207a53c6.
2015-09-16 14:08:59 +09:30
Toby Zerner
8414a59908 Don't use a default dropdown label 2015-09-16 10:12:49 +09:30
Toby Zerner
64207a53c6 Scroll overflowing post content
closes #143
2015-09-16 08:39:30 +09:30
Franz Liedke
a9e001a4ce Merge pull request #488 from billmn/patch-1
Changed default Admin password
2015-09-15 17:56:04 +02:00
Davide Bellini
fc8dfd8893 Changed default Admin password
Default Admin password doesn't pass the new validation rule (min 8 chars)

See: cbcad27679 (diff-2e6d4ed85cd06d3e11f7f8428746214eR126)
2015-09-15 17:52:33 +02:00
Toby Zerner
701ad0a977 Add API to set asset compiler filename 2015-09-15 21:20:32 +09:30
Franz Liedke
cd5f5515e2 Try to make PHP extension requirement message clearer 2015-09-15 09:18:26 +02:00
Franz Liedke
3221e80014 Give more padding to discussion list controls
Closes #218.
2015-09-15 09:01:41 +02:00
Toby Zerner
d8c2cbc265 Mark all notifications with the same subject as read 2015-09-15 16:20:22 +09:30
Toby Zerner
f6ad891850 Rename ExternalAuthenticator to Authenticator 2015-09-15 16:03:10 +09:30
Toby Zerner
e524c59f97 Improve external authentication API
Some providers (e.g. Twitter) don't expose user email addresses, so it
turns out we can't use that as the sole form of identification/account
matching.

This commit introduces a new `auth_tokens` table which stores arbitrary
attributes during the sign up process. For example, when Twitter is
authenticated, a new auth token containing the user's Twitter ID will
be created. When sign up is completed with this token, that Twitter ID
will be set as an attribute on the user's account.
2015-09-15 15:56:48 +09:30
Toby Zerner
cac670e699 Focus on username field when login buttons are present 2015-09-15 13:04:45 +09:30
Toby Zerner
d9062ced96 Remove margin when no login buttons are present 2015-09-15 13:04:28 +09:30
Toby Zerner
90a3bff638 Tweak notifications indicator appearance 2015-09-15 12:54:49 +09:30
Toby Zerner
ddafefc354 Merge branch 'compact-posts' 2015-09-15 12:42:32 +09:30
Toby Zerner
fa265152c7 Clean up new post layout 2015-09-15 12:42:03 +09:30
Toby Zerner
5c98a08e0f Merge branch 'master' into compact-posts 2015-09-15 11:27:49 +09:30
Toby Zerner
6beb4fe898 Add external authenticator (social login) API
Allows registrations to be completed with a pre-confirmed email address
and no password.
2015-09-15 11:27:31 +09:30
Toby Zerner
53f7112248 Update beta 2 release date 2015-09-15 10:28:47 +09:30
Toby Zerner
a2def83045 Update dependencies 2015-09-14 18:45:49 +09:30
Toby Zerner
cbcad27679 Improve installer validation
Very rough, but works for now. The basic premise being that we need to
collect all user data before we proceed with installation.
2015-09-14 18:13:24 +09:30
Toby Zerner
9bf485359a Prevent XML from being interpreted as PHP short tags 2015-09-14 18:12:36 +09:30
Toby Zerner
60323e0cf9 Bump version number 2015-09-14 16:32:31 +09:30
Toby Zerner
8cccaaaf6b Improve API error handling
- Change 'path' key to 'source.pointer', as per spec
- Add 500 error detail if debug mode is on
2015-09-14 15:40:07 +09:30
Toby Zerner
b7d8afe6a4 Add doctrine/dbal dependency so that migrations can rename columns 2015-09-14 15:31:05 +09:30
Toby Zerner
ff8ec59310 Increase text contrast
closes #390
2015-09-14 15:16:19 +09:30
Toby Zerner
8eda6c7d36 Style disabled fields properly 2015-09-14 14:49:28 +09:30
Toby Zerner
d5b58b3146 Only set XHR authorization header if token isn't empty 2015-09-14 14:49:11 +09:30
Toby Zerner
f00d2b1363 Remove unused component 2015-09-14 14:48:05 +09:30
Toby Zerner
190aa925ac Set cookies to be HTTP only 2015-09-14 14:40:05 +09:30
Toby Zerner
60b19efe0a Password is not necessarily required
e.g. on my LAMP setup, I sometimes use a MySQL account without a
password
2015-09-14 14:39:18 +09:30
Franz Liedke
b2fa6b1a2e Add changelog 2015-09-12 19:11:03 +02:00
Franz Liedke
e7d7df3b0c Cleanup 2015-09-11 09:16:53 +02:00
Franz Liedke
3b5a01e603 Implement more validation in installer 2015-09-11 09:16:43 +02:00
Malay Ladu
b05f83d25a Add green color for online indicator on user cards
Closes #452.
2015-09-11 08:49:07 +02:00
Franz Liedke
902d01712b Remove pointless JSON-API action base class
Cleanup related to #118.
2015-09-09 09:04:49 +02:00
Franz Liedke
502a3787d5 Move remaining extension handling to middleware 2015-09-09 08:56:11 +02:00
Franz Liedke
b8ac49ffcc Move exception handling for Flarum exception classes to middleware
Related to #118.
2015-09-08 22:36:32 +02:00
Franz Liedke
4b4cea4d87 Implement interface to serialize exceptions to JSON-API format
Related to #118
2015-09-08 22:35:39 +02:00
Toby Zerner
c0e7ff5ea1 Give iframes in posts a max width 2015-09-08 11:51:36 +09:30
Toby Zerner
e54944d6c3 Reduce event post font size 2015-09-08 11:13:35 +09:30
Toby Zerner
d39bca192e Add more breathing room between posts and sidebar 2015-09-08 11:13:28 +09:30
Toby Zerner
efff485d6c Restyle posts to be more compact, relocate controls 2015-09-08 10:29:00 +09:30
Toby Zerner
6a5427b600 Make unread discussion titles less overwhelming 2015-09-08 10:27:50 +09:30
Toby Zerner
e8621636c5 Add init hook as a way to effectively monkey patch constructors
Related to #246
2015-09-08 10:27:02 +09:30
Toby Zerner
1aaff46f8e Increase text contrast slightly 2015-09-08 10:24:20 +09:30
Toby Zerner
8c4e095f23 Allow first post to be hidden/restored
Anti-spam extensions may automatically hide the first post in a
discussion, and thus we had to implement smarter permissions so
discussions with zero posts wouldn't be visible to users other than the
author/mods. This change allows those hidden posts to be restored again.
2015-09-07 16:03:45 +09:30
Toby Zerner
05c44ad2df Merge pull request #438 from mtotheikle/default_extension_build_script
Add a default build script for extensions
2015-09-07 11:01:33 +09:30
Toby Zerner
84012ca2fd Preliminary implementation of master API keys
Part of #205
2015-09-07 08:37:33 +09:30
Michael Williams
6393432d92 Add a default build script for extensions 2015-09-06 09:21:04 -07:00
Toby Zerner
f6e21b75e1 Remove unused translations 2015-09-05 16:05:02 +09:30
Toby Zerner
6ee9412f35 Prevent invalid LESS from crashing application
Failure is silent for now... The default LESS will compile without the
invalid customisations. Not sure if we should log an error somewhere
and display it on the admin page?

closes #400
2015-09-04 22:33:26 +09:30
Toby Zerner
478ca90c31 Fallback to English if system-wide default_locale doesn't exist 2015-09-04 22:19:28 +09:30
Toby Zerner
1f8f79d272 Don't require database password confirmation 2015-09-04 21:45:52 +09:30
Franz Liedke
85fc0a3129 Web installer: Fix name of table prefix field 2015-09-04 12:14:48 +02:00
Franz Liedke
db8b9ed0c0 Installer: Fix password confirmation 2015-09-04 12:11:13 +02:00
Franz Liedke
a3d59977b3 Clean up code 2015-09-04 12:05:12 +02:00
Franz Liedke
211d2d25cd Merge pull request #413 from WinterSilence/patch-2
Update RouteCollection::getPath
2015-09-04 12:03:51 +02:00
Franz Liedke
0a992ee9f2 Reorder installer fields 2015-09-04 12:00:39 +02:00
Franz Liedke
42f1abacaf Ask for password confirmation in web installer, too
Closes #405.
2015-09-04 12:00:03 +02:00
Franz Liedke
b26c67dd3c Require password confirmation in console installer
Refs #405.
2015-09-04 11:57:11 +02:00
Toby Zerner
fc7fc41383 Prevent error when hiding/restoring a post with a deleted user 2015-09-04 13:51:13 +09:30
Toby Zerner
a5d3aa9b36 Correctly style hidden post username 2015-09-04 13:50:43 +09:30
Toby Zerner
b18909f1af Fix notifications dropdown appearance on mobile 2015-09-04 13:50:33 +09:30
Toby Zerner
695df18be0 Don't show placeholder when loading discussions 2015-09-04 13:50:17 +09:30
Toby Zerner
ece23de750 API: Add User::hasPermissionLike() and User::getPermissions() 2015-09-04 12:23:50 +09:30
Toby Zerner
4705600d47 Fix typehint 2015-09-04 12:23:27 +09:30
Toby Zerner
8423de754c Fix bad query in isVisibleTo 2015-09-04 12:23:17 +09:30
Toby Zerner
b597e6f8f6 Don't load a custom relation if the relation is already loaded 2015-09-04 12:22:49 +09:30
Toby Zerner
276334ec52 Improve some post/discussion permission logic
- Allow users to see their own posts, even if they have been hidden by
someone else
- Don't require hiding a post to be necessarily attributed to a user
- Hide discussions with zero posts, unless the user can edit posts, or
they are the discussion author
2015-09-04 12:22:27 +09:30
Toby Zerner
9277fca0ec Slightly darken light grey text 2015-09-04 12:19:20 +09:30
Toby Zerner
9ca67635fb Remove unused translations 2015-09-04 12:19:09 +09:30
Toby Zerner
7a6c48c30b Correct check that a translation is an object
typeof translation === 'object' returns true when translation is null
2015-09-04 12:19:02 +09:30
Toby Zerner
f0186d7674 API: Add typehints 2015-09-04 12:18:09 +09:30
Toby Zerner
9bf6862c6d Clean up Post CSS 2015-09-04 12:17:30 +09:30
Toby Zerner
44f460cb11 Prevent ItemList crash when item is a number 2015-09-04 12:17:01 +09:30
Toby Zerner
7cce5b02ba Allow non-array value to be passed into listItems
Useful in some scenarios when using JSX
2015-09-04 12:16:23 +09:30
Toby Zerner
722058f2fb Move generic util into lib
Might come in handy for the admin section later on
2015-09-04 12:15:41 +09:30
Toby Zerner
70815b024a Make Dropdown and NotificationsDropdown components more extensible 2015-09-04 12:15:11 +09:30
Toby Zerner
7269385786 Make a copy of props passed into a component
Prevents some rare errors where the props object is read-only, and is
generally safer.
2015-09-04 12:13:55 +09:30
Toby Zerner
2f8a449b74 Simplify notification markup 2015-09-04 12:12:48 +09:30
Toby Zerner
b3aa0298d5 Fix use of "new" keyword making eslint angry 2015-09-04 12:12:21 +09:30
Toby Zerner
e192402a42 Add item priorities 2015-09-04 12:11:45 +09:30
Toby Zerner
c81ceafb54 Clean up editorconfig, eslint, npm dependencies 2015-09-04 12:11:34 +09:30
Toby Zerner
93b6f11484 Merge pull request #418 from Luceos/registration_fix
call to $this-> assertValidPassword from static context
2015-09-04 10:46:35 +09:30
Daniel Klabbers
0413daab74 call to $this-> assertValidPassword from static context 2015-09-04 00:00:24 +02:00
Franz Liedke
f0c240f863 Add a first empty state to the discussion list 2015-09-03 09:59:33 +02:00
Franz Liedke
21dd516eaa Fix code style issues 2015-09-03 08:48:26 +02:00
Franz Liedke
3c9d851889 Check prerequisites in console installer, too 2015-09-03 08:42:16 +02:00
Franz Liedke
942db77416 Extract installation prerequisites into composable classes and use those in the web-based installer 2015-09-03 08:23:34 +02:00
Anton
04db806995 Update RouteCollection.php 2015-09-02 19:22:40 +03:00
Anton
f3bc7d1c23 Update RouteCollection::getPath
This version work faster - old code create closure at every calling getPath
2015-09-02 10:58:44 +03:00
Franz Liedke
bd47653377 Merge pull request #403 from mtotheikle/allow-extra-signup-data
Allow support for passing extra signup data to API
2015-09-01 18:28:53 +02:00
Michael Williams
07ed4d10c0 Allow support for passing extra signup data to API 2015-09-01 07:58:14 -07:00
Franz Liedke
25141c0f2f Merge pull request #402 from johannsa/master
Fixes enable extension in EntensionManager
2015-09-01 14:58:58 +02:00
Johann Rodríguez
e35bb9e400 Fix enable extension in EntensionManager 2015-09-01 12:09:11 +01:00
Franz Liedke
753a846e7a Check MySQL version when installing on console
Related to #364.
2015-09-01 08:02:07 +02:00
Franz Liedke
d3e57d77b4 Fix typehint 2015-09-01 07:49:06 +02:00
Toby Zerner
6e0bffe395 API: Add more locale registration APIs 2015-09-01 10:08:37 +09:30
Toby Zerner
eec4e97d65 Tidy up default extension metadata 2015-09-01 10:08:37 +09:30
Toby Zerner
bf83b36882 Remove redundant call 2015-09-01 10:08:37 +09:30
Toby Zerner
6aafe54ee7 Fix potential error when discussion doesn't exist
Not sure how this could be the case, but can't hurt to add the checks.
addresses #343
2015-09-01 10:08:36 +09:30
Franz Liedke
c91f8de1f5 Be more consistent in case AJAX does not work 2015-08-31 22:37:04 +02:00
Franz Liedke
5783dbe77b Try to fix Safari bug during installation 2015-08-31 22:35:12 +02:00
Franz Liedke
ab496eb8f8 Merge pull request #387 from rodymol123/master
Align select input properly
2015-08-31 22:04:46 +02:00
Rody Molenaar
6f13a246db Align select 2015-08-31 21:57:43 +02:00
Franz Liedke
4c34d0867d Add field for table prefix in web installer
Related to #269.
2015-08-31 09:10:27 +02:00
Toby Zerner
f2a3a0cb10 Require the PHP fileinfo extension
It's required for the intervention/image package
2015-08-31 15:29:20 +09:30
Toby Zerner
5b7527144c Permit trailing slashes in discussion/user URLs
closes #334
2015-08-31 14:43:42 +09:30
Toby Zerner
6c169499b5 Only migrate enabled extensions when upgrading
Also remove the Extension::install() and Extension::uninstall()
methods, because they add nothing that can't be done with migrations.
2015-08-31 14:35:52 +09:30
Toby Zerner
5e22458014 Installer: Prevent crash when views directory is not writable
Use plain PHP templates instead of Blade templates so there is nothing
that needs to be written.

closes #376
2015-08-31 14:25:16 +09:30
Toby Zerner
c72bdc8238 Fix Laravel 5.1 compat
closes #307
2015-08-31 14:07:11 +09:30
Toby Zerner
2438bbfd41 Reload post if user relationship isn't loaded
May fix #295, but haven't been able to reproduce/test.
2015-08-31 14:03:08 +09:30
Toby Zerner
5af5f1fc77 Properly style modal title bar on mobile
closes #286
2015-08-31 13:46:59 +09:30
Toby Zerner
e7f4e5060c Use GroupBadge component to display user badges
closes #277
2015-08-31 13:44:05 +09:30
Toby Zerner
bcc16a3329 Add target="_blank" and rel="nofollow" to all formatted links
closes #247
2015-08-31 13:36:54 +09:30
Toby Zerner
283abb88c2 Fix reply composer preview button
closes #238
2015-08-31 13:27:04 +09:30
Toby Zerner
af2307868a Fix JavaScript style 2015-08-31 13:19:51 +09:30
Toby Zerner
f9d724738c Add syntax highlighting for code blocks
We might consider extracting this into an extension, but TextFormatter
does syntax highlighting for code blocks by default in live previews
anyway.

closes #248
2015-08-31 13:17:16 +09:30
Toby Zerner
42e722d824 Fix incorrect translation output
app.trans returns a VirtualElement, so there's no need to wrap it in
m.trust.

closes #237
2015-08-31 12:41:36 +09:30
Toby Zerner
f5517fbd88 Validate password length
We can't do this using the ValidatesBeforeSave trait because the
password has been hashed by then. Instead, we must validate the
original password as it comes in.
2015-08-31 12:38:15 +09:30
Toby Zerner
6a0e3fcf2d Validate post length to prevent truncation
closes #235
2015-08-31 12:36:19 +09:30
Toby Zerner
0ae2d18f28 Extract base Page class 2015-08-31 12:05:33 +09:30
Toby Zerner
0474f410a4 Refactor start/endComputation into lazyRedraw method 2015-08-31 12:04:51 +09:30
Toby Zerner
9f28b4e8dc Require extensions directory to be writable 2015-08-31 11:22:57 +09:30
Toby Zerner
f44e9f5140 Remove <script> tags from plain post content
closes #362
2015-08-31 10:49:24 +09:30
Franz Liedke
3e14ef0714 Fix last commit 2015-08-30 15:02:48 +02:00
Franz Liedke
c999226449 Travis: Use Composer scripts 2015-08-30 15:00:01 +02:00
Franz Liedke
ba097dc147 Add Composer scripts 2015-08-30 14:59:10 +02:00
Franz Liedke
1d1cc9e443 Fix asset URL generation
This is important when Flarum is deployed in a subfolder.

Closes #291.
2015-08-29 22:38:31 +02:00
Franz Liedke
f5d2d2ff79 Installer: Check for openssl extension
Closes #296.
2015-08-29 22:07:50 +02:00
Franz Liedke
a04acca92e Allow Ctrl key for submitting posts, too
Closes #276.
2015-08-29 17:09:14 +02:00
Toby Zerner
4033319ed0 Merge pull request #338 from lbausch/discussion_validation
Add validation to limit discussion title length
2015-08-29 18:58:42 +09:30
Lorenz Bausch
a4fe6f3ce3 limit max title length to 80 characters 2015-08-29 11:26:18 +02:00
Lorenz Bausch
ae06b45bc1 remove executable flag from file 2015-08-29 11:23:05 +02:00
Toby Zerner
be33761950 Include the extension stub's .gitignore file 2015-08-29 18:29:33 +09:30
Toby Zerner
015aaaa899 Add CONTRIBUTING
(copied from flarum/flarum)
2015-08-29 18:29:19 +09:30
Toby Zerner
67f6b8599d Allow string primitives to be added to ItemLists 2015-08-29 15:25:36 +09:30
Toby Zerner
12d5e48b95 Add helpful hint on how to upgrade PHP 2015-08-29 14:44:55 +09:30
Toby Zerner
a41e3e66ce Merge pull request #299 from huytd/fix-wordwrap
Fix text overlap in search result with long content
2015-08-29 08:31:37 +09:30
Huy Tran
874c023f8a Fix text overlap in search result with long content 2015-08-28 15:18:17 -07:00
Toby Zerner
bb3c57f9a4 Fix default database name 2015-08-28 14:53:01 +09:30
Toby Zerner
98a79e957d Merge pull request #278 from huytd/https-web-font
Make Google Web Font work on both HTTP and HTTPS
2015-08-28 12:50:12 +09:30
Huy Tran
cf68c95fb8 Make Google Web Font work on both HTTP and HTTPS 2015-08-27 20:17:42 -07:00
Toby Zerner
d5074c5286 Use gd as the image driver
Presumably gd is more common than imagick, and we already check for it
during installation.
2015-08-28 05:41:25 +09:30
Toby Zerner
41019597d0 Require the PHP DOM extension 2015-08-28 05:30:27 +09:30
Toby Zerner
b689c9de3b Allow non-admins to reset their password
The EditUser command requires the actor to have the "edit" permission,
which is only granted to admins. We don't want to allow users to change
their own password via the API, though. So instead of dispatching the
command, we'll just update the user's password directly in the action.
2015-08-28 03:38:55 +09:30
Toby Zerner
baed659668 Fix reset password error 2015-08-28 02:16:28 +09:30
773 changed files with 80222 additions and 18114 deletions

View File

@@ -12,21 +12,8 @@ insert_final_newline = true
indent_style = space
indent_size = 2
[*.js]
indent_style = space
indent_size = 2
[*.{css,less}]
indent_style = space
indent_size = 2
[*.html]
indent_style = space
indent_size = 2
[*.{diff,md}]
trim_trailing_whitespace = false
[*.php]
indent_style = space
indent_size = 4

View File

@@ -1,5 +0,0 @@
**/bower_components/**/*
**/node_modules/**/*
vendor/**/*
**/Gulpfile.js
**/dist/**/*

170
.eslintrc
View File

@@ -1,170 +0,0 @@
{
"parser": "babel-eslint", // https://github.com/babel/babel-eslint
"env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments
"browser": true // browser global variables
},
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"classes": true,
"defaultParams": true,
"destructuring": true,
"forOf": true,
"generators": false,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": false,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"spread": true,
"superInFunctions": true,
"templateStrings": true,
"jsx": true
},
"globals": {
"m": true,
"app": true,
"$": true,
"moment": true
},
"rules": {
/**
* Strict mode
*/
// babel inserts "use strict"; for us
"strict": [2, "never"], // http://eslint.org/docs/rules/strict
/**
* ES6
*/
"no-var": 2, // http://eslint.org/docs/rules/no-var
"prefer-const": 2, // http://eslint.org/docs/rules/prefer-const
/**
* Variables
*/
"no-shadow": 2, // http://eslint.org/docs/rules/no-shadow
"no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
"no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars
"vars": "local",
"args": "after-used"
}],
"no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
/**
* Possible errors
*/
"comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle
"no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
"no-console": 1, // http://eslint.org/docs/rules/no-console
"no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
"no-alert": 1, // http://eslint.org/docs/rules/no-alert
"no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
"no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
"no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
"no-empty": 2, // http://eslint.org/docs/rules/no-empty
"no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
"no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
"no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
"no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
"no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
"no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
"no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
"no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
"no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys
"no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
"no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
"use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
"block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
/**
* Best practices
*/
"consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
"curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly
"default-case": 2, // http://eslint.org/docs/rules/default-case
"dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation
"allowKeywords": true
}],
"eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
"no-caller": 2, // http://eslint.org/docs/rules/no-caller
"no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
"no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
"no-eval": 2, // http://eslint.org/docs/rules/no-eval
"no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
"no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
"no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
"no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
"no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
"no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
"no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
"no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
"no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
"no-new": 2, // http://eslint.org/docs/rules/no-new
"no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
"no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
"no-octal": 2, // http://eslint.org/docs/rules/no-octal
"no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
"no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
"no-proto": 2, // http://eslint.org/docs/rules/no-proto
"no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
"no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
"no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
"no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
"no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
"no-with": 2, // http://eslint.org/docs/rules/no-with
"radix": 2, // http://eslint.org/docs/rules/radix
"vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
"wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife
"yoda": 2, // http://eslint.org/docs/rules/yoda
/**
* Style
*/
"indent": [2, 2], // http://eslint.org/docs/rules/indent
"brace-style": [2, // http://eslint.org/docs/rules/brace-style
"1tbs", {
"allowSingleLine": true
}],
"quotes": [
2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes
],
"camelcase": [2, { // http://eslint.org/docs/rules/camelcase
"properties": "never"
}],
"comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing
"before": false,
"after": true
}],
"comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style
"eol-last": 2, // http://eslint.org/docs/rules/eol-last
"key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing
"beforeColon": false,
"afterColon": true
}],
"new-cap": [2, { // http://eslint.org/docs/rules/new-cap
"newIsCap": true
}],
"no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines
"max": 2
}],
"no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
"no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
"no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
"no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func
"no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
"one-var": [2, "never"], // http://eslint.org/docs/rules/one-var
"padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks
"semi": [2, "always"], // http://eslint.org/docs/rules/semi
"semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing
"before": false,
"after": true
}],
"space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords
"space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
"space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren
"space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
"space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case
"spaced-line-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment
}
}

7
.gitattributes vendored
View File

@@ -2,3 +2,10 @@
.gitignore export-ignore
.gitmodules export-ignore
.travis.yml export-ignore
.editorconfig export-ignore
.styleci.yml export-ignore
phpunit.xml export-ignore
tests export-ignore
js/*/dist/*.js -diff

3
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,3 @@
# Contributing to Flarum
Howdy! We're really excited that you are interested in contributing to Flarum. Before submitting your contribution, please take a moment and read through the [Contributing Guidelines](https://github.com/flarum/flarum/blob/master/CONTRIBUTING.md).

26
.github/ISSUE_TEMPLATE.md vendored Normal file
View 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.
```

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ composer.phar
Thumbs.db
tests/_output/*
.vagrant
.idea/*

26
.php_cs
View File

@@ -1,26 +0,0 @@
<?php
$header = <<<EOF
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.
EOF;
Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header);
$finder = Symfony\CS\Finder\DefaultFinder::create()
->exclude('stubs')
->in(__DIR__);
return Symfony\CS\Config\Config::create()
->setUsingCache(true)
->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
->fixers([
'short_array_syntax',
'header_comment',
'-psr0'
])
->finder($finder);

18
.styleci.yml Normal file
View File

@@ -0,0 +1,18 @@
preset: recommended
enabled:
- logical_not_operators_with_successor_space
disabled:
- align_double_arrow
- blank_line_after_opening_tag
- multiline_array_trailing_comma
- new_with_braces
- phpdoc_align
- phpdoc_order
- phpdoc_separation
- phpdoc_types
finder:
exclude:
- "stubs"

View File

@@ -1,8 +1,10 @@
language: php
php:
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
matrix:
allow_failures:
@@ -10,12 +12,12 @@ matrix:
fast_finish: true
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi;
- composer self-update
- composer install
script:
- vendor/bin/phpcs --standard=PSR2 -np src
- vendor/bin/phpspec run
- vendor/bin/phpunit --coverage-clover=coverage.xml
notifications:
email:
@@ -27,4 +29,7 @@ notifications:
on_failure: always
on_start: false
after_success:
- bash <(curl -s https://codecov.io/bash)
sudo: false

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2015 Toby Zerner
Copyright (c) 2014-2017 Toby Zerner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
# Flarum Core
This repository contains the core code of Flarum. If you want to install Flarum, visit the [main Flarum repository](http://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
Interested in contributing to Flarum? Please read the [Contributing docs](http://flarum.org/docs/contributing) 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.

View File

@@ -1,20 +1,36 @@
{
"name": "flarum/core",
"description": "",
"description": "Delightfully simple forum software.",
"keywords": ["forum", "discussion"],
"homepage": "http://flarum.org",
"license": "MIT",
"authors": [
{
"name": "Toby Zerner",
"email": "toby@flarum.org"
"email": "toby.zerner@gmail.com"
},
{
"name": "Franz Liedke",
"email": "franz@develophp.org"
}
],
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/core",
"docs": "http://flarum.org/docs"
},
"require": {
"php": ">=5.4.0",
"php": ">=5.6.0",
"dflydev/fig-cookies": "^1.0.2",
"doctrine/dbal": "^2.5",
"components/font-awesome": "^4.6",
"franzl/whoops-middleware": "^0.4.0",
"illuminate/bus": "5.1.*",
"illuminate/cache": "5.1.*",
"illuminate/config": "5.1.*",
"illuminate/container": "5.1.*",
"illuminate/contracts": "5.1.*",
"illuminate/database": "5.1.*",
"illuminate/database": "^5.1.31",
"illuminate/events": "5.1.*",
"illuminate/filesystem": "5.1.*",
"illuminate/hashing": "5.1.*",
@@ -22,21 +38,26 @@
"illuminate/support": "5.1.*",
"illuminate/validation": "5.1.*",
"illuminate/view": "5.1.*",
"league/flysystem": "^1.0.11",
"tobscure/json-api": "^0.1.1",
"oyejorge/less.php": "~1.5",
"intervention/image": "^2.3.0",
"s9e/text-formatter": "^0.1.0",
"psr/http-message": "^1.0",
"zendframework/zend-diactoros": "^1.1",
"league/flysystem": "^1.0.11",
"league/oauth2-client": "~1.0",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "^1.16.0",
"nikic/fast-route": "^0.6",
"dflydev/fig-cookies": "^1.0",
"oyejorge/less.php": "~1.5",
"psr/http-message": "^1.0",
"symfony/console": "^2.7",
"symfony/yaml": "^2.7"
"symfony/http-foundation": "^2.7",
"symfony/translation": "^2.7",
"symfony/yaml": "^2.7",
"s9e/text-formatter": "^0.8.1",
"tobscure/json-api": "^0.3.0",
"zendframework/zend-diactoros": "^1.1",
"zendframework/zend-stratigility": "^1.3"
},
"require-dev": {
"squizlabs/php_codesniffer": "2.*",
"phpspec/phpspec": "^2.2"
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^4.8"
},
"autoload": {
"psr-4": {
@@ -45,5 +66,15 @@
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
}
}
}

2392
composer.lock generated

File diff suppressed because it is too large Load Diff

13
error/403.html Normal file
View 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
View 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
View 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
View 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>

2
js/admin/.gitignore vendored
View File

@@ -1,3 +1 @@
node_modules
mithril.js
dist

View File

@@ -1,15 +1,14 @@
var gulp = require('flarum-gulp');
var nodeDir = 'node_modules';
var bowerDir = '../bower_components';
gulp({
includeHelpers: true,
files: [
nodeDir + '/babel-core/external-helpers.js',
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',
@@ -28,6 +27,5 @@ gulp({
'../lib/**/*.js'
]
},
externalHelpers: true,
outputFile: 'dist/app.js'
});

23990
js/admin/dist/app.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
{
"private": true,
"devDependencies": {
"gulp": "^3.8.11",
"flarum-gulp": "git+https://github.com/flarum/gulp.git",
"babel-core": "^5.0.0"
"gulp": "^3.9.1",
"flarum-gulp": "^0.2.0"
}
}

View File

@@ -14,4 +14,20 @@ 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;

View File

@@ -15,15 +15,15 @@ export default class AddExtensionModal extends Modal {
}
title() {
return 'Add Extension';
return app.translator.trans('core.admin.add_extension.title');
}
content() {
return (
<div className="Modal-body">
<p>One day in the not-too-distant future, this dialog will allow you to add an extension to your forum with ease. We're building an ecosystem as we speak!</p>
<p>In the meantime, if you manage to get your hands on a new extension, simply drop it in your forum's <code>extensions</code> directory.</p>
<p>If you're a developer, you can <a href="http://flarum.org/docs/extend">read the docs</a> and have a go at building your own.</p>
<p>{app.translator.trans('core.admin.add_extension.temporary_text')}</p>
<p>{app.translator.trans('core.admin.add_extension.install_text', {a: <a href="https://discuss.flarum.org/t/extensions" target="_blank"/>})}</p>
<p>{app.translator.trans('core.admin.add_extension.developer_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</p>
</div>
);
}

View File

@@ -35,36 +35,43 @@ export default class AdminNav extends Component {
items.add('dashboard', AdminLinkButton.component({
href: app.route('dashboard'),
icon: 'bar-chart',
children: 'Dashboard',
description: 'Your forum at a glance.'
children: app.translator.trans('core.admin.nav.dashboard_button'),
description: app.translator.trans('core.admin.nav.dashboard_text')
}));
items.add('basics', AdminLinkButton.component({
href: app.route('basics'),
icon: 'pencil',
children: 'Basics',
description: 'Set your forum title, language, and other basic settings.'
children: app.translator.trans('core.admin.nav.basics_button'),
description: app.translator.trans('core.admin.nav.basics_text')
}));
items.add('mail', AdminLinkButton.component({
href: app.route('mail'),
icon: 'envelope',
children: app.translator.trans('core.admin.nav.email_button'),
description: app.translator.trans('core.admin.nav.email_text')
}));
items.add('permissions', AdminLinkButton.component({
href: app.route('permissions'),
icon: 'key',
children: 'Permissions',
description: 'Configure who can see and do what.'
children: app.translator.trans('core.admin.nav.permissions_button'),
description: app.translator.trans('core.admin.nav.permissions_text')
}));
items.add('appearance', AdminLinkButton.component({
href: app.route('appearance'),
icon: 'paint-brush',
children: 'Appearance',
description: 'Customize your forum\'s colors, logos, and other variables.'
children: app.translator.trans('core.admin.nav.appearance_button'),
description: app.translator.trans('core.admin.nav.appearance_text')
}));
items.add('extensions', AdminLinkButton.component({
href: app.route('extensions'),
icon: 'puzzle-piece',
children: 'Extensions',
description: 'Add extra functionality to your forum and make it your own.'
children: app.translator.trans('core.admin.nav.extensions_button'),
description: app.translator.trans('core.admin.nav.extensions_text')
}));
return items;

View File

@@ -1,17 +1,19 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
import Button from 'flarum/components/Button';
import Switch from 'flarum/components/Switch';
import EditCustomCssModal from 'flarum/components/EditCustomCssModal';
import saveConfig from 'flarum/utils/saveConfig';
import EditCustomHeaderModal from 'flarum/components/EditCustomHeaderModal';
import UploadImageButton from 'flarum/components/UploadImageButton';
import saveSettings from 'flarum/utils/saveSettings';
export default class AppearancePage extends Component {
constructor(...args) {
super(...args);
export default class AppearancePage extends Page {
init() {
super.init();
this.primaryColor = m.prop(app.config.theme_primary_color);
this.secondaryColor = m.prop(app.config.theme_secondary_color);
this.darkMode = m.prop(app.config.theme_dark_mode === '1');
this.coloredHeader = m.prop(app.config.theme_colored_header === '1');
this.primaryColor = m.prop(app.data.settings.theme_primary_color);
this.secondaryColor = m.prop(app.data.settings.theme_secondary_color);
this.darkMode = m.prop(app.data.settings.theme_dark_mode === '1');
this.coloredHeader = m.prop(app.data.settings.theme_colored_header === '1');
}
view() {
@@ -20,44 +22,73 @@ export default class AppearancePage extends Component {
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
<fieldset className="AppearancePage-colors">
<legend>Colors</legend>
<legend>{app.translator.trans('core.admin.appearance.colors_heading')}</legend>
<div className="helpText">
Choose two colors to theme your forum with. The first will be used as a highlight color, while the second will be used to style background elements.
{app.translator.trans('core.admin.appearance.colors_text')}
</div>
<div className="AppearancePage-colors-input">
<input className="FormControl" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
<input className="FormControl" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
<input className="FormControl" type="color" placeholder="#aaaaaa" value={this.primaryColor()} onchange={m.withAttr('value', this.primaryColor)}/>
<input className="FormControl" type="color" placeholder="#aaaaaa" value={this.secondaryColor()} onchange={m.withAttr('value', this.secondaryColor)}/>
</div>
{Switch.component({
state: this.darkMode(),
children: 'Dark Mode',
children: app.translator.trans('core.admin.appearance.dark_mode_label'),
onchange: this.darkMode
})}
{Switch.component({
state: this.coloredHeader(),
children: 'Colored Header',
children: app.translator.trans('core.admin.appearance.colored_header_label'),
onchange: this.coloredHeader
})}
{Button.component({
className: 'Button Button--primary',
children: 'Save Changes',
type: 'submit',
children: app.translator.trans('core.admin.appearance.submit_button'),
loading: this.loading
})}
</fieldset>
</form>
<fieldset>
<legend>Custom Styles</legend>
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
<div className="helpText">
Customize your forum's appearance by adding your own LESS/CSS code to be applied on top of Flarum's default styles.
{app.translator.trans('core.admin.appearance.logo_text')}
</div>
<UploadImageButton name="logo"/>
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.favicon_text')}
</div>
<UploadImageButton name="favicon"/>
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.custom_header_text')}
</div>
{Button.component({
className: 'Button',
children: 'Edit Custom CSS',
children: app.translator.trans('core.admin.appearance.edit_header_button'),
onclick: () => app.modal.show(new EditCustomHeaderModal())
})}
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.custom_styles_text')}
</div>
{Button.component({
className: 'Button',
children: app.translator.trans('core.admin.appearance.edit_css_button'),
onclick: () => app.modal.show(new EditCustomCssModal())
})}
</fieldset>
@@ -72,13 +103,13 @@ export default class AppearancePage extends Component {
const hex = /^#[0-9a-f]{3}([0-9a-f]{3})?$/i;
if (!hex.test(this.primaryColor()) || !hex.test(this.secondaryColor())) {
alert('Please enter a hexadecimal color code.');
alert(app.translator.trans('core.admin.appearance.enter_hex_message'));
return;
}
this.loading = true;
saveConfig({
saveSettings({
theme_primary_color: this.primaryColor(),
theme_secondary_color: this.secondaryColor(),
theme_dark_mode: this.darkMode(),

View File

@@ -1,13 +1,15 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
import FieldSet from 'flarum/components/FieldSet';
import Select from 'flarum/components/Select';
import Button from 'flarum/components/Button';
import Alert from 'flarum/components/Alert';
import saveConfig from 'flarum/utils/saveConfig';
import saveSettings from 'flarum/utils/saveSettings';
import ItemList from 'flarum/utils/ItemList';
import Switch from 'flarum/components/Switch';
export default class BasicsPage extends Component {
constructor(...args) {
super(...args);
export default class BasicsPage extends Page {
init() {
super.init();
this.loading = false;
@@ -15,20 +17,23 @@ export default class BasicsPage extends Component {
'forum_title',
'forum_description',
'default_locale',
'show_language_selector',
'default_route',
'welcome_title',
'welcome_message'
];
this.values = {};
const config = app.config;
this.fields.forEach(key => this.values[key] = m.prop(config[key]));
const settings = app.data.settings;
this.fields.forEach(key => this.values[key] = m.prop(settings[key] || false));
this.localeOptions = {};
const locales = app.locales;
const locales = app.data.locales;
for (const i in locales) {
this.localeOptions[i] = `${locales[i]} (${i})`;
}
if (typeof this.values.show_language_selector() !== "number") this.values.show_language_selector(1);
}
view() {
@@ -37,17 +42,17 @@ export default class BasicsPage extends Component {
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
{FieldSet.component({
label: 'Forum Title',
label: app.translator.trans('core.admin.basics.forum_title_heading'),
children: [
<input className="FormControl" value={this.values.forum_title()} oninput={m.withAttr('value', this.values.forum_title)}/>
]
})}
{FieldSet.component({
label: 'Forum Description',
label: app.translator.trans('core.admin.basics.forum_description_heading'),
children: [
<div className="helpText">
Enter a short sentence or two that describes your community. This will appear in the meta tag and show up in search engines.
{app.translator.trans('core.admin.basics.forum_description_text')}
</div>,
<textarea className="FormControl" value={this.values.forum_description()} oninput={m.withAttr('value', this.values.forum_description)}/>
]
@@ -55,44 +60,44 @@ export default class BasicsPage extends Component {
{Object.keys(this.localeOptions).length > 1
? FieldSet.component({
label: 'Default Language',
label: app.translator.trans('core.admin.basics.default_language_heading'),
children: [
Select.component({
options: this.localeOptions,
value: this.values.default_locale(),
onchange: this.values.default_locale
}),
Switch.component({
state: this.values.show_language_selector(),
onchange: this.values.show_language_selector,
children: app.translator.trans('core.admin.basics.show_language_selector_label'),
})
]
})
: ''}
{FieldSet.component({
label: 'Home Page',
label: app.translator.trans('core.admin.basics.home_page_heading'),
className: 'BasicsPage-homePage',
children: [
<div className="helpText">
Choose the page which users will first see when they visit your forum. If entering a custom value, use the path relative to the forum root.
{app.translator.trans('core.admin.basics.home_page_text')}
</div>,
<label className="checkbox">
<input type="radio" name="homePage" value="/all" checked={this.values.default_route() === '/all'} onclick={m.withAttr('value', this.values.default_route)}/>
All Discussions
</label>,
<label className="checkbox">
<input type="radio" name="homePage" value="custom" checked={this.values.default_route() !== '/all'} onclick={() => {
this.values.default_route('');
m.redraw(true);
this.$('.BasicsPage-homePage input').select();
}}/>
Custom <input className="FormControl" value={this.values.default_route()} oninput={m.withAttr('value', this.values.default_route)} style={this.values.default_route() !== '/all' ? 'margin-top: 5px' : 'display:none'}/>
</label>
this.homePageItems().toArray().map(({path, label}) =>
<label className="checkbox">
<input type="radio" name="homePage" value={path} checked={this.values.default_route() === path} onclick={m.withAttr('value', this.values.default_route)}/>
{label}
</label>
)
]
})}
{FieldSet.component({
label: 'Welcome Banner',
label: app.translator.trans('core.admin.basics.welcome_banner_heading'),
className: 'BasicsPage-welcomeBanner',
children: [
<div className="helpText">
Configure the text that displays in the banner on the All Discussions page. Use this to welcome guests to your forum.
{app.translator.trans('core.admin.basics.welcome_banner_text')}
</div>,
<div className="BasicsPage-welcomeBanner-input">
<input className="FormControl" value={this.values.welcome_title()} oninput={m.withAttr('value', this.values.welcome_title)}/>
@@ -104,7 +109,7 @@ export default class BasicsPage extends Component {
{Button.component({
type: 'submit',
className: 'Button Button--primary',
children: 'Save Changes',
children: app.translator.trans('core.admin.basics.submit_button'),
loading: this.loading,
disabled: !this.changed()
})}
@@ -115,9 +120,25 @@ export default class BasicsPage extends Component {
}
changed() {
const config = app.config;
return this.fields.some(key => this.values[key]() !== app.data.settings[key]);
}
return this.fields.some(key => this.values[key]() !== config[key]);
/**
* Build a list of options for the default homepage. Each option must be an
* object with `path` and `label` properties.
*
* @return {ItemList}
* @public
*/
homePageItems() {
const items = new ItemList();
items.add('allDiscussions', {
path: '/all',
label: app.translator.trans('core.admin.basics.all_discussions_label')
});
return items;
}
onsubmit(e) {
@@ -128,15 +149,16 @@ export default class BasicsPage extends Component {
this.loading = true;
app.alerts.dismiss(this.successAlert);
const config = {};
const settings = {};
this.fields.forEach(key => config[key] = this.values[key]());
this.fields.forEach(key => settings[key] = this.values[key]());
saveConfig(config)
saveSettings(settings)
.then(() => {
app.alerts.show(this.successAlert = new Alert({type: 'success', children: 'Your changes were saved.'}));
app.alerts.show(this.successAlert = new Alert({type: 'success', children: app.translator.trans('core.admin.basics.saved_message')}));
})
.finally(() => {
.catch(() => {})
.then(() => {
this.loading = false;
m.redraw();
});

View File

@@ -1,18 +1,19 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
export default class DashboardPage extends Component {
export default class DashboardPage extends Page {
view() {
return (
<div className="DashboardPage">
<div className="container">
<h2>Welcome to Flarum Beta</h2>
<p>Thanks for trying out Flarum! You are running version <strong>{app.forum.attribute('version')}</strong>. This is beta software, and should not be used in production.</p>
<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>Having problems? Read the <a href="http://flarum.org/docs/troubleshooting" target="_blank">Troubleshooting docs</a>.</li>
<li>Found a bug? Please <a href="https://github.com/flarum/core/issues" target="_blank">report it on GitHub</a>.</li>
<li>Got some feedback? Let us know what you think on the <a href="http://discuss.flarum.org" target="_blank">Support Forum</a>.</li>
<li>Want to contribute? Read the <a href="http://flarum.org/docs/contributing" target="_blank">Contributing docs</a>.</li>
<li>Interested in developing extensions? Read the <a href="http://flarum.org/docs/extend" target="_blank">Extension docs</a>.</li>
<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>

View File

@@ -1,51 +1,24 @@
import Modal from 'flarum/components/Modal';
import Button from 'flarum/components/Button';
import saveConfig from 'flarum/utils/saveConfig';
export default class EditCustomCssModal extends Modal {
constructor(...args) {
super(...args);
this.customLess = m.prop(app.config.custom_less || '');
}
import SettingsModal from 'flarum/components/SettingsModal';
export default class EditCustomCssModal extends SettingsModal {
className() {
return 'EditCustomCssModal Modal--large';
}
title() {
return 'Edit Custom CSS';
return app.translator.trans('core.admin.edit_css.title');
}
content() {
return (
<div className="Modal-body">
<p>Customize your forum's appearance by adding your own LESS/CSS code to be applied on top of Flarum's default styles. <a href="">Read the documentation</a> for more information.</p>
<div className="Form">
<div className="Form-group">
<textarea className="FormControl" rows="30" value={this.customLess()} onchange={m.withAttr('value', this.customLess)}/>
</div>
<div className="Form-group">
{Button.component({
className: 'Button Button--primary',
children: 'Save Changes',
loading: this.loading
})}
</div>
</div>
form() {
return [
<p>{app.translator.trans('core.admin.edit_css.customize_text', {a: <a href="https://github.com/flarum/core/tree/master/less" target="_blank"/>})}</p>,
<div className="Form-group">
<textarea className="FormControl" rows="30" bidi={this.setting('custom_less')}/>
</div>
);
];
}
onsubmit(e) {
e.preventDefault();
this.loading = true;
saveConfig({
custom_less: this.customLess()
}).then(() => window.location.reload());
onsaved() {
window.location.reload();
}
}

View File

@@ -0,0 +1,24 @@
import SettingsModal from 'flarum/components/SettingsModal';
export default class EditCustomHeaderModal extends SettingsModal {
className() {
return 'EditCustomHeaderModal Modal--large';
}
title() {
return app.translator.trans('core.admin.edit_header.title');
}
form() {
return [
<p>{app.translator.trans('core.admin.edit_header.customize_text')}</p>,
<div className="Form-group">
<textarea className="FormControl" rows="30" bidi={this.setting('custom_header')}/>
</div>
];
}
onsaved() {
window.location.reload();
}
}

View File

@@ -8,9 +8,7 @@ import Group from 'flarum/models/Group';
* to create or edit a group.
*/
export default class EditGroupModal extends Modal {
constructor(...args) {
super(...args);
init() {
this.group = this.props.group || app.store.createRecord('groups');
this.nameSingular = m.prop(this.group.nameSingular() || '');
@@ -30,7 +28,7 @@ export default class EditGroupModal extends Modal {
style: {backgroundColor: this.color()}
}) : '',
' ',
this.namePlural() || 'Create Group'
this.namePlural() || app.translator.trans('core.admin.edit_group.title')
];
}
@@ -39,22 +37,22 @@ export default class EditGroupModal extends Modal {
<div className="Modal-body">
<div className="Form">
<div className="Form-group">
<label>Name</label>
<label>{app.translator.trans('core.admin.edit_group.name_label')}</label>
<div className="EditGroupModal-name-input">
<input className="FormControl" placeholder="Singular (e.g. Mod)" value={this.nameSingular()} oninput={m.withAttr('value', this.nameSingular)}/>
<input className="FormControl" placeholder="Plural (e.g. Mods)" value={this.namePlural()} oninput={m.withAttr('value', this.namePlural)}/>
<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>Color</label>
<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>Icon</label>
<label>{app.translator.trans('core.admin.edit_group.icon_label')}</label>
<div className="helpText">
Enter the name of any <a href="http://fortawesome.github.io/Font-Awesome/icons/" tabindex="-1">FontAwesome</a> icon class, <em>without</em> the <code>fa-</code> prefix.
{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>
@@ -64,11 +62,11 @@ export default class EditGroupModal extends Modal {
type: 'submit',
className: 'Button Button--primary EditGroupModal-save',
loading: this.loading,
children: 'Save Changes'
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.delete.bind(this)}>
Delete Group
<button type="button" className="Button EditGroupModal-delete" onclick={this.deleteGroup.bind(this)}>
{app.translator.trans('core.admin.edit_group.delete_button')}
</button>
) : ''}
</div>
@@ -87,17 +85,16 @@ export default class EditGroupModal extends Modal {
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
}).then(
() => this.hide(),
() => {
}, {errorHandler: this.onerror.bind(this)})
.then(this.hide.bind(this))
.catch(() => {
this.loading = false;
m.redraw();
}
);
});
}
delete() {
if (confirm('Are you sure you want to delete this group? The group members will NOT be deleted.')) {
deleteGroup() {
if (confirm(app.translator.trans('core.admin.edit_group.delete_confirmation'))) {
this.group.delete().then(() => m.redraw());
this.hide();
}

View File

@@ -1,4 +1,4 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
import LinkButton from 'flarum/components/LinkButton';
import Button from 'flarum/components/Button';
import Dropdown from 'flarum/components/Dropdown';
@@ -7,15 +7,16 @@ import AddExtensionModal from 'flarum/components/AddExtensionModal';
import LoadingModal from 'flarum/components/LoadingModal';
import ItemList from 'flarum/utils/ItemList';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/listItems';
export default class ExtensionsPage extends Component {
export default class ExtensionsPage extends Page {
view() {
return (
<div className="ExtensionsPage">
<div className="ExtensionsPage-header">
<div className="container">
{Button.component({
children: 'Add Extension',
children: app.translator.trans('core.admin.extensions.add_button'),
icon: 'plus',
className: 'Button Button--primary',
onclick: () => app.modal.show(new AddExtensionModal())
@@ -26,29 +27,33 @@ export default class ExtensionsPage extends Component {
<div className="ExtensionsPage-list">
<div className="container">
<ul className="ExtensionList">
{app.extensions
.sort((a, b) => a.name.localeCompare(b.name))
.map(extension => (
<li className={'ExtensionListItem ' + (!this.isEnabled(extension.name) ? 'disabled' : '')}>
{Dropdown.component({
icon: 'ellipsis-v',
children: this.controlItems(extension).toArray(),
className: 'ExtensionListItem-controls',
buttonClassName: 'Button Button--icon Button--flat',
menuClassName: 'Dropdown-menu--right'
})}
{Object.keys(app.data.extensions)
.map(id => {
const extension = app.data.extensions[id];
const controls = this.controlItems(extension.id).toArray();
return <li className={'ExtensionListItem ' + (!this.isEnabled(extension.id) ? 'disabled' : '')}>
<div className="ExtensionListItem-content">
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
{extension.icon ? icon(extension.icon.name) : ''}
</span>
<h4 className="ExtensionListItem-title">
{extension.title}{' '}
<small className="ExtensionListItem-version">{extension.version}</small>
</h4>
<div className="ExtensionListItem-description">{extension.description}</div>
{controls.length ? (
<Dropdown
className="ExtensionListItem-controls"
buttonClassName="Button Button--icon Button--flat"
menuClassName="Dropdown-menu--right"
icon="ellipsis-h">
{controls}
</Dropdown>
) : ''}
<label className="ExtensionListItem-title">
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
{extension.extra['flarum-extension'].title}
</label>
<div className="ExtensionListItem-version">{extension.version}</div>
</div>
</li>
))}
</li>;
})}
</ul>
</div>
</div>
@@ -56,40 +61,26 @@ export default class ExtensionsPage extends Component {
);
}
controlItems(extension) {
controlItems(name) {
const items = new ItemList();
const enabled = this.isEnabled(extension.name);
const enabled = this.isEnabled(name);
if (app.extensionSettings[extension.name]) {
if (app.extensionSettings[name]) {
items.add('settings', Button.component({
icon: 'cog',
children: 'Settings',
onclick: app.extensionSettings[extension.name]
children: app.translator.trans('core.admin.extensions.settings_button'),
onclick: app.extensionSettings[name]
}));
}
items.add('toggle', Button.component({
icon: 'power-off',
children: enabled ? 'Disable' : 'Enable',
onclick: () => {
app.request({
url: app.forum.attribute('apiUrl') + '/extensions/' + extension.name,
method: 'PATCH',
data: {enabled: !enabled}
}).then(() => window.location.reload());
app.modal.show(new LoadingModal());
}
}));
if (!enabled) {
items.add('uninstall', Button.component({
icon: 'trash-o',
children: 'Uninstall',
children: app.translator.trans('core.admin.extensions.uninstall_button'),
onclick: () => {
app.request({
url: app.forum.attribute('apiUrl') + '/extensions/' + extension.name,
method: 'DELETE',
url: app.forum.attribute('apiUrl') + '/extensions/' + name,
method: 'DELETE'
}).then(() => window.location.reload());
app.modal.show(new LoadingModal());
@@ -97,19 +88,27 @@ export default class ExtensionsPage extends Component {
}));
}
// items.add('separator2', Separator.component());
// items.add('support', LinkButton.component({
// icon: 'support',
// children: 'Support'
// }));
return items;
}
isEnabled(name) {
const enabled = JSON.parse(app.config.extensions_enabled);
const enabled = JSON.parse(app.data.settings.extensions_enabled);
return enabled.indexOf(name) !== -1;
}
toggle(id) {
const enabled = this.isEnabled(id);
app.request({
url: app.forum.attribute('apiUrl') + '/extensions/' + id,
method: 'PATCH',
data: {enabled: !enabled}
}).then(() => {
if (!enabled) localStorage.setItem('enabledExtension', id);
window.location.reload();
});
app.modal.show(new LoadingModal());
}
}

View File

@@ -15,6 +15,13 @@ export default class HeaderPrimary extends Component {
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Build an item list for the controls.
*

View File

@@ -15,6 +15,13 @@ export default class HeaderSecondary extends Component {
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Build an item list for the controls.
*

View File

@@ -10,7 +10,7 @@ export default class LoadingModal extends Modal {
}
title() {
return 'Please Wait...';
return app.translator.trans('core.admin.loading.title');
}
content() {

View File

@@ -0,0 +1,124 @@
import Page from 'flarum/components/Page';
import FieldSet from 'flarum/components/FieldSet';
import Button from 'flarum/components/Button';
import Alert from 'flarum/components/Alert';
import saveSettings from 'flarum/utils/saveSettings';
export default class MailPage extends Page {
init() {
super.init();
this.loading = false;
this.fields = [
'mail_driver',
'mail_host',
'mail_from',
'mail_port',
'mail_username',
'mail_password',
'mail_encryption'
];
this.values = {};
const settings = app.data.settings;
this.fields.forEach(key => this.values[key] = m.prop(settings[key]));
this.localeOptions = {};
const locales = app.locales;
for (const i in locales) {
this.localeOptions[i] = `${locales[i]} (${i})`;
}
}
view() {
return (
<div className="MailPage">
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
<h2>{app.translator.trans('core.admin.email.heading')}</h2>
<div className="helpText">
{app.translator.trans('core.admin.email.text')}
</div>
{FieldSet.component({
label: app.translator.trans('core.admin.email.server_heading'),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
<label>{app.translator.trans('core.admin.email.driver_label')}</label>
<input className="FormControl" value={this.values.mail_driver() || ''} oninput={m.withAttr('value', this.values.mail_driver)} />
<label>{app.translator.trans('core.admin.email.host_label')}</label>
<input className="FormControl" value={this.values.mail_host() || ''} oninput={m.withAttr('value', this.values.mail_host)} />
<label>{app.translator.trans('core.admin.email.port_label')}</label>
<input className="FormControl" value={this.values.mail_port() || ''} oninput={m.withAttr('value', this.values.mail_port)} />
<label>{app.translator.trans('core.admin.email.encryption_label')}</label>
<input className="FormControl" value={this.values.mail_encryption() || ''} oninput={m.withAttr('value', this.values.mail_encryption)} />
</div>
]
})}
{FieldSet.component({
label: app.translator.trans('core.admin.email.account_heading'),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
<label>{app.translator.trans('core.admin.email.username_label')}</label>
<input className="FormControl" value={this.values.mail_username() || ''} oninput={m.withAttr('value', this.values.mail_username)} />
<label>{app.translator.trans('core.admin.email.password_label')}</label>
<input className="FormControl" value={this.values.mail_password() || ''} oninput={m.withAttr('value', this.values.mail_password)} />
</div>
]
})}
{FieldSet.component({
label: app.translator.trans('core.admin.email.addresses_heading'),
className: 'MailPage-MailSettings',
children: [
<div className="MailPage-MailSettings-input">
<label>{app.translator.trans('core.admin.email.from_label')}</label>
<input className="FormControl" value={this.values.mail_from() || ''} oninput={m.withAttr('value', this.values.mail_from)} />
</div>
]
})}
{Button.component({
type: 'submit',
className: 'Button Button--primary',
children: app.translator.trans('core.admin.email.submit_button'),
loading: this.loading,
disabled: !this.changed()
})}
</form>
</div>
</div>
);
}
changed() {
return this.fields.some(key => this.values[key]() !== app.data.settings[key]);
}
onsubmit(e) {
e.preventDefault();
if (this.loading) return;
this.loading = true;
app.alerts.dismiss(this.successAlert);
const settings = {};
this.fields.forEach(key => settings[key] = this.values[key]());
saveSettings(settings)
.then(() => {
app.alerts.show(this.successAlert = new Alert({type: 'success', children: app.translator.trans('core.admin.basics.saved_message')}));
})
.catch(() => {})
.then(() => {
this.loading = false;
m.redraw();
});
}
}

View File

@@ -0,0 +1,32 @@
import Component from 'flarum/Component';
/**
* The `Page` component
*
* @abstract
*/
export default class Page extends Component {
init() {
app.previous = app.current;
app.current = this;
app.modal.close();
/**
* A class name to apply to the body while the route is active.
*
* @type {String}
*/
this.bodyClass = '';
}
config(isInitialized, context) {
if (isInitialized) return;
if (this.bodyClass) {
$('#app').addClass(this.bodyClass);
context.onunload = () => $('#app').removeClass(this.bodyClass);
}
}
}

View File

@@ -2,6 +2,7 @@ import Dropdown from 'flarum/components/Dropdown';
import Button from 'flarum/components/Button';
import Separator from 'flarum/components/Separator';
import Group from 'flarum/models/Group';
import Badge from 'flarum/components/Badge';
import GroupBadge from 'flarum/components/GroupBadge';
function badgeForId(id) {
@@ -10,6 +11,27 @@ function badgeForId(id) {
return group ? GroupBadge.component({group, label: null}) : '';
}
function filterByRequiredPermissions(groupIds, permission) {
app.getRequiredPermissions(permission)
.forEach(required => {
const restrictToGroupIds = app.data.permissions[required] || [];
if (restrictToGroupIds.indexOf(Group.GUEST_ID) !== -1) {
// do nothing
} else if (restrictToGroupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = groupIds.filter(id => id !== Group.GUEST_ID);
} else if (groupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = restrictToGroupIds;
} else {
groupIds = restrictToGroupIds.filter(id => groupIds.indexOf(id) !== -1);
}
groupIds = filterByRequiredPermissions(groupIds, required);
});
return groupIds;
}
export default class PermissionDropdown extends Dropdown {
static initProps(props) {
super.initProps(props);
@@ -21,15 +43,18 @@ export default class PermissionDropdown extends Dropdown {
view() {
this.props.children = [];
const groupIds = app.permissions[this.props.permission] || [];
let groupIds = app.data.permissions[this.props.permission] || [];
groupIds = filterByRequiredPermissions(groupIds, this.props.permission);
const everyone = groupIds.indexOf(Group.GUEST_ID) !== -1;
const members = groupIds.indexOf(Group.MEMBER_ID) !== -1;
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
if (everyone) {
this.props.label = 'Everyone';
this.props.label = Badge.component({icon: 'globe'});
} else if (members) {
this.props.label = 'Members';
this.props.label = Badge.component({icon: 'user'});
} else {
this.props.label = [
badgeForId(Group.ADMINISTRATOR_ID),
@@ -37,57 +62,62 @@ export default class PermissionDropdown extends Dropdown {
];
}
if (this.props.allowGuest) {
if (this.showing) {
if (this.props.allowGuest) {
this.props.children.push(
Button.component({
children: [Badge.component({icon: 'globe'}), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
icon: everyone ? 'check' : true,
onclick: () => this.save([Group.GUEST_ID]),
disabled: this.isGroupDisabled(Group.GUEST_ID)
})
);
}
this.props.children.push(
Button.component({
children: 'Everyone',
icon: everyone ? 'check' : true,
onclick: () => this.save([Group.GUEST_ID])
children: [Badge.component({icon: 'user'}), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
icon: members ? 'check' : true,
onclick: () => this.save([Group.MEMBER_ID]),
disabled: this.isGroupDisabled(Group.MEMBER_ID)
}),
Separator.component(),
Button.component({
children: [badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()],
icon: !everyone && !members ? 'check' : true,
disabled: !everyone && !members,
onclick: e => {
if (e.shiftKey) e.stopPropagation();
this.save([]);
}
})
);
[].push.apply(
this.props.children,
app.store.all('groups')
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => Button.component({
children: [badgeForId(group.id()), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
onclick: (e) => {
if (e.shiftKey) e.stopPropagation();
this.toggle(group.id());
},
disabled: this.isGroupDisabled(group.id()) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID)
}))
);
}
this.props.children.push(
Button.component({
children: 'Members',
icon: members ? 'check' : true,
onclick: () => this.save([Group.MEMBER_ID])
}),
Separator.component(),
Button.component({
children: [GroupBadge.component({group: adminGroup, label: null}), ' ', adminGroup.namePlural()],
icon: !everyone && !members ? 'check' : true,
disabled: !everyone && !members,
onclick: e => {
e.stopPropagation();
this.save([]);
}
})
);
[].push.apply(
this.props.children,
app.store.all('groups')
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => Button.component({
children: [GroupBadge.component({group, label: null}), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
onclick: (e) => {
e.stopPropagation();
this.toggle(group.id());
}
}))
);
return super.view();
}
save(groupIds) {
const permission = this.props.permission;
app.permissions[permission] = groupIds;
app.data.permissions[permission] = groupIds;
app.request({
method: 'POST',
@@ -99,7 +129,7 @@ export default class PermissionDropdown extends Dropdown {
toggle(groupId) {
const permission = this.props.permission;
let groupIds = app.permissions[permission] || [];
let groupIds = app.data.permissions[permission] || [];
const index = groupIds.indexOf(groupId);
@@ -112,4 +142,8 @@ export default class PermissionDropdown extends Dropdown {
this.save(groupIds);
}
isGroupDisabled(id) {
return filterByRequiredPermissions([id], this.props.permission).indexOf(id) === -1;
}
}

View File

@@ -1,13 +1,12 @@
import Component from 'flarum/Component';
import PermissionDropdown from 'flarum/components/PermissionDropdown';
import ConfigDropdown from 'flarum/components/ConfigDropdown';
import SettingDropdown from 'flarum/components/SettingDropdown';
import Button from 'flarum/components/Button';
import ItemList from 'flarum/utils/ItemList';
import icon from 'flarum/helpers/icon';
export default class PermissionGrid extends Component {
constructor(...args) {
super(...args);
init() {
this.permissions = this.permissionItems().toArray();
}
@@ -45,7 +44,7 @@ export default class PermissionGrid extends Component {
</tr>
{section.children.map(child => (
<tr className="PermissionGrid-child">
<th>{child.label}</th>
<th>{icon(child.icon)}{child.label}</th>
{permissionCells(child)}
<td/>
</tr>
@@ -60,24 +59,24 @@ export default class PermissionGrid extends Component {
const items = new ItemList();
items.add('view', {
label: 'View the forum',
label: app.translator.trans('core.admin.permissions.read_heading'),
children: this.viewItems().toArray()
});
}, 100);
items.add('start', {
label: 'Start discussions',
label: app.translator.trans('core.admin.permissions.create_heading'),
children: this.startItems().toArray()
});
}, 90);
items.add('reply', {
label: 'Reply to discussions',
label: app.translator.trans('core.admin.permissions.participate_heading'),
children: this.replyItems().toArray()
});
}, 80);
items.add('moderate', {
label: 'Moderate',
label: app.translator.trans('core.admin.permissions.moderate_heading'),
children: this.moderateItems().toArray()
});
}, 70);
return items;
}
@@ -85,22 +84,31 @@ export default class PermissionGrid extends Component {
viewItems() {
const items = new ItemList();
items.add('view', {
label: 'View discussions',
permission: 'forum.view',
items.add('viewDiscussions', {
icon: 'eye',
label: app.translator.trans('core.admin.permissions.view_discussions_label'),
permission: 'viewDiscussions',
allowGuest: true
});
}, 100);
items.add('viewUserList', {
icon: 'users',
label: app.translator.trans('core.admin.permissions.view_user_list_label'),
permission: 'viewUserList',
allowGuest: true
}, 100);
items.add('signUp', {
label: 'Sign up',
setting: () => ConfigDropdown.component({
icon: 'user-plus',
label: app.translator.trans('core.admin.permissions.sign_up_label'),
setting: () => SettingDropdown.component({
key: 'allow_sign_up',
options: [
{value: '1', label: 'Open'},
{value: '0', label: 'Closed'}
{value: '1', label: app.translator.trans('core.admin.permissions_controls.signup_open_button')},
{value: '0', label: app.translator.trans('core.admin.permissions_controls.signup_closed_button')}
]
})
});
}, 90);
return items;
}
@@ -109,26 +117,30 @@ export default class PermissionGrid extends Component {
const items = new ItemList();
items.add('start', {
label: 'Start discussions',
permission: 'forum.startDiscussion'
});
icon: 'edit',
label: app.translator.trans('core.admin.permissions.start_discussions_label'),
permission: 'startDiscussion'
}, 100);
items.add('allowRenaming', {
label: 'Allow renaming',
icon: 'i-cursor',
label: app.translator.trans('core.admin.permissions.allow_renaming_label'),
setting: () => {
const minutes = parseInt(app.config.allow_renaming, 10);
const minutes = parseInt(app.data.settings.allow_renaming, 10);
return ConfigDropdown.component({
defaultLabel: minutes ? `For ${minutes} minutes` : 'Indefinitely',
return SettingDropdown.component({
defaultLabel: minutes
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, {count: minutes})
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
key: 'allow_renaming',
options: [
{value: '-1', label: 'Indefinitely'},
{value: '10', label: 'For 10 minutes'},
{value: 'reply', label: 'Until next reply'}
{value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')},
{value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button')},
{value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button')}
]
});
}
});
}, 90);
return items;
}
@@ -137,26 +149,30 @@ export default class PermissionGrid extends Component {
const items = new ItemList();
items.add('reply', {
label: 'Reply to discussions',
icon: 'reply',
label: app.translator.trans('core.admin.permissions.reply_to_discussions_label'),
permission: 'discussion.reply'
});
}, 100);
items.add('allowPostEditing', {
label: 'Allow post editing',
icon: 'pencil',
label: app.translator.trans('core.admin.permissions.allow_post_editing_label'),
setting: () => {
const minutes = parseInt(app.config.allow_post_editing, 10);
const minutes = parseInt(app.data.settings.allow_post_editing, 10);
return ConfigDropdown.component({
defaultLabel: minutes ? `For ${minutes} minutes` : 'Indefinitely',
return SettingDropdown.component({
defaultLabel: minutes
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, {count: minutes})
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
key: 'allow_post_editing',
options: [
{value: '-1', label: 'Indefinitely'},
{value: '10', label: 'For 10 minutes'},
{value: 'reply', label: 'Until next reply'}
{value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')},
{value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button')},
{value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button')}
]
});
}
});
}, 90);
return items;
}
@@ -164,25 +180,41 @@ export default class PermissionGrid extends Component {
moderateItems() {
const items = new ItemList();
items.add('editPosts', {
label: 'Edit posts',
permission: 'discussion.editPosts'
});
items.add('deletePosts', {
label: 'Delete posts',
permission: 'discussion.deletePosts'
});
items.add('viewIpsPosts', {
icon: 'bullseye',
label: app.translator.trans('core.admin.permissions.view_post_ips_label'),
permission: 'discussion.viewIpsPosts'
}, 110);
items.add('renameDiscussions', {
label: 'Rename discussions',
icon: 'i-cursor',
label: app.translator.trans('core.admin.permissions.rename_discussions_label'),
permission: 'discussion.rename'
});
}, 100);
items.add('hideDiscussions', {
icon: 'trash-o',
label: app.translator.trans('core.admin.permissions.delete_discussions_label'),
permission: 'discussion.hide'
}, 90);
items.add('deleteDiscussions', {
label: 'Delete discussions',
icon: 'times',
label: app.translator.trans('core.admin.permissions.delete_discussions_forever_label'),
permission: 'discussion.delete'
});
}, 80);
items.add('editPosts', {
icon: 'pencil',
label: app.translator.trans('core.admin.permissions.edit_and_delete_posts_label'),
permission: 'discussion.editPosts'
}, 70);
items.add('deletePosts', {
icon: 'times',
label: app.translator.trans('core.admin.permissions.delete_posts_forever_label'),
permission: 'discussion.deletePosts'
}, 60);
return items;
}
@@ -191,17 +223,20 @@ export default class PermissionGrid extends Component {
const items = new ItemList();
items.add('global', {
label: 'Global',
label: app.translator.trans('core.admin.permissions.global_heading'),
render: item => {
if (item.setting) {
return item.setting();
} else if (item.permission) {
return PermissionDropdown.component(Object.assign({}, item));
return PermissionDropdown.component({
permission: item.permission,
allowGuest: item.allowGuest
});
}
return '';
}
});
}, 100);
return items;
}

View File

@@ -1,11 +1,11 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
import GroupBadge from 'flarum/components/GroupBadge';
import EditGroupModal from 'flarum/components/EditGroupModal';
import Group from 'flarum/models/Group';
import icon from 'flarum/helpers/icon';
import PermissionGrid from 'flarum/components/PermissionGrid';
export default class PermissionsPage extends Component {
export default class PermissionsPage extends Page {
view() {
return (
<div className="PermissionsPage">
@@ -25,7 +25,7 @@ export default class PermissionsPage extends Component {
))}
<button className="Button Group Group--add" onclick={() => app.modal.show(new EditGroupModal())}>
{icon('plus', {className: 'Group-icon'})}
<span className="Group-name">New Group</span>
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
</button>
</div>
</div>

View File

@@ -43,7 +43,7 @@ export default class SessionDropdown extends Dropdown {
items.add('logOut',
Button.component({
icon: 'sign-out',
children: app.trans('core.log_out'),
children: app.translator.trans('core.admin.header.log_out_button'),
onclick: app.session.logout.bind(app.session)
}),
-100

View File

@@ -1,23 +1,23 @@
import SelectDropdown from 'flarum/components/SelectDropdown';
import Button from 'flarum/components/Button';
import saveConfig from 'flarum/utils/saveConfig';
import saveSettings from 'flarum/utils/saveSettings';
export default class ConfigDropdown extends SelectDropdown {
export default class SettingDropdown extends SelectDropdown {
static initProps(props) {
super.initProps(props);
props.className = 'ConfigDropdown';
props.className = 'SettingDropdown';
props.buttonClassName = 'Button Button--text';
props.caretIcon = 'caret-down';
props.defaultLabel = 'Custom';
props.children = props.options.map(({value, label}) => {
const active = app.config[props.key] === value;
const active = app.data.settings[props.key] === value;
return Button.component({
children: label,
icon: active ? 'check' : true,
onclick: saveConfig.bind(this, {[props.key]: value}),
onclick: saveSettings.bind(this, {[props.key]: value}),
active
});
});

View File

@@ -0,0 +1,79 @@
import Modal from 'flarum/components/Modal';
import Button from 'flarum/components/Button';
import saveSettings from 'flarum/utils/saveSettings';
export default class SettingsModal extends Modal {
init() {
this.settings = {};
this.loading = false;
}
form() {
return '';
}
content() {
return (
<div className="Modal-body">
<div className="Form">
{this.form()}
<div className="Form-group">
{this.submitButton()}
</div>
</div>
</div>
);
}
submitButton() {
return (
<Button
type="submit"
className="Button Button--primary"
loading={this.loading}
disabled={!this.changed()}>
{app.translator.trans('core.admin.settings.submit_button')}
</Button>
);
}
setting(key, fallback = '') {
this.settings[key] = this.settings[key] || m.prop(app.data.settings[key] || fallback);
return this.settings[key];
}
dirty() {
const dirty = {};
Object.keys(this.settings).forEach(key => {
const value = this.settings[key]();
if (value !== app.data.settings[key]) {
dirty[key] = value;
}
});
return dirty;
}
changed() {
return Object.keys(this.dirty()).length;
}
onsubmit(e) {
e.preventDefault();
this.loading = true;
saveSettings(this.dirty()).then(
this.onsaved.bind(this),
this.loaded.bind(this)
);
}
onsaved() {
this.hide();
}
}

View File

@@ -0,0 +1,97 @@
import Button from 'flarum/components/Button';
export default class UploadImageButton extends Button {
init() {
this.loading = false;
}
view() {
this.props.loading = this.loading;
this.props.className = (this.props.className || '') + ' Button';
if (app.data.settings[this.props.name + '_path']) {
this.props.onclick = this.remove.bind(this);
this.props.children = app.translator.trans('core.admin.upload_image.remove_button');
return (
<div>
<p><img src={app.forum.attribute(this.props.name+'Url')} alt=""/></p>
<p>{super.view()}</p>
</div>
);
} else {
this.props.onclick = this.upload.bind(this);
this.props.children = app.translator.trans('core.admin.upload_image.upload_button');
}
return super.view();
}
/**
* Prompt the user to upload an image.
*/
upload() {
if (this.loading) return;
const $input = $('<input type="file">');
$input.appendTo('body').hide().click().on('change', e => {
const data = new FormData();
data.append(this.props.name, $(e.target)[0].files[0]);
this.loading = true;
m.redraw();
app.request({
method: 'POST',
url: this.resourceUrl(),
serialize: raw => raw,
data
}).then(
this.success.bind(this),
this.failure.bind(this)
);
});
}
/**
* Remove the logo.
*/
remove() {
this.loading = true;
m.redraw();
app.request({
method: 'DELETE',
url: this.resourceUrl()
}).then(
this.success.bind(this),
this.failure.bind(this)
);
}
resourceUrl() {
return app.forum.attribute('apiUrl') + '/' + this.props.name;
}
/**
* After a successful upload/removal, reload the page.
*
* @param {Object} response
* @protected
*/
success(response) {
window.location.reload();
}
/**
* If upload/removal fails, stop loading.
*
* @param {Object} response
* @protected
*/
failure(response) {
this.loading = false;
m.redraw();
}
}

View File

@@ -31,6 +31,7 @@ export default function boot(app) {
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();
@@ -54,4 +55,12 @@ export default function boot(app) {
}).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');
}
}

View File

@@ -3,6 +3,7 @@ import BasicsPage from 'flarum/components/BasicsPage';
import PermissionsPage from 'flarum/components/PermissionsPage';
import AppearancePage from 'flarum/components/AppearancePage';
import ExtensionsPage from 'flarum/components/ExtensionsPage';
import MailPage from 'flarum/components/MailPage';
/**
* The `routes` initializer defines the admin app's routes.
@@ -15,6 +16,7 @@ export default function(app) {
'basics': {path: '/basics', component: BasicsPage.component()},
'permissions': {path: '/permissions', component: PermissionsPage.component()},
'appearance': {path: '/appearance', component: AppearancePage.component()},
'extensions': {path: '/extensions', component: ExtensionsPage.component()}
'extensions': {path: '/extensions', component: ExtensionsPage.component()},
'mail': {path: '/mail', component: MailPage.component()}
};
}

View File

@@ -1,14 +0,0 @@
export default function saveConfig(config) {
const oldConfig = JSON.parse(JSON.stringify(app.config));
Object.assign(app.config, config);
return app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/config',
data: {config}
}).catch(error => {
app.config = oldConfig;
throw error;
});
}

View File

@@ -0,0 +1,14 @@
export default function saveSettings(settings) {
const oldSettings = JSON.parse(JSON.stringify(app.data.settings));
Object.assign(app.data.settings, settings);
return app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/settings',
data: settings
}).catch(error => {
app.data.settings = oldSettings;
throw error;
});
}

View File

@@ -7,9 +7,11 @@
"spin.js": "~2.0.1",
"moment": "~2.8.4",
"color-thief": "v2.0",
"mithril": "lhorie/mithril.js#next",
"mithril": "lhorie/mithril.js#v0.2.5",
"es6-micro-loader": "caridy/es6-micro-loader#v0.2.1",
"fastclick": "~1.0.6",
"autolink": "*"
"autolink": "~1.0.0",
"m.attrs.bidi": "tobscure/m.attrs.bidi",
"punycode": "http://cdnjs.cloudflare.com/ajax/libs/punycode/1.4.1/punycode.js"
}
}

2
js/forum/.gitignore vendored
View File

@@ -1,3 +1 @@
node_modules
mithril.js
dist

View File

@@ -1,20 +1,19 @@
var gulp = require('flarum-gulp');
var nodeDir = 'node_modules';
var bowerDir = '../bower_components';
gulp({
includeHelpers: true,
files: [
nodeDir + '/babel-core/external-helpers.js',
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/js/color-thief.js',
bowerDir + '/color-thief/src/color-thief.js',
bowerDir + '/moment/moment.js',
bowerDir + '/autolink/autolink.js',
bowerDir + '/autolink/autolink-min.js',
bowerDir + '/bootstrap/js/affix.js',
bowerDir + '/bootstrap/js/dropdown.js',
@@ -24,7 +23,8 @@ gulp({
bowerDir + '/spin.js/spin.js',
bowerDir + '/spin.js/jquery.spin.js',
bowerDir + '/fastclick/lib/fastclick.js'
bowerDir + '/fastclick/lib/fastclick.js',
bowerDir + '/punycode/index.js'
],
modules: {
'flarum': [
@@ -32,6 +32,5 @@ gulp({
'../lib/**/*.js'
]
},
externalHelpers: true,
outputFile: 'dist/app.js'
});

32990
js/forum/dist/app.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
{
"private": true,
"devDependencies": {
"gulp": "^3.8.11",
"flarum-gulp": "git+https://github.com/flarum/gulp.git",
"babel-core": "^5.0.0"
"gulp": "^3.9.1",
"flarum-gulp": "^0.2.0"
}
}

View File

@@ -4,6 +4,7 @@ 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) {
@@ -76,4 +77,27 @@ export default class ForumApp extends App {
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();
}
}
}

View File

@@ -5,6 +5,7 @@ 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();
@@ -15,5 +16,6 @@ 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;

View File

@@ -1,66 +0,0 @@
import Component from 'flarum/Component';
import humanTime from 'flarum/helpers/humanTime';
import avatar from 'flarum/helpers/avatar';
/**
* The `Activity` component represents a piece of activity of a user's activity
* feed. Subclasses should implement the `description` and `content` methods.
*
* ### Props
*
* - `activity`
*
* @abstract
*/
export default class Activity extends Component {
view() {
const activity = this.props.activity;
return (
<div className="Activity">
{avatar(this.user(), {className: 'Activity-avatar'})}
<div className="Activity-header">
<strong className="Activity-description">{this.description()}</strong>
{humanTime(this.time())}
</div>
{this.content()}
</div>
);
}
/**
* Get the user whose avatar should be displayed.
*
* @return {User}
* @abstract
*/
user() {
}
/**
* Get the time of the activity.
*
* @return {Date}
* @abstract
*/
time() {
}
/**
* Get the description of the activity.
*
* @return {VirtualElement}
*/
description() {
}
/**
* Get the content to show below the activity description.
*
* @return {VirtualElement}
*/
content() {
}
}

View File

@@ -16,9 +16,7 @@ import LoadingIndicator from 'flarum/components/LoadingIndicator';
* - `user`
*/
export default class AvatarEditor extends Component {
constructor(...args) {
super(...args);
init() {
/**
* Whether or not an avatar upload is in progress.
*
@@ -39,10 +37,11 @@ export default class AvatarEditor extends Component {
return (
<div className={'AvatarEditor Dropdown ' + this.props.className + (this.loading ? ' loading' : '')}>
{avatar(user)}
<a className="Dropdown-toggle"
<a className={ user.avatarUrl() ? "Dropdown-toggle" : "Dropdown-toggle AvatarEditor--noAvatar" }
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
data-toggle="dropdown"
onclick={this.quickUpload.bind(this)}>
{this.loading ? LoadingIndicator.component() : icon('pencil')}
{this.loading ? LoadingIndicator.component() : (user.avatarUrl() ? icon('pencil') : icon('plus-circle'))}
</a>
<ul className="Dropdown-menu Menu">
{listItems(this.controlItems().toArray())}
@@ -62,7 +61,7 @@ export default class AvatarEditor extends Component {
items.add('upload',
Button.component({
icon: 'upload',
children: app.trans('core.upload'),
children: app.translator.trans('core.forum.user.avatar_upload_button'),
onclick: this.upload.bind(this)
})
);
@@ -70,7 +69,7 @@ export default class AvatarEditor extends Component {
items.add('remove',
Button.component({
icon: 'times',
children: app.trans('core.remove'),
children: app.translator.trans('core.forum.user.avatar_remove_button'),
onclick: this.remove.bind(this)
})
);
@@ -163,7 +162,8 @@ export default class AvatarEditor extends Component {
* @param {Object} response
* @protected
*/
failure() {
failure(response) {
this.loading = false;
m.redraw();
}
}

View File

@@ -6,8 +6,8 @@ import Button from 'flarum/components/Button';
* to change their email address.
*/
export default class ChangeEmailModal extends Modal {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* Whether or not the email has been changed successfully.
@@ -22,6 +22,13 @@ export default class ChangeEmailModal extends Modal {
* @type {function}
*/
this.email = m.prop(app.session.user.email());
/**
* The value of the password input.
*
* @type {function}
*/
this.password = m.prop('');
}
className() {
@@ -29,21 +36,19 @@ export default class ChangeEmailModal extends Modal {
}
title() {
return app.trans('core.change_email');
return app.translator.trans('core.forum.change_email.title');
}
content() {
if (this.success) {
const emailProviderName = this.email().split('@')[1];
return (
<div className="Modal-body">
<div className="Form Form--centered">
<p className="helpText">{m.trust(app.trans('core.confirmation_email_sent', {email: <strong>{this.email()}</strong>}))}</p>
<p className="helpText">{app.translator.trans('core.forum.change_email.confirmation_message', {email: <strong>{this.email()}</strong>})}</p>
<div className="Form-group">
<a href={'http://' + emailProviderName} className="Button Button--primary Button--block">
{app.trans('core.go_to', {location: emailProviderName})}
</a>
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
{app.translator.trans('core.forum.change_email.dismiss_button')}
</Button>
</div>
</div>
</div>
@@ -56,8 +61,13 @@ export default class ChangeEmailModal extends Modal {
<div className="Form-group">
<input type="email" name="email" className="FormControl"
placeholder={app.session.user.email()}
value={this.email()}
onchange={m.withAttr('value', this.email)}
bidi={this.email}
disabled={this.loading}/>
</div>
<div className="Form-group">
<input type="password" name="password" className="FormControl"
placeholder={app.translator.trans('core.forum.change_email.confirm_password_placeholder')}
bidi={this.password}
disabled={this.loading}/>
</div>
<div className="Form-group">
@@ -65,7 +75,7 @@ export default class ChangeEmailModal extends Modal {
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.trans('core.save_changes')
children: app.translator.trans('core.forum.change_email.submit_button')
})}
</div>
</div>
@@ -83,17 +93,24 @@ export default class ChangeEmailModal extends Modal {
return;
}
const oldEmail = app.session.user.email();
this.loading = true;
app.session.user.save({email: this.email()}).then(
() => {
this.loading = false;
this.success = true;
m.redraw();
},
() => {
this.loading = false;
}
);
app.session.user.save({email: this.email()}, {
errorHandler: this.onerror.bind(this),
meta: {password: this.password()}
})
.then(() => this.success = true)
.catch(() => {})
.then(this.loaded.bind(this));
}
onerror(error) {
if (error.status === 401) {
error.alert.props.children = app.translator.trans('core.forum.change_email.incorrect_password_message');
}
super.onerror(error);
}
}

View File

@@ -11,20 +11,20 @@ export default class ChangePasswordModal extends Modal {
}
title() {
return app.trans('core.change_password');
return app.translator.trans('core.forum.change_password.title');
}
content() {
return (
<div className="Modal-body">
<div className="Form Form--centered">
<p className="helpText">{app.trans('core.change_password_help')}</p>
<p className="helpText">{app.translator.trans('core.forum.change_password.text')}</p>
<div className="Form-group">
{Button.component({
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.trans('core.send_password_reset_email')
children: app.translator.trans('core.forum.change_password.send_button')
})}
</div>
</div>
@@ -42,8 +42,8 @@ export default class ChangePasswordModal extends Modal {
url: app.forum.attribute('apiUrl') + '/forgot',
data: {email: app.session.user.email()}
}).then(
() => this.hide(),
() => this.loading = false
this.hide.bind(this),
this.loaded.bind(this)
);
}
}

View File

@@ -1,4 +1,4 @@
/*global s9e*/
/*global s9e, hljs*/
import Post from 'flarum/components/Post';
import classList from 'flarum/utils/classList';
@@ -6,7 +6,6 @@ import PostUser from 'flarum/components/PostUser';
import PostMeta from 'flarum/components/PostMeta';
import PostEdited from 'flarum/components/PostEdited';
import EditPostComposer from 'flarum/components/EditPostComposer';
import Composer from 'flarum/components/Composer';
import ItemList from 'flarum/utils/ItemList';
import listItems from 'flarum/helpers/listItems';
import Button from 'flarum/components/Button';
@@ -21,8 +20,8 @@ import Button from 'flarum/components/Button';
* - `post`
*/
export default class CommentPost extends Post {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* If the post has been hidden, then this flag determines whether or not its
@@ -42,36 +41,54 @@ export default class CommentPost extends Post {
}
content() {
return [
<header className="Post-header"><ul>{listItems(this.headerItems().toArray())}</ul></header>,
// Note: we avoid using JSX for the <ul> below because it results in some
// weirdness in Mithril.js 0.1.x (see flarum/core#975). This workaround can
// be reverted when we upgrade to Mithril 1.0.
return super.content().concat([
<header className="Post-header">{m('ul', listItems(this.headerItems().toArray()))}</header>,
<div className="Post-body">
{this.isEditing()
? <div className="Post-preview" config={this.configPreview.bind(this)}/>
: m.trust(this.props.post.contentHtml())}
</div>,
<footer className="Post-footer"><ul>{listItems(this.footerItems().toArray())}</ul></footer>,
<aside className="Post-actions"><ul>{listItems(this.actionItems().toArray())}</ul></aside>
];
</div>
]);
}
config(isInitialized, context) {
super.config(...arguments);
const contentHtml = this.isEditing() ? '' : this.props.post.contentHtml();
// If the post content has changed since the last render, we'll run through
// all of the <script> tags in the content and evaluate them. This is
// necessary because TextFormatter outputs them for e.g. syntax highlighting.
if (context.contentHtml !== contentHtml) {
this.$('.Post-body script').each(function() {
eval.call(window, $(this).text());
});
}
context.contentHtml = contentHtml;
}
isEditing() {
return app.composer.component instanceof EditPostComposer &&
app.composer.component.props.post === this.props.post &&
app.composer.position !== Composer.PositionEnum.MINIMIZED;
app.composer.component.props.post === this.props.post;
}
attrs() {
const post = this.props.post;
const attrs = super.attrs();
return {
className: classList({
'CommentPost': true,
'hidden': post.isHidden(),
'edited': post.isEdited(),
'revealContent': this.revealContent,
'editing': this.isEditing()
})
};
attrs.className += ' '+classList({
'CommentPost': true,
'Post--hidden': post.isHidden(),
'Post--edited': post.isEdited(),
'revealContent': this.revealContent,
'editing': this.isEditing()
});
return attrs;
}
configPreview(element, isInitialized, context) {
@@ -133,22 +150,4 @@ export default class CommentPost extends Post {
return items;
}
/**
* Build an item list for the post's footer.
*
* @return {ItemList}
*/
footerItems() {
return new ItemList();
}
/**
* Build an item list for the post's actions.
*
* @return {ItemList}
*/
actionItems() {
return new ItemList();
}
}

View File

@@ -11,9 +11,7 @@ import computed from 'flarum/utils/computed';
* `show`, `hide`, `close`, `minimize`, `fullScreen`, and `exitFullScreen`.
*/
class Composer extends Component {
constructor(...args) {
super(...args);
init() {
/**
* The composer's current position.
*
@@ -21,13 +19,6 @@ class Composer extends Component {
*/
this.position = Composer.PositionEnum.HIDDEN;
/**
* The composer's previous position.
*
* @type {Composer.PositionEnum}
*/
this.oldPosition = null;
/**
* The composer's intended height, which can be modified by the user
* (by dragging the composer handle).
@@ -36,6 +27,13 @@ class Composer extends Component {
*/
this.height = null;
/**
* Whether or not the composer currently has focus.
*
* @type {Boolean}
*/
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
@@ -61,20 +59,19 @@ class Composer extends Component {
view() {
const classes = {
'normal': this.position === Composer.PositionEnum.NORMAL,
'minimized': this.position === Composer.PositionEnum.MINIMIZED,
'fullScreen': this.position === Composer.PositionEnum.FULLSCREEN
'fullScreen': this.position === Composer.PositionEnum.FULLSCREEN,
'active': this.active
};
classes.visible = this.position === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen;
classes.visible = classes.normal || classes.minimized || classes.fullScreen;
// If the composer is minimized, tell the composer's content component that
// it shouldn't let the user interact with it. Set up a handler so that if
// the content IS clicked, the composer will be shown.
if (this.component) this.component.props.disabled = classes.minimized;
const showIfMinimized = () => {
if (this.position === Composer.PositionEnum.MINIMIZED) this.show();
m.redraw.strategy('none');
};
const showIfMinimized = this.position === Composer.PositionEnum.MINIMIZED ? this.show.bind(this) : undefined;
return (
<div className={'Composer ' + classList(classes)}>
@@ -88,7 +85,11 @@ class Composer extends Component {
}
config(isInitialized, context) {
this.updateHeight();
let defaultHeight;
if (!isInitialized) {
defaultHeight = this.$().height();
}
if (isInitialized) return;
@@ -99,12 +100,15 @@ class Composer extends Component {
// Initialize the composer's intended height based on what the user has set
// it at previously, or otherwise the composer's default height. After that,
// we'll hide the composer.
this.height = localStorage.getItem('composerHeight') || this.$().height();
this.$().hide();
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
// add a class to the composer to draw attention to it.
this.$().on('focus blur', ':input', e => this.$().toggleClass('active', e.type === 'focusin'));
this.$().on('focus blur', ':input', e => {
this.active = e.type === 'focusin';
m.redraw();
});
// When the escape key is pressed on any inputs, close the composer.
this.$().on('keydown', ':input', 'esc', () => this.close());
@@ -113,7 +117,7 @@ class Composer extends Component {
// component a chance to scream at the user to make sure they don't
// unintentionally lose any contnet.
window.onbeforeunload = () => {
return (this.component && this.component.preventExit()) || null;
return (this.component && this.component.preventExit()) || undefined;
};
const handlers = {};
@@ -198,19 +202,17 @@ class Composer extends Component {
* of any flexible elements inside the composer's body.
*/
updateHeight() {
// TODO: update this in a way that is independent of the TextEditor being
// present.
const height = this.computedHeight();
const $flexible = this.$('.TextEditor-flexible');
const $flexible = this.$('.Composer-flexible');
this.$().height(height);
if ($flexible.length) {
const headerHeight = $flexible.offset().top - this.$().offset().top;
const paddingBottom = parseInt($flexible.css('padding-bottom'), 10);
const footerHeight = this.$('.TextEditor-controls').outerHeight(true);
const footerHeight = this.$('.Composer-footer').outerHeight(true);
$flexible.height(height - headerHeight - paddingBottom - footerHeight);
$flexible.height(this.$().outerHeight() - headerHeight - paddingBottom - footerHeight);
}
}
@@ -221,99 +223,27 @@ class Composer extends Component {
*/
updateBodyPadding() {
const visible = this.position !== Composer.PositionEnum.HIDDEN &&
this.position !== Composer.PositionEnum.MINIMIZED;
this.position !== Composer.PositionEnum.MINIMIZED &&
this.$().css('position') !== 'absolute';
const paddingBottom = visible
? this.computedHeight() - parseInt($('#app').css('padding-bottom'), 10)
: 0;
$('#content').css({paddingBottom});
}
/**
* Update (and animate) the DOM to reflect the composer's current state.
* Determine whether or not the Composer is covering the screen.
*
* This will be true if the Composer is in full-screen mode on desktop, or
* if the Composer is positioned absolutely as on mobile devices.
*
* @return {Boolean}
* @public
*/
update() {
// Before we redraw the composer to its new state, we need to save the
// current height of the composer, as well as the page's scroll position, so
// that we can smoothly transition from the old to the new state.
const $composer = this.$().stop(true);
const oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;
const scrollTop = $(window).scrollTop();
m.redraw(true);
// Now that we've redrawn and the composer's DOM has been updated, we want
// to update the composer's height. Once we've done that, we'll capture the
// real value to use as the end point for our animation later on.
$composer.show();
this.updateHeight();
const newHeight = $composer.outerHeight();
switch (this.position) {
case Composer.PositionEnum.NORMAL:
// If the composer is being opened, we will make it visible and animate
// it growing/sliding up from the bottom of the viewport. Or if the user
// has just exited fullscreen mode, we will simply tell the content to
// take focus.
if (this.oldPosition !== Composer.PositionEnum.FULLSCREEN) {
$composer.show()
.css({height: oldHeight})
.animate({bottom: 0, height: newHeight}, 'fast', this.component.focus.bind(this.component));
if ($composer.css('position') === 'absolute') {
$composer.css('top', $(window).scrollTop());
this.$backdrop = $('<div/>')
.addClass('composer-backdrop')
.appendTo('body');
}
} else {
this.component.focus();
}
break;
case Composer.PositionEnum.MINIMIZED:
// If the composer has been minimized, we will animate it shrinking down
// to its new smaller size.
$composer.css({top: 'auto', height: oldHeight})
.animate({height: newHeight}, 'fast');
if (this.$backdrop) this.$backdrop.remove();
break;
case Composer.PositionEnum.HIDDEN:
// If the composer has been hidden, then we will animate it sliding down
// beyond the edge of the viewport. Once the animation is complete, we
// un-draw the composer's component.
$composer.css({top: 'auto', height: oldHeight})
.animate({bottom: -newHeight}, 'fast', () => {
$composer.hide();
this.clear();
m.redraw();
});
if (this.$backdrop) this.$backdrop.remove();
break;
case Composer.PositionEnum.FULLSCREEN:
this.component.focus();
break;
default:
// no default
}
// Provided the composer isn't in fullscreen mode, we'll want to update the
// body's padding to make sure all of the page's content can still be seen.
// Plus, we'll scroll back to where we were before the composer was opened,
// as its opening may have changed the content of the page.
if (this.position !== Composer.PositionEnum.FULLSCREEN) {
this.updateBodyPadding();
$('html, body').scrollTop(scrollTop);
}
this.oldPosition = this.position;
isFullScreen() {
return this.position === Composer.PositionEnum.FULLSCREEN || this.$().css('position') === 'absolute';
}
/**
@@ -363,20 +293,77 @@ class Composer extends Component {
this.component = null;
}
/**
* Animate the Composer into the given position.
*
* @param {Composer.PositionEnum} position
*/
animateToPosition(position) {
// Before we redraw the composer to its new state, we need to save the
// current height of the composer, as well as the page's scroll position, so
// that we can smoothly transition from the old to the new state.
const oldPosition = this.position;
const $composer = this.$().stop(true);
const oldHeight = $composer.outerHeight();
const scrollTop = $(window).scrollTop();
this.position = position;
m.redraw(true);
// Now that we've redrawn and the composer's DOM has been updated, we want
// to update the composer's height. Once we've done that, we'll capture the
// real value to use as the end point for our animation later on.
$composer.show();
this.updateHeight();
const newHeight = $composer.outerHeight();
if (oldPosition === Composer.PositionEnum.HIDDEN) {
$composer.css({bottom: -newHeight, height: newHeight});
} else {
$composer.css({height: oldHeight});
}
$composer.animate({bottom: 0, height: newHeight}, 'fast', () => this.component.focus());
this.updateBodyPadding();
$(window).scrollTop(scrollTop);
}
/**
* Show the Composer backdrop.
*/
showBackdrop() {
this.$backdrop = $('<div/>')
.addClass('composer-backdrop')
.appendTo('body');
}
/**
* Hide the Composer backdrop.
*/
hideBackdrop() {
if (this.$backdrop) this.$backdrop.remove();
}
/**
* Show the composer.
*
* @public
*/
show() {
// If the composer is hidden or minimized, we'll need to update its
// position. Otherwise, if the composer is already showing (whether it's
// fullscreen or not), we can leave it as is.
if ([Composer.PositionEnum.MINIMIZED, Composer.PositionEnum.HIDDEN].indexOf(this.position) !== -1) {
this.position = Composer.PositionEnum.NORMAL;
if (this.position === Composer.PositionEnum.NORMAL || this.position === Composer.PositionEnum.FULLSCREEN) {
return;
}
this.update();
this.animateToPosition(Composer.PositionEnum.NORMAL);
if (this.isFullScreen()) {
this.$().css('top', $(window).scrollTop());
this.showBackdrop();
this.component.focus();
}
}
/**
@@ -385,8 +372,20 @@ class Composer extends Component {
* @public
*/
hide() {
this.position = Composer.PositionEnum.HIDDEN;
this.update();
const $composer = this.$();
// Animate the composer sliding down off the bottom edge of the viewport.
// Only when the animation is completed, update the Composer state flag and
// other elements on the page.
$composer.stop(true).animate({bottom: -$composer.height()}, 'fast', () => {
this.position = Composer.PositionEnum.HIDDEN;
this.clear();
m.redraw();
$composer.hide();
this.hideBackdrop();
this.updateBodyPadding();
});
}
/**
@@ -407,10 +406,12 @@ class Composer extends Component {
* @public
*/
minimize() {
if (this.position !== Composer.PositionEnum.HIDDEN) {
this.position = Composer.PositionEnum.MINIMIZED;
this.update();
}
if (this.position === Composer.PositionEnum.HIDDEN) return;
this.animateToPosition(Composer.PositionEnum.MINIMIZED);
this.$().css('top', 'auto');
this.hideBackdrop();
}
/**
@@ -422,7 +423,9 @@ class Composer extends Component {
fullScreen() {
if (this.position !== Composer.PositionEnum.HIDDEN) {
this.position = Composer.PositionEnum.FULLSCREEN;
this.update();
m.redraw();
this.updateHeight();
this.component.focus();
}
}
@@ -434,7 +437,9 @@ class Composer extends Component {
exitFullScreen() {
if (this.position === Composer.PositionEnum.FULLSCREEN) {
this.position = Composer.PositionEnum.NORMAL;
this.update();
m.redraw();
this.updateHeight();
this.component.focus();
}
}
@@ -449,28 +454,28 @@ class Composer extends Component {
if (this.position === Composer.PositionEnum.FULLSCREEN) {
items.add('exitFullScreen', ComposerButton.component({
icon: 'compress',
title: app.trans('core.exit_full_screen'),
title: app.translator.trans('core.forum.composer.exit_full_screen_tooltip'),
onclick: this.exitFullScreen.bind(this)
}));
} else {
if (this.position !== Composer.PositionEnum.MINIMIZED) {
items.add('minimize', ComposerButton.component({
icon: 'minus minimize',
title: app.trans('core.minimize'),
title: app.translator.trans('core.forum.composer.minimize_tooltip'),
onclick: this.minimize.bind(this),
itemClassName: 'App-backControl'
}));
items.add('fullScreen', ComposerButton.component({
icon: 'expand',
title: app.trans('core.full_screen'),
title: app.translator.trans('core.forum.composer.full_screen_tooltip'),
onclick: this.fullScreen.bind(this)
}));
}
items.add('close', ComposerButton.component({
icon: 'times',
title: app.trans('core.close'),
title: app.translator.trans('core.forum.composer.close_tooltip'),
onclick: this.close.bind(this)
}));
}

View File

@@ -22,9 +22,7 @@ import ItemList from 'flarum/utils/ItemList';
* @abstract
*/
export default class ComposerBody extends Component {
constructor(props) {
super(props);
init() {
/**
* Whether or not the component is loading.
*
@@ -58,7 +56,7 @@ export default class ComposerBody extends Component {
this.editor.props.disabled = this.loading;
return (
<div className="ComposerBody">
<div className={'ComposerBody ' + (this.props.className || '')}>
{avatar(this.props.user, {className: 'ComposerBody-avatar'})}
<div className="ComposerBody-content">
<ul className="ComposerBody-header">{listItems(this.headerItems().toArray())}</ul>
@@ -104,4 +102,12 @@ export default class ComposerBody extends Component {
*/
onsubmit() {
}
/**
* Stop loading.
*/
loaded() {
this.loading = false;
m.redraw();
}
}

View File

@@ -1,4 +1,5 @@
import ComposerBody from 'flarum/components/ComposerBody';
import extractText from 'flarum/utils/extractText';
/**
* The `DiscussionComposer` component displays the composer content for starting
@@ -12,8 +13,8 @@ import ComposerBody from 'flarum/components/ComposerBody';
* - `titlePlaceholder`
*/
export default class DiscussionComposer extends ComposerBody {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* The value of the title input.
@@ -26,16 +27,19 @@ export default class DiscussionComposer extends ComposerBody {
static initProps(props) {
super.initProps(props);
props.placeholder = props.placeholder || app.trans('core.write_a_post');
props.submitLabel = props.submitLabel || app.trans('core.post_discussion');
props.confirmExit = props.confirmExit || app.trans('core.confirm_discard_discussion');
props.titlePlaceholder = props.titlePlaceholder || app.trans('core.discussion_title');
props.placeholder = props.placeholder || extractText(app.translator.trans('core.forum.composer_discussion.body_placeholder'));
props.submitLabel = props.submitLabel || app.translator.trans('core.forum.composer_discussion.submit_button');
props.confirmExit = props.confirmExit || extractText(app.translator.trans('core.forum.composer_discussion.discard_confirmation'));
props.titlePlaceholder = props.titlePlaceholder || extractText(app.translator.trans('core.forum.composer_discussion.title_placeholder'));
props.className = 'ComposerBody--discussion';
}
headerItems() {
const items = super.headerItems();
items.add('title', (
items.add('title', <h3>{app.translator.trans('core.forum.composer_discussion.title')}</h3>, 100);
items.add('discussionTitle', (
<h3>
<input className="FormControl"
value={this.title()}
@@ -64,23 +68,6 @@ export default class DiscussionComposer extends ComposerBody {
m.redraw.strategy('none');
}
config(isInitialized, context) {
super.config(isInitialized, context);
// If the user presses the backspace key in the text editor, and the cursor
// is already at the start, then we'll move the focus back into the title
// input.
this.editor.$('textarea').keydown((e) => {
if (e.which === 8 && e.target.selectionStart === 0 && e.target.selectionEnd === 0) {
e.preventDefault();
const $title = this.$(':input:enabled:visible:first')[0];
$title.focus();
$title.selectionStart = $title.selectionEnd = $title.value.length;
}
});
}
preventExit() {
return (this.title() || this.content()) && this.props.confirmExit;
}
@@ -108,11 +95,7 @@ export default class DiscussionComposer extends ComposerBody {
app.cache.discussionList.addDiscussion(discussion);
m.route(app.route.discussion(discussion));
},
response => {
this.loading = false;
m.redraw();
app.alertErrors(response.errors);
}
this.loaded.bind(this)
);
}
}

View File

@@ -2,6 +2,7 @@ import Component from 'flarum/Component';
import DiscussionListItem from 'flarum/components/DiscussionListItem';
import Button from 'flarum/components/Button';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
import Placeholder from 'flarum/components/Placeholder';
/**
* The `DiscussionList` component displays a list of discussions.
@@ -12,9 +13,7 @@ import LoadingIndicator from 'flarum/components/LoadingIndicator';
* to send along in the API request to get discussion results.
*/
export default class DiscussionList extends Component {
constructor(...args) {
super(...args);
init() {
/**
* Whether or not discussion results are loading.
*
@@ -47,12 +46,21 @@ export default class DiscussionList extends Component {
loading = LoadingIndicator.component();
} else if (this.moreResults) {
loading = Button.component({
children: app.trans('core.load_more'),
children: app.translator.trans('core.forum.discussion_list.load_more_button'),
className: 'Button',
onclick: this.loadMore.bind(this)
});
}
if (this.discussions.length === 0 && !this.loading) {
const text = app.translator.trans('core.forum.discussion_list.empty_text');
return (
<div className="DiscussionList">
{Placeholder.component({text})}
</div>
);
}
return (
<div className="DiscussionList">
<ul className="DiscussionList-discussions">
@@ -107,7 +115,7 @@ export default class DiscussionList extends Component {
map.latest = '-lastTime';
map.top = '-commentsCount';
map.newest = '-startTime';
map.oldest = '+startTime';
map.oldest = 'startTime';
return map;
}
@@ -179,12 +187,7 @@ export default class DiscussionList extends Component {
this.loading = false;
this.moreResults = !!results.payload.links.next;
// Since this may be called during the component's constructor, i.e. in the
// middle of a redraw, forcing another redraw would not bode well. Instead
// we start/end a computation so Mithril will only redraw if it isn't
// already doing so.
m.startComputation();
m.endComputation();
m.lazyRedraw();
return results;
}

View File

@@ -13,6 +13,7 @@ import SubtreeRetainer from 'flarum/utils/SubtreeRetainer';
import DiscussionControls from 'flarum/utils/DiscussionControls';
import slidable from 'flarum/utils/slidable';
import extractText from 'flarum/utils/extractText';
import classList from 'flarum/utils/classList';
/**
* The `DiscussionListItem` component shows a single discussion in the
@@ -24,9 +25,7 @@ import extractText from 'flarum/utils/extractText';
* - `params`
*/
export default class DiscussionListItem extends Component {
constructor(...args) {
super(...args);
init() {
/**
* Set up a subtree retainer so that the discussion will not be redrawn
* unless new data comes in.
@@ -43,6 +42,16 @@ export default class DiscussionListItem extends Component {
);
}
attrs() {
return {
className: classList([
'DiscussionListItem',
this.active() ? 'active' : '',
this.props.discussion.isHidden() ? 'DiscussionListItem--hidden' : ''
])
};
}
view() {
const retain = this.subtree.retain();
@@ -51,13 +60,15 @@ export default class DiscussionListItem extends Component {
const discussion = this.props.discussion;
const startUser = discussion.startUser();
const isUnread = discussion.isUnread();
const isRead = discussion.isRead();
const showUnread = !this.showRepliesCount() && isUnread;
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 attrs = this.attrs();
return (
<div className={'DiscussionListItem ' + (this.active() ? 'active' : '')}>
<div {...attrs}>
{controls.length ? Dropdown.component({
icon: 'ellipsis-v',
@@ -71,10 +82,10 @@ export default class DiscussionListItem extends Component {
{icon('check')}
</a>
<div className={'DiscussionListItem-content Slidable-content' + (isUnread ? ' unread' : '')}>
<div className={'DiscussionListItem-content Slidable-content' + (isUnread ? ' unread' : '') + (isRead ? ' read' : '')}>
<a href={startUser ? app.route.user(startUser) : '#'}
className="DiscussionListItem-author"
title={extractText(app.trans('core.discussion_started', {user: startUser, ago: humanTime(discussion.startTime())}))}
title={extractText(app.translator.trans('core.forum.discussion_list.started_text', {user: startUser, ago: humanTime(discussion.startTime())}))}
config={function(element) {
$(element).tooltip({placement: 'right'});
m.route.apply(this, arguments);
@@ -95,7 +106,7 @@ export default class DiscussionListItem extends Component {
<span className="DiscussionListItem-count"
onclick={this.markAsRead.bind(this)}
title={showUnread ? app.trans('core.mark_as_read') : ''}>
title={showUnread ? app.translator.trans('core.forum.discussion_list.mark_as_read_tooltip') : ''}>
{abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'repliesCount']())}
</span>

View File

@@ -1,4 +1,4 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
import ItemList from 'flarum/utils/ItemList';
import DiscussionHero from 'flarum/components/DiscussionHero';
import PostStream from 'flarum/components/PostStream';
@@ -6,17 +6,15 @@ import PostStreamScrubber from 'flarum/components/PostStreamScrubber';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
import SplitDropdown from 'flarum/components/SplitDropdown';
import listItems from 'flarum/helpers/listItems';
import mixin from 'flarum/utils/mixin';
import evented from 'flarum/utils/evented';
import DiscussionControls from 'flarum/utils/DiscussionControls';
/**
* The `DiscussionPage` component displays a whole discussion page, including
* the discussion list pane, the hero, the posts, and the sidebar.
*/
export default class DiscussionPage extends mixin(Component, evented) {
constructor(...args) {
super(...args);
export default class DiscussionPage extends Page {
init() {
super.init();
/**
* The discussion that is being viewed.
@@ -43,18 +41,14 @@ export default class DiscussionPage extends mixin(Component, evented) {
app.pane.enable();
app.pane.hide();
if (app.current instanceof DiscussionPage) {
if (app.previous instanceof DiscussionPage) {
m.redraw.strategy('diff');
}
}
// Push onto the history stack, but use a generalised key so that navigating
// to a few different discussions won't override the behaviour of the back
// button.
app.history.push('discussion');
app.current = this;
app.drawer.hide();
app.modal.close();
this.bodyClass = 'App--discussion';
}
onunload(e) {
@@ -67,9 +61,9 @@ export default class DiscussionPage extends mixin(Component, evented) {
if (idParam && idParam.split('-')[0] === this.discussion.id()) {
e.preventDefault();
const near = Number(m.route.param('near')) || 1;
const near = m.route.param('near') || '1';
if (near !== Number(this.near)) {
if (near !== String(this.near)) {
this.stream.goToNumber(near);
}
@@ -121,13 +115,6 @@ export default class DiscussionPage extends mixin(Component, evented) {
);
}
config(isInitialized, context) {
if (isInitialized) return;
$('#app').addClass('App--discussion');
context.onunload = () => $('#app').removeClass('App--discussion');
}
/**
* Clear and reload the discussion.
*/
@@ -141,20 +128,15 @@ export default class DiscussionPage extends mixin(Component, evented) {
// component for the first time on page load, then any calls to m.redraw
// will be ineffective and thus any configs (scroll code) will be run
// before stuff is drawn to the page.
setTimeout(this.init.bind(this, preloadedDiscussion));
setTimeout(this.show.bind(this, preloadedDiscussion), 0);
} else {
const params = this.requestParams();
app.store.find('discussions', m.route.param('id').split('-')[0], params)
.then(this.init.bind(this));
.then(this.show.bind(this));
}
// Since this may be called during the component's constructor, i.e. in the
// middle of a redraw, forcing another redraw would not bode well. Instead
// we start/end a computation so Mithril will only redraw if it isn't
// already doing so.
m.startComputation();
m.endComputation();
m.lazyRedraw();
}
/**
@@ -174,9 +156,10 @@ export default class DiscussionPage extends mixin(Component, evented) {
*
* @param {Discussion} discussion
*/
init(discussion) {
show(discussion) {
this.discussion = discussion;
app.history.push('discussion', discussion.title());
app.setTitle(discussion.title());
app.setTitleCount(0);
@@ -201,9 +184,7 @@ export default class DiscussionPage extends mixin(Component, evented) {
// the specific post that was routed to.
this.stream = new PostStream({discussion, includedPosts});
this.stream.on('positionChanged', this.positionChanged.bind(this));
this.stream.goToNumber(m.route.param('near') || includedPosts[0].number(), true);
this.trigger('loaded', discussion);
this.stream.goToNumber(m.route.param('near') || (includedPosts[0] && includedPosts[0].number()), true);
}
/**
@@ -293,7 +274,7 @@ export default class DiscussionPage extends mixin(Component, evented) {
m.route(url, true);
window.history.replaceState(null, document.title, url);
app.history.push('discussion');
app.history.push('discussion', discussion.title());
// If the user hasn't read past here before, then we'll update their read
// state and redraw.

View File

@@ -20,6 +20,6 @@ export default class DiscussionRenamedNotification extends Notification {
}
content() {
return app.trans('core.discussion_renamed_notification', {user: this.props.notification.sender()});
return app.translator.trans('core.forum.notifications.discussion_renamed_text', {user: this.props.notification.sender()});
}
}

View File

@@ -1,4 +1,5 @@
import EventPost from 'flarum/components/EventPost';
import extractText from 'flarum/utils/extractText';
/**
* The `DiscussionRenamedPost` component displays a discussion event post
@@ -13,8 +14,11 @@ export default class DiscussionRenamedPost extends EventPost {
return 'pencil';
}
descriptionKey() {
return 'core.discussion_renamed_post';
description(data) {
const renamed = app.translator.trans('core.forum.post_stream.discussion_renamed_text', data);
const oldName = app.translator.trans('core.forum.post_stream.discussion_renamed_old_tooltip', data);
return <span title={extractText(oldName)}>{renamed}</span>;
}
descriptionData() {
@@ -23,8 +27,8 @@ export default class DiscussionRenamedPost extends EventPost {
const newTitle = post.content()[1];
return {
old: <strong className="DiscussionRenamedPost-old">{oldTitle}</strong>,
new: <strong className="DiscussionRenamedPost-new">{newTitle}</strong>
'old': oldTitle,
'new': <strong className="DiscussionRenamedPost-new">{newTitle}</strong>
};
}
}

View File

@@ -13,6 +13,8 @@ export default class DiscussionsSearchSource {
}
search(query) {
query = query.toLowerCase();
this.results[query] = [];
const params = {
@@ -25,14 +27,16 @@ export default class DiscussionsSearchSource {
}
view(query) {
query = query.toLowerCase();
const results = this.results[query] || [];
return [
<li className="Dropdown-header">{app.trans('core.discussions')}</li>,
<li className="Dropdown-header">{app.translator.trans('core.forum.search.discussions_heading')}</li>,
<li>
{LinkButton.component({
icon: 'search',
children: app.trans('core.search_all_discussions', {query}),
children: app.translator.trans('core.forum.search.all_discussions_button', {query}),
href: app.route('index', {q: query})
})}
</li>,

View File

@@ -6,8 +6,8 @@ import DiscussionList from 'flarum/components/DiscussionList';
* page.
*/
export default class DiscussionsUserPage extends UserPage {
constructor(...args) {
super(...args);
init() {
super.init();
this.loadUser(m.route.param('username'));
}

View File

@@ -1,6 +1,13 @@
import ComposerBody from 'flarum/components/ComposerBody';
import icon from 'flarum/helpers/icon';
function minimizeComposerIfFullScreen(e) {
if (app.composer.isFullScreen()) {
app.composer.minimize();
e.stopPropagation();
}
}
/**
* The `EditPostComposer` component displays the composer content for editing a
* post. It sets the initial content to the content of the post that is being
@@ -12,10 +19,12 @@ import icon from 'flarum/helpers/icon';
* - `post`
*/
export default class EditPostComposer extends ComposerBody {
constructor(...args) {
super(...args);
init() {
super.init();
this.editor.props.preview = e => {
minimizeComposerIfFullScreen(e);
this.editor.props.preview = () => {
m.route(app.route.post(this.props.post));
};
}
@@ -23,8 +32,8 @@ export default class EditPostComposer extends ComposerBody {
static initProps(props) {
super.initProps(props);
props.submitLabel = props.submitLabel || app.trans('core.save_changes');
props.confirmExit = props.confirmExit || app.trans('core.confirm_discard_edit');
props.submitLabel = props.submitLabel || app.translator.trans('core.forum.composer_edit.submit_button');
props.confirmExit = props.confirmExit || app.translator.trans('core.forum.composer_edit.discard_confirmation');
props.originalContent = props.originalContent || props.post.content();
props.user = props.user || props.post.user();
@@ -35,11 +44,17 @@ export default class EditPostComposer extends ComposerBody {
const items = super.headerItems();
const post = this.props.post;
const routeAndMinimize = function(element, isInitialized) {
if (isInitialized) return;
$(element).on('click', minimizeComposerIfFullScreen);
m.route.apply(this, arguments);
};
items.add('title', (
<h3>
{icon('pencil')}{' '}
<a href={app.route.discussion(post.discussion(), post.number())} config={m.route}>
{app.trans('core.editing_post', {number: post.number(), discussion: post.discussion().title()})}
{icon('pencil')} {' '}
<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()})}
</a>
</h3>
));
@@ -64,11 +79,8 @@ export default class EditPostComposer extends ComposerBody {
const data = this.data();
this.props.post.save(data).then(
() => {
app.composer.hide();
m.redraw();
},
() => this.loading = false
() => app.composer.hide(),
this.loaded.bind(this)
);
}
}

View File

@@ -2,18 +2,20 @@ import Modal from 'flarum/components/Modal';
import Button from 'flarum/components/Button';
import GroupBadge from 'flarum/components/GroupBadge';
import Group from 'flarum/models/Group';
import extractText from 'flarum/utils/extractText';
/**
* The `EditUserModal` component displays a modal dialog with a login form.
*/
export default class EditUserModal extends Modal {
constructor(...args) {
super(...args);
init() {
super.init();
const user = this.props.user;
this.username = m.prop(user.username() || '');
this.email = m.prop(user.email() || '');
this.isActivated = m.prop(user.isActivated() || false);
this.setPassword = m.prop(false);
this.password = m.prop(user.password() || '');
this.groups = {};
@@ -28,7 +30,7 @@ export default class EditUserModal extends Modal {
}
title() {
return 'Edit User';
return app.translator.trans('core.forum.edit_user.title');
}
content() {
@@ -36,52 +38,60 @@ export default class EditUserModal extends Modal {
<div className="Modal-body">
<div className="Form">
<div className="Form-group">
<label>Username</label>
<input className="FormControl" placeholder={app.trans('core.username')}
value={this.username()}
onchange={m.withAttr('value', this.username)} />
<label>{app.translator.trans('core.forum.edit_user.username_heading')}</label>
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.username_label'))}
bidi={this.username} />
</div>
<div className="Form-group">
<label>Email</label>
<div>
<input className="FormControl" placeholder={app.trans('core.email')}
value={this.email()}
onchange={m.withAttr('value', this.email)} />
</div>
</div>
<div className="Form-group">
<label>Password</label>
<div>
<label className="checkbox">
<input type="checkbox" checked={this.setPassword()} onchange={e => {
this.setPassword(e.target.checked);
m.redraw(true);
if (e.target.checked) this.$('[name=password]').select();
m.redraw.strategy('none');
}}/>
Set new password
</label>
{this.setPassword() ? (
<input className="FormControl" type="password" name="password" placeholder={app.trans('core.password')}
value={this.password()}
onchange={m.withAttr('value', this.password)} />
{app.session.user !== this.props.user ? [
<div className="Form-group">
<label>{app.translator.trans('core.forum.edit_user.email_heading')}</label>
<div>
<input className="FormControl" placeholder={extractText(app.translator.trans('core.forum.edit_user.email_label'))}
bidi={this.email} />
</div>
{!this.isActivated() ? (
<div>
{Button.component({
className: 'Button Button--block',
children: app.translator.trans('core.forum.edit_user.activate_button'),
loading: this.loading,
onclick: this.activate.bind(this)
})}
</div>
) : ''}
</div>,
<div className="Form-group">
<label>{app.translator.trans('core.forum.edit_user.password_heading')}</label>
<div>
<label className="checkbox">
<input type="checkbox" checked={this.setPassword()} onchange={e => {
this.setPassword(e.target.checked);
m.redraw(true);
if (e.target.checked) this.$('[name=password]').select();
m.redraw.strategy('none');
}}/>
{app.translator.trans('core.forum.edit_user.set_password_label')}
</label>
{this.setPassword() ? (
<input className="FormControl" type="password" name="password" placeholder={extractText(app.translator.trans('core.forum.edit_user.password_label'))}
bidi={this.password} />
) : ''}
</div>
</div>
</div>
] : ''}
<div className="Form-group EditUserModal-groups">
<label>Groups</label>
<label>{app.translator.trans('core.forum.edit_user.groups_heading')}</label>
<div>
{Object.keys(this.groups)
.map(id => app.store.getById('groups', id))
.map(group => (
<label className="checkbox">
<input type="checkbox"
checked={this.groups[group.id()]()}
disabled={this.props.user.id() === '1' && group.id() === Group.ADMINISTRATOR_ID}
onchange={m.withAttr('checked', this.groups[group.id()])}/>
bidi={this.groups[group.id()]}
disabled={this.props.user.id() === '1' && group.id() === Group.ADMINISTRATOR_ID} />
{GroupBadge.component({group, label: ''})} {group.nameSingular()}
</label>
))}
@@ -93,7 +103,7 @@ export default class EditUserModal extends Modal {
className: 'Button Button--primary',
type: 'submit',
loading: this.loading,
children: app.trans('core.save_changes')
children: app.translator.trans('core.forum.edit_user.submit_button')
})}
</div>
</div>
@@ -101,31 +111,55 @@ export default class EditUserModal extends Modal {
);
}
onsubmit(e) {
e.preventDefault();
activate() {
this.loading = true;
const data = {
username: this.username(),
isActivated: true,
};
this.props.user.save(data, {errorHandler: this.onerror.bind(this)})
.then(() => {
this.isActivated(true);
this.loading = false;
m.redraw();
})
.catch(() => {
this.loading = false;
m.redraw();
});
}
data() {
const groups = Object.keys(this.groups)
.filter(id => this.groups[id]())
.map(id => app.store.getById('groups', id));
const data = {
username: this.username(),
email: this.email(),
relationships: {groups}
};
if (app.session.user !== this.props.user) {
data.email = this.email();
}
if (this.setPassword()) {
data.password = this.password();
}
this.props.user.save(data).then(
() => this.hide(),
response => {
return data;
}
onsubmit(e) {
e.preventDefault();
this.loading = true;
this.props.user.save(this.data(), {errorHandler: this.onerror.bind(this)})
.then(this.hide.bind(this))
.catch(() => {
this.loading = false;
this.handleErrors(response);
}
);
m.redraw();
});
}
}

View File

@@ -16,9 +16,11 @@ import icon from 'flarum/helpers/icon';
*/
export default class EventPost extends Post {
attrs() {
return {
className: 'EventPost ' + ucfirst(this.props.post.contentType()) + 'Post'
};
const attrs = super.attrs();
attrs.className += ' EventPost ' + ucfirst(this.props.post.contentType()) + 'Post';
return attrs;
}
content() {
@@ -31,12 +33,12 @@ export default class EventPost extends Post {
: username
});
return [
return super.content().concat([
icon(this.icon(), {className: 'EventPost-icon'}),
<div class="EventPost-info">
{app.trans(this.descriptionKey(), data)}
{this.description(data)}
</div>
];
]);
}
/**
@@ -48,6 +50,16 @@ export default class EventPost extends Post {
return '';
}
/**
* Get the description text for the event.
*
* @param {Object} data
* @return {String|Object} The description to render in the DOM
*/
description(data) {
return app.translator.transChoice(this.descriptionKey(), data.count, data);
}
/**
* Get the translation key for the description of the event.
*

View File

@@ -1,6 +1,7 @@
import Modal from 'flarum/components/Modal';
import Alert from 'flarum/components/Alert';
import Button from 'flarum/components/Button';
import extractText from 'flarum/utils/extractText';
/**
* The `ForgotPasswordModal` component displays a modal which allows the user to
@@ -11,8 +12,8 @@ import Button from 'flarum/components/Button';
* - `email`
*/
export default class ForgotPasswordModal extends Modal {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* The value of the email input.
@@ -34,21 +35,19 @@ export default class ForgotPasswordModal extends Modal {
}
title() {
return app.trans('core.forgot_password');
return app.translator.trans('core.forum.forgot_password.title');
}
content() {
if (this.success) {
const emailProviderName = this.email().split('@')[1];
return (
<div className="Modal-body">
<div className="Form Form--centered">
<p className="helpText">{app.trans('core.password_reset_email_sent')}</p>
<p className="helpText">{app.translator.trans('core.forum.forgot_password.email_sent_message')}</p>
<div className="Form-group">
<a href={'http://' + emailProviderName} className="Button Button--primary Button--block">
{app.trans('core.go_to', {location: emailProviderName})}
</a>
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
{app.translator.trans('core.forum.forgot_password.dismiss_button')}
</Button>
</div>
</div>
</div>
@@ -58,9 +57,9 @@ export default class ForgotPasswordModal extends Modal {
return (
<div className="Modal-body">
<div className="Form Form--centered">
<p className="helpText">{app.trans('core.forgot_password_help')}</p>
<p className="helpText">{app.translator.trans('core.forum.forgot_password.text')}</p>
<div className="Form-group">
<input className="FormControl" name="email" type="email" placeholder={app.trans('core.email')}
<input className="FormControl" name="email" type="email" placeholder={extractText(app.translator.trans('core.forum.forgot_password.email_placeholder'))}
value={this.email()}
onchange={m.withAttr('value', this.email)}
disabled={this.loading} />
@@ -70,7 +69,7 @@ export default class ForgotPasswordModal extends Modal {
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.trans('core.recover_password')
children: app.translator.trans('core.forum.forgot_password.submit_button')
})}
</div>
</div>
@@ -87,23 +86,21 @@ export default class ForgotPasswordModal extends Modal {
method: 'POST',
url: app.forum.attribute('apiUrl') + '/forgot',
data: {email: this.email()},
handlers: {
404: () => {
this.alert = new Alert({type: 'warning', message: 'That email wasn\'t found in our database.'});
throw new Error();
}
}
}).then(
() => {
this.loading = false;
errorHandler: this.onerror.bind(this)
})
.then(() => {
this.success = true;
this.alert = null;
m.redraw();
},
response => {
this.loading = false;
this.handleErrors(response);
}
);
})
.catch(() => {})
.then(this.loaded.bind(this));
}
onerror(error) {
if (error.status === 404) {
error.alert.props.children = app.translator.trans('core.forum.forgot_password.not_found_message');
}
super.onerror(error);
}
}

View File

@@ -1,8 +1,6 @@
import Component from 'flarum/Component';
import ItemList from 'flarum/utils/ItemList';
import listItems from 'flarum/helpers/listItems';
import SelectDropdown from 'flarum/components/SelectDropdown';
import Button from 'flarum/components/Button';
/**
* The `HeaderPrimary` component displays primary header controls. On the
@@ -17,6 +15,13 @@ export default class HeaderPrimary extends Component {
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Build an item list for the controls.
*

View File

@@ -22,6 +22,13 @@ export default class HeaderSecondary extends Component {
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Build an item list for the controls.
*
@@ -30,16 +37,16 @@ export default class HeaderSecondary extends Component {
items() {
const items = new ItemList();
items.add('search', app.search.render());
items.add('search', app.search.render(), 30);
if (Object.keys(app.locales).length > 1) {
if (app.forum.attribute("showLanguageSelector") && Object.keys(app.data.locales).length > 1) {
const locales = [];
for (const locale in app.locales) {
for (const locale in app.data.locales) {
locales.push(Button.component({
active: app.locale === locale,
children: app.locales[locale],
icon: app.locale === locale ? 'check' : true,
active: app.data.locale === locale,
children: app.data.locales[locale],
icon: app.data.locale === locale ? 'check' : true,
onclick: () => {
if (app.session.user) {
app.session.user.savePreferences({locale}).then(() => window.location.reload());
@@ -54,29 +61,29 @@ export default class HeaderSecondary extends Component {
items.add('locale', SelectDropdown.component({
children: locales,
buttonClassName: 'Button Button--link'
}));
}), 20);
}
if (app.session.user) {
items.add('notifications', NotificationsDropdown.component());
items.add('session', SessionDropdown.component());
items.add('notifications', NotificationsDropdown.component(), 10);
items.add('session', SessionDropdown.component(), 0);
} else {
if (app.forum.attribute('allowSignUp')) {
items.add('signUp',
Button.component({
children: app.trans('core.sign_up'),
children: app.translator.trans('core.forum.header.sign_up_link'),
className: 'Button Button--link',
onclick: () => app.modal.show(new SignUpModal())
})
}), 10
);
}
items.add('logIn',
Button.component({
children: app.trans('core.log_in'),
children: app.translator.trans('core.forum.header.log_in_link'),
className: 'Button Button--link',
onclick: () => app.modal.show(new LogInModal())
})
}), 0
);
}

View File

@@ -1,13 +1,14 @@
import Component from 'flarum/Component';
import { extend } from 'flarum/extend';
import Page from 'flarum/components/Page';
import ItemList from 'flarum/utils/ItemList';
import affixSidebar from 'flarum/utils/affixSidebar';
import listItems from 'flarum/helpers/listItems';
import icon from 'flarum/helpers/icon';
import DiscussionList from 'flarum/components/DiscussionList';
import WelcomeHero from 'flarum/components/WelcomeHero';
import DiscussionComposer from 'flarum/components/DiscussionComposer';
import LogInModal from 'flarum/components/LogInModal';
import DiscussionPage from 'flarum/components/DiscussionPage';
import Select from 'flarum/components/Select';
import Dropdown from 'flarum/components/Dropdown';
import Button from 'flarum/components/Button';
import LinkButton from 'flarum/components/LinkButton';
import SelectDropdown from 'flarum/components/SelectDropdown';
@@ -16,22 +17,22 @@ import SelectDropdown from 'flarum/components/SelectDropdown';
* The `IndexPage` component displays the index page, including the welcome
* hero, the sidebar, and the discussion list.
*/
export default class IndexPage extends Component {
constructor(...args) {
super(...args);
export default class IndexPage extends Page {
init() {
super.init();
// If the user is returning from a discussion page, then take note of which
// discussion they have just visited. After the view is rendered, we will
// scroll down so that this discussion is in view.
if (app.current instanceof DiscussionPage) {
this.lastDiscussion = app.current.discussion;
if (app.previous instanceof DiscussionPage) {
this.lastDiscussion = app.previous.discussion;
}
// If the user is coming from the discussion list, then they have either
// just switched one of the parameters (filter, sort, search) or they
// probably want to refresh the results. We will clear the discussion list
// cache so that results are reloaded.
if (app.current instanceof IndexPage) {
if (app.previous instanceof IndexPage) {
app.cache.discussionList = null;
}
@@ -54,10 +55,9 @@ export default class IndexPage extends Component {
app.cache.discussionList = new DiscussionList({params});
}
app.history.push('index');
app.current = this;
app.drawer.hide();
app.modal.close();
app.history.push('index', app.translator.trans('core.forum.header.back_to_index_tooltip'));
this.bodyClass = 'App--index';
}
onunload() {
@@ -71,7 +71,7 @@ export default class IndexPage extends Component {
<div className="IndexPage">
{this.hero()}
<div className="container">
<nav className="IndexPage-nav sideNav" config={affixSidebar}>
<nav className="IndexPage-nav sideNav">
<ul>{listItems(this.sidebarItems().toArray())}</ul>
</nav>
<div className="IndexPage-results sideNavOffset">
@@ -87,27 +87,30 @@ export default class IndexPage extends Component {
}
config(isInitialized, context) {
super.config(...arguments);
if (isInitialized) return;
$('#app').addClass('App--index');
context.onunload = () => {
$('#app').removeClass('App--index')
.css('min-height', '');
};
extend(context, 'onunload', () => $('#app').css('min-height', ''));
app.setTitle('');
app.setTitleCount(0);
// Work out the difference between the height of this hero and that of the
// previous hero. Maintain the same scroll position relative to the bottom
// of the hero so that the 'fixed' sidebar doesn't jump around.
const heroHeight = this.$('.Hero').outerHeight();
// of the hero so that the sidebar doesn't jump around.
const oldHeroHeight = app.cache.heroHeight;
const heroHeight = app.cache.heroHeight = this.$('.Hero').outerHeight();
const scrollTop = app.cache.scrollTop;
$('#app').css('min-height', $(window).height() + heroHeight);
$(window).scrollTop(scrollTop - (app.cache.heroHeight - heroHeight));
app.cache.heroHeight = heroHeight;
// Scroll to the remembered position. We do this after a short delay so that
// it happens after the browser has done its own "back button" scrolling,
// which isn't right. https://github.com/flarum/core/issues/835
const scroll = () => $(window).scrollTop(scrollTop - oldHeroHeight + heroHeight);
scroll();
setTimeout(scroll, 1);
// If we've just returned from a discussion page, then the constructor will
// have set the `lastDiscussion` property. If this is the case, we want to
@@ -146,11 +149,11 @@ export default class IndexPage extends Component {
*/
sidebarItems() {
const items = new ItemList();
const canStartDiscussion = app.forum.canStartDiscussion() || !app.session.user;
const canStartDiscussion = app.forum.attribute('canStartDiscussion') || !app.session.user;
items.add('newDiscussion',
Button.component({
children: canStartDiscussion ? app.trans('core.start_a_discussion') : 'Can\'t Start Discussion',
children: app.translator.trans(canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button'),
icon: 'edit',
className: 'Button Button--primary IndexPage-newDiscussion',
itemClassName: 'App-primaryControl',
@@ -183,7 +186,7 @@ export default class IndexPage extends Component {
items.add('allDiscussions',
LinkButton.component({
href: app.route('index', params),
children: app.trans('core.all_discussions'),
children: app.translator.trans('core.forum.index.all_discussions_link'),
icon: 'comments-o'
}),
100
@@ -201,17 +204,28 @@ export default class IndexPage extends Component {
*/
viewItems() {
const items = new ItemList();
const sortMap = app.cache.discussionList.sortMap();
const sortOptions = {};
for (const i in app.cache.discussionList.sortMap()) {
sortOptions[i] = app.trans('core.sort_' + i);
for (const i in sortMap) {
sortOptions[i] = app.translator.trans('core.forum.index_sort.' + i + '_button');
}
items.add('sort',
Select.component({
options: sortOptions,
value: this.params().sort,
onchange: this.changeSort.bind(this)
Dropdown.component({
buttonClassName: 'Button',
label: sortOptions[this.params().sort] || Object.keys(sortMap).map(key => sortOptions[key])[0],
children: Object.keys(sortOptions).map(value => {
const label = sortOptions[value];
const active = (this.params().sort || Object.keys(sortMap)[0]) === value;
return Button.component({
children: label,
icon: active ? 'check' : true,
onclick: this.changeSort.bind(this, value),
active: active,
})
}),
})
);
@@ -229,17 +243,23 @@ export default class IndexPage extends Component {
items.add('refresh',
Button.component({
title: app.trans('core.refresh'),
title: app.translator.trans('core.forum.index.refresh_tooltip'),
icon: 'refresh',
className: 'Button Button--icon',
onclick: () => app.cache.discussionList.refresh()
onclick: () => {
app.cache.discussionList.refresh();
if (app.session.user) {
app.store.find('users', app.session.user.id());
m.redraw();
}
}
})
);
if (app.session.user) {
items.add('markAllAsRead',
Button.component({
title: app.trans('core.mark_all_as_read'),
title: app.translator.trans('core.forum.index.mark_all_as_read_tooltip'),
icon: 'check',
className: 'Button Button--icon',
onclick: this.markAllAsRead.bind(this)
@@ -360,6 +380,10 @@ export default class IndexPage extends Component {
* @return void
*/
markAllAsRead() {
app.session.user.save({readTime: new Date()});
const confirmation = confirm(app.translator.trans('core.forum.index.mark_all_as_read_confirmation'));
if (confirmation) {
app.session.user.save({readTime: new Date()});
}
}
}

View File

@@ -0,0 +1,30 @@
import Button from 'flarum/components/Button';
/**
* The `LogInButton` component displays a social login button which will open
* a popup window containing the specified path.
*
* ### Props
*
* - `path`
*/
export default class LogInButton extends Button {
static initProps(props) {
props.className = (props.className || '') + ' LogInButton';
props.onclick = function() {
const width = 600;
const height = 400;
const $window = $(window);
window.open(app.forum.attribute('baseUrl') + props.path, 'logInPopup',
`width=${width},` +
`height=${height},` +
`top=${$window.height() / 2 - height / 2},` +
`left=${$window.width() / 2 - width / 2},` +
'status=no,scrollbars=no,resizable=no');
};
super.initProps(props);
}
}

View File

@@ -0,0 +1,25 @@
import Component from 'flarum/Component';
import ItemList from 'flarum/utils/ItemList';
/**
* The `LogInButtons` component displays a collection of social login buttons.
*/
export default class LogInButtons extends Component {
view() {
return (
<div className="LogInButtons">
{this.items().toArray()}
</div>
);
}
/**
* Build a list of LogInButton components.
*
* @return {ItemList}
* @public
*/
items() {
return new ItemList();
}
}

View File

@@ -3,25 +3,27 @@ import ForgotPasswordModal from 'flarum/components/ForgotPasswordModal';
import SignUpModal from 'flarum/components/SignUpModal';
import Alert from 'flarum/components/Alert';
import Button from 'flarum/components/Button';
import LogInButtons from 'flarum/components/LogInButtons';
import extractText from 'flarum/utils/extractText';
/**
* The `LogInModal` component displays a modal dialog with a login form.
*
* ### Props
*
* - `email`
* - `identification`
* - `password`
*/
export default class LogInModal extends Modal {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* The value of the email input.
* The value of the identification input.
*
* @type {Function}
*/
this.email = m.prop(this.props.email || '');
this.identification = m.prop(this.props.identification || '');
/**
* The value of the password input.
@@ -29,6 +31,13 @@ export default class LogInModal extends Modal {
* @type {Function}
*/
this.password = m.prop(this.props.password || '');
/**
* The value of the remember me input.
*
* @type {Function}
*/
this.remember = m.prop(!!this.props.remember);
}
className() {
@@ -36,45 +45,54 @@ export default class LogInModal extends Modal {
}
title() {
return app.trans('core.log_in');
return app.translator.trans('core.forum.log_in.title');
}
content() {
return [
<div className="Modal-body">
<LogInButtons/>
<div className="Form Form--centered">
<div className="Form-group">
<input className="FormControl" name="email" placeholder={app.trans('core.username_or_email')}
value={this.email()}
onchange={m.withAttr('value', this.email)}
<input className="FormControl" name="identification" type="text" placeholder={extractText(app.translator.trans('core.forum.log_in.username_or_email_placeholder'))}
bidi={this.identification}
disabled={this.loading} />
</div>
<div className="Form-group">
<input className="FormControl" name="password" type="password" placeholder={app.trans('core.password')}
value={this.password()}
onchange={m.withAttr('value', this.password)}
<input className="FormControl" name="password" type="password" placeholder={extractText(app.translator.trans('core.forum.log_in.password_placeholder'))}
bidi={this.password}
disabled={this.loading} />
</div>
<div className="Form-group">
<div>
<label className="checkbox">
<input type="checkbox" bidi={this.remember} disabled={this.loading} />
{app.translator.trans('core.forum.log_in.remember_me_label')}
</label>
</div>
</div>
<div className="Form-group">
{Button.component({
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.trans('core.log_in')
children: app.translator.trans('core.forum.log_in.submit_button')
})}
</div>
</div>
</div>,
<div className="Modal-footer">
<p className="LogInModal-forgotPassword">
<a onclick={this.forgotPassword.bind(this)}>{app.trans('core.forgot_password_link')}</a>
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>
</p>
{app.forum.attribute('allowSignUp') ? (
<p className="LogInModal-signUp">
{app.trans('core.before_sign_up_link')}{' '}
<a onclick={this.signUp.bind(this)}>{app.trans('core.sign_up')}</a>
{app.translator.trans('core.forum.log_in.sign_up_text', {a: <a onclick={this.signUp.bind(this)}/>})}
</p>
) : ''}
</div>
@@ -84,9 +102,11 @@ export default class LogInModal extends Modal {
/**
* Open the forgot password modal, prefilling it with an email if the user has
* entered one.
*
* @public
*/
forgotPassword() {
const email = this.email();
const email = this.identification();
const props = email.indexOf('@') !== -1 ? {email} : undefined;
app.modal.show(new ForgotPasswordModal(props));
@@ -95,17 +115,19 @@ export default class LogInModal extends Modal {
/**
* Open the sign up modal, prefilling it with an email/username/password if
* the user has entered one.
*
* @public
*/
signUp() {
const props = {password: this.password()};
const email = this.email();
props[email.indexOf('@') !== -1 ? 'email' : 'username'] = email;
const identification = this.identification();
props[identification.indexOf('@') !== -1 ? 'email' : 'username'] = identification;
app.modal.show(new SignUpModal(props));
}
onready() {
this.$('[name=' + (this.email() ? 'password' : 'email') + ']').select();
this.$('[name=' + (this.identification() ? 'password' : 'identification') + ']').select();
}
onsubmit(e) {
@@ -113,28 +135,22 @@ export default class LogInModal extends Modal {
this.loading = true;
const email = this.email();
const identification = this.identification();
const password = this.password();
const remember = this.remember();
app.session.login(email, password).then(
null,
response => {
this.loading = false;
app.session.login({identification, password, remember}, {errorHandler: this.onerror.bind(this)})
.then(
() => window.location.reload(),
this.loaded.bind(this)
);
}
if (response && response.code === 'confirm_email') {
this.alert = Alert.component({
children: app.trans('core.email_confirmation_required', {email: response.email})
});
} else {
this.alert = Alert.component({
type: 'error',
children: app.trans('core.invalid_login')
});
}
onerror(error) {
if (error.status === 401) {
error.alert.props.children = app.translator.trans('core.forum.log_in.invalid_login_message');
}
m.redraw();
this.onready();
}
);
super.onerror(error);
}
}

View File

@@ -19,18 +19,21 @@ export default class Notification extends Component {
const href = this.href();
return (
<div className={'Notification Notification--' + notification.contentType() + ' ' + (!notification.isRead() ? 'unread' : '')}
onclick={this.markAsRead.bind(this)}>
<a href={href} config={href.indexOf('://') === -1 ? m.route : undefined}>
{avatar(notification.sender())}
{icon(this.icon(), {className: 'Notification-icon'})}
<span className="Notification-content">{this.content()}</span>
{humanTime(notification.time())}
<div className="Notification-excerpt">
{this.excerpt()}
</div>
</a>
</div>
<a className={'Notification Notification--' + notification.contentType() + ' ' + (!notification.isRead() ? 'unread' : '')}
href={href}
config={function(element, isInitialized) {
if (href.indexOf('://') === -1) m.route.apply(this, arguments);
if (!isInitialized) $(element).click(this.markAsRead.bind(this));
}}>
{avatar(notification.sender())}
{icon(this.icon(), {className: 'Notification-icon'})}
<span className="Notification-content">{this.content()}</span>
{humanTime(notification.time())}
<div className="Notification-excerpt">
{this.excerpt()}
</div>
</a>
);
}
@@ -74,6 +77,10 @@ export default class Notification extends Component {
* Mark the notification as read.
*/
markAsRead() {
if (this.props.notification.isRead()) return;
app.session.user.pushAttributes({unreadNotificationsCount: app.session.user.unreadNotificationsCount() - 1});
this.props.notification.save({isRead: true});
}
}

View File

@@ -12,17 +12,15 @@ import ItemList from 'flarum/utils/ItemList';
* - `user`
*/
export default class NotificationGrid extends Component {
constructor(...args) {
super(...args);
init() {
/**
* Information about the available notification methods.
*
* @type {Array}
*/
this.methods = [
{name: 'alert', icon: 'bell', label: app.trans('core.alert')},
{name: 'email', icon: 'envelope-o', label: app.trans('core.email')}
{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')}
];
/**
@@ -182,7 +180,7 @@ export default class NotificationGrid extends Component {
items.add('discussionRenamed', {
name: 'discussionRenamed',
icon: 'pencil',
label: app.trans('core.notify_discussion_renamed')
label: app.translator.trans('core.forum.settings.notify_discussion_renamed_label')
});
return items;

View File

@@ -9,9 +9,7 @@ import Discussion from 'flarum/models/Discussion';
* notifications, grouped by discussion.
*/
export default class NotificationList extends Component {
constructor(...args) {
super(...args);
init() {
/**
* Whether or not the notifications are loading.
*
@@ -59,12 +57,12 @@ export default class NotificationList extends Component {
{Button.component({
className: 'Button Button--icon Button--link',
icon: 'check',
title: app.trans('core.mark_all_as_read'),
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.trans('core.notifications')}</h4>
<h4 className="App-titleControl App-titleControl--text">{app.translator.trans('core.forum.notifications.title')}</h4>
</div>
<div className="NotificationList-content">
@@ -98,7 +96,7 @@ export default class NotificationList extends Component {
);
})
: !this.loading
? <div className="NotificationList-empty">{app.trans('core.no_notifications')}</div>
? <div className="NotificationList-empty">{app.translator.trans('core.forum.notifications.empty_text')}</div>
: LoadingIndicator.component({className: 'LoadingIndicator--block'})}
</div>
</div>
@@ -110,20 +108,23 @@ export default class NotificationList extends Component {
* been loaded.
*/
load() {
if (app.cache.notifications && !app.session.user.unreadNotificationsCount()) {
if (app.cache.notifications && !app.session.user.newNotificationsCount()) {
return;
}
this.loading = true;
m.redraw();
app.store.find('notifications').then(notifications => {
app.session.user.pushAttributes({unreadNotificationsCount: 0});
app.cache.notifications = notifications.sort((a, b) => b.time() - a.time());
this.loading = false;
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();
});
}
/**
@@ -132,10 +133,13 @@ export default class NotificationList extends Component {
markAllAsRead() {
if (!app.cache.notifications) return;
app.cache.notifications.forEach(notification => {
if (!notification.isRead()) {
notification.save({isRead: true});
}
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'
});
}
}

View File

@@ -1,50 +1,74 @@
import Component from 'flarum/Component';
import Dropdown from 'flarum/components/Dropdown';
import icon from 'flarum/helpers/icon';
import NotificationList from 'flarum/components/NotificationList';
export default class NotificationsDropdown extends Component {
constructor(...args) {
super(...args);
export default class NotificationsDropdown extends Dropdown {
static initProps(props) {
props.className = props.className || 'NotificationsDropdown';
props.buttonClassName = props.buttonClassName || 'Button Button--flat';
props.menuClassName = props.menuClassName || 'Dropdown-menu--right';
props.label = props.label || app.translator.trans('core.forum.notifications.tooltip');
props.icon = props.icon || 'bell';
/**
* Whether or not the notifications dropdown is visible.
*
* @type {Boolean}
*/
this.showing = false;
super.initProps(props);
}
init() {
super.init();
this.list = new NotificationList();
}
view() {
const user = app.session.user;
const unread = user.unreadNotificationsCount();
getButton() {
const newNotifications = this.getNewCount();
const vdom = super.getButton();
vdom.attrs.title = this.props.label;
vdom.attrs.className += (newNotifications ? ' new' : '');
vdom.attrs.onclick = this.onclick.bind(this);
return vdom;
}
getButtonContent() {
const unread = this.getUnreadCount();
return [
icon(this.props.icon, {className: 'Button-icon'}),
unread ? <span className="NotificationsDropdown-unread">{unread}</span> : '',
<span className="Button-label">{this.props.label}</span>
];
}
getMenu() {
return (
<div className="Dropdown NotificationsDropdown">
<a href="javascript:;"
className={'Dropdown-toggle Button Button--flat NotificationsDropdown-button' + (unread ? ' unread' : '')}
data-toggle="dropdown"
onclick={this.onclick.bind(this)}>
<span className="Button-icon">{unread || icon('bell')}</span>
<span className="Button-label">{app.trans('core.notifications')}</span>
</a>
<div className="Dropdown-menu Dropdown-menu--right" onclick={this.menuClick.bind(this)}>
{this.showing ? this.list.render() : ''}
</div>
<div className={'Dropdown-menu ' + this.props.menuClassName} onclick={this.menuClick.bind(this)}>
{this.showing ? this.list.render() : ''}
</div>
);
}
onclick() {
if (app.drawer.isOpen()) {
m.route(app.route('notifications'));
this.goToRoute();
} else {
this.showing = true;
this.list.load();
}
}
goToRoute() {
m.route(app.route('notifications'));
}
getUnreadCount() {
return app.session.user.unreadNotificationsCount();
}
getNewCount() {
return app.session.user.newNotificationsCount();
}
menuClick(e) {
// Don't close the notifications dropdown if the user is opening a link in a
// new tab or window.

View File

@@ -1,21 +1,20 @@
import Component from 'flarum/Component';
import Page from 'flarum/components/Page';
import NotificationList from 'flarum/components/NotificationList';
/**
* The `NotificationsPage` component shows the notifications list. It is only
* used on mobile devices where the notifications dropdown is within the drawer.
*/
export default class NotificationsPage extends Component {
constructor(...args) {
super(...args);
export default class NotificationsPage extends Page {
init() {
super.init();
app.current = this;
app.history.push('notifications');
app.drawer.hide();
app.modal.close();
this.list = new NotificationList();
this.list.load();
this.bodyClass = 'App--notifications';
}
view() {

View File

@@ -0,0 +1,33 @@
import Component from 'flarum/Component';
/**
* The `Page` component
*
* @abstract
*/
export default class Page extends Component {
init() {
app.previous = app.current;
app.current = this;
app.drawer.hide();
app.modal.close();
/**
* A class name to apply to the body while the route is active.
*
* @type {String}
*/
this.bodyClass = '';
}
config(isInitialized, context) {
if (isInitialized) return;
if (this.bodyClass) {
$('#app').addClass(this.bodyClass);
context.onunload = () => $('#app').removeClass(this.bodyClass);
}
}
}

View File

@@ -2,6 +2,8 @@ import Component from 'flarum/Component';
import SubtreeRetainer from 'flarum/utils/SubtreeRetainer';
import Dropdown from 'flarum/components/Dropdown';
import PostControls from 'flarum/utils/PostControls';
import listItems from 'flarum/helpers/listItems';
import ItemList from 'flarum/utils/ItemList';
/**
* The `Post` component displays a single post. The basic post template just
@@ -15,8 +17,8 @@ import PostControls from 'flarum/utils/PostControls';
* @abstract
*/
export default class Post extends Component {
constructor(...args) {
super(...args);
init() {
this.loading = false;
/**
* Set up a subtree retainer so that the post will not be redrawn
@@ -29,14 +31,15 @@ export default class Post extends Component {
() => {
const user = this.props.post.user();
return user && user.freshness;
}
},
() => this.controlsOpen
);
}
view() {
const attrs = this.attrs();
attrs.className = 'Post ' + (attrs.className || '');
attrs.className = 'Post ' + (this.loading ? 'Post--loading ' : '') + (attrs.className || '');
return (
<article {...attrs}>
@@ -45,15 +48,24 @@ export default class Post extends Component {
return (
<div>
{controls.length ? Dropdown.component({
children: controls,
className: 'Post-controls',
buttonClassName: 'Button Button--icon Button--flat',
menuClassName: 'Dropdown-menu--right',
icon: 'ellipsis-v'
}) : ''}
{this.content()}
<aside className="Post-actions">
<ul>
{listItems(this.actionItems().toArray())}
{controls.length ? <li>
<Dropdown
className="Post-controls"
buttonClassName="Button Button--icon Button--flat"
menuClassName="Dropdown-menu--right"
icon="ellipsis-h"
onshow={() => this.$('.Post-actions').addClass('open')}
onhide={() => this.$('.Post-actions').removeClass('open')}>
{controls}
</Dropdown>
</li> : ''}
</ul>
</aside>
<footer className="Post-footer"><ul>{listItems(this.footerItems().toArray())}</ul></footer>
</div>
);
})()}
@@ -61,6 +73,13 @@ export default class Post extends Component {
);
}
config(isInitialized) {
const $actions = this.$('.Post-actions');
const $controls = this.$('.Post-controls');
$actions.toggleClass('open', $controls.hasClass('open'));
}
/**
* Get attributes for the post element.
*
@@ -73,9 +92,27 @@ export default class Post extends Component {
/**
* Get the post's content.
*
* @return {Object}
* @return {Array}
*/
content() {
return '';
return [];
}
/**
* Build an item list for the post's actions.
*
* @return {ItemList}
*/
actionItems() {
return new ItemList();
}
/**
* Build an item list for the post's footer.
*
* @return {ItemList}
*/
footerItems() {
return new ItemList();
}
}

View File

@@ -1,5 +1,4 @@
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
import humanTime from 'flarum/utils/humanTime';
import extractText from 'flarum/utils/extractText';
@@ -12,19 +11,34 @@ import extractText from 'flarum/utils/extractText';
* - `post`
*/
export default class PostEdited extends Component {
init() {
this.shouldUpdateTooltip = false;
this.oldEditedInfo = null;
}
view() {
const post = this.props.post;
const editUser = post.editUser();
const title = extractText(app.trans('core.post_edited', {user: editUser, ago: humanTime(post.editTime())}));
const editedInfo = extractText(app.translator.trans(
'core.forum.post.edited_tooltip',
{user: editUser, ago: humanTime(post.editTime())}
));
if (editedInfo !== this.oldEditedInfo) {
this.shouldUpdateTooltip = true;
this.oldEditedInfo = editedInfo;
}
return (
<span className="PostEdited" title={title}>{icon('pencil')}</span>
<span className="PostEdited" title={editedInfo}>
{app.translator.trans('core.forum.post.edited_text')}
</span>
);
}
config(isInitialized) {
if (isInitialized) return;
this.$().tooltip();
if (this.shouldUpdateTooltip) {
this.$().tooltip('destroy').tooltip();
this.shouldUpdateTooltip = false;
}
}
}

View File

@@ -15,7 +15,7 @@ export default class PostMeta extends Component {
view() {
const post = this.props.post;
const time = post.time();
const permalink = window.location.origin + app.route.post(post);
const permalink = this.getPermalink(post);
const touch = 'ontouchstart' in document.documentElement;
// When the dropdown menu is shown, select the contents of the permalink
@@ -33,8 +33,9 @@ export default class PostMeta extends Component {
</a>
<div className="Dropdown-menu dropdown-menu">
<span className="PostMeta-number">{app.trans('core.post_number', {number: post.number()})}</span>{' '}
{fullTime(time)}
<span className="PostMeta-number">{app.translator.trans('core.forum.post.number_tooltip', {number: post.number()})}</span>{' '}
<span className="PostMeta-time">{fullTime(time)}</span>{' '}
<span className="PostMeta-ip">{post.data.attributes.ipAddress}</span>
{touch
? <a className="Button PostMeta-permalink" href={permalink}>{permalink}</a>
: <input className="FormControl PostMeta-permalink" value={permalink} onclick={e => e.stopPropagation()} />}
@@ -42,4 +43,14 @@ export default class PostMeta extends Component {
</div>
);
}
/**
* Get the permalink for the given post.
*
* @param {Post} post
* @returns {String}
*/
getPermalink(post) {
return window.location.origin + app.route.post(post);
}
}

View File

@@ -5,6 +5,7 @@ import anchorScroll from 'flarum/utils/anchorScroll';
import mixin from 'flarum/utils/mixin';
import evented from 'flarum/utils/evented';
import ReplyPlaceholder from 'flarum/components/ReplyPlaceholder';
import Button from 'flarum/components/Button';
/**
* The `PostStream` component displays an infinitely-scrollable wall of posts in
@@ -15,10 +16,8 @@ import ReplyPlaceholder from 'flarum/components/ReplyPlaceholder';
* - `discussion`
* - `includedPosts`
*/
class PostStream extends mixin(Component, evented) {
constructor(...args) {
super(...args);
class PostStream extends Component {
init() {
/**
* The discussion to display the post stream for.
*
@@ -38,7 +37,7 @@ class PostStream extends mixin(Component, evented) {
this.loadPageTimeouts = {};
this.pagesLoading = 0;
this.init(this.props.includedPosts);
this.show(this.props.includedPosts);
}
/**
@@ -56,7 +55,9 @@ class PostStream extends mixin(Component, evented) {
return this.goToLast().then(() => {
$('html,body').stop(true).animate({
scrollTop: $(document).height() - $(window).height()
}, 'fast');
}, 'fast', () => {
this.flashItem(this.$('.PostStream-item:last-child'));
});
});
}
@@ -153,7 +154,7 @@ class PostStream extends mixin(Component, evented) {
*
* @param {Post[]} posts
*/
init(posts) {
show(posts) {
this.visibleStart = posts.length ? this.discussion.postIds().indexOf(posts[0].id()) : 0;
this.visibleEnd = this.visibleStart + posts.length;
}
@@ -181,7 +182,7 @@ class PostStream extends mixin(Component, evented) {
.map(id => {
const post = app.store.getById('posts', id);
return post && post.discussion() ? post : null;
return post && post.discussion() && typeof post.canEdit() !== 'undefined' ? post : null;
});
}
@@ -196,58 +197,72 @@ class PostStream extends mixin(Component, evented) {
this.visibleEnd = this.sanitizeIndex(this.visibleEnd);
this.viewingEnd = this.visibleEnd === this.count();
const posts = this.posts();
const postIds = this.discussion.postIds();
const items = posts.map((post, i) => {
let content;
const attrs = {'data-index': this.visibleStart + i};
if (post) {
const time = post.time();
const PostComponent = app.postComponents[post.contentType()];
content = PostComponent ? PostComponent.component({post}) : '';
attrs.key = 'post' + post.id();
attrs.config = fadeIn;
attrs['data-time'] = time.toISOString();
attrs['data-number'] = post.number();
attrs['data-id'] = post.id();
attrs['data-type'] = post.contentType();
// If the post before this one was more than 4 hours ago, we will
// display a 'time gap' indicating how long it has been in between
// the posts.
const dt = time - lastTime;
if (dt > 1000 * 60 * 60 * 24 * 4) {
content = [
<div className="PostStream-timeGap">
<span>{app.translator.trans('core.forum.post_stream.time_lapsed_text', {period: moment.duration(dt).humanize()})}</span>
</div>,
content
];
}
lastTime = time;
} else {
attrs.key = 'post' + postIds[this.visibleStart + i];
content = PostLoading.component();
}
return <div className="PostStream-item" {...attrs}>{content}</div>;
});
if (!this.viewingEnd && posts[this.visibleEnd - this.visibleStart - 1]) {
items.push(
<div className="PostStream-loadMore" key="loadMore">
<Button className="Button" onclick={this.loadNext.bind(this)}>
{app.translator.trans('core.forum.post_stream.load_more_button')}
</Button>
</div>
);
}
// If we're viewing the end of the discussion, the user can reply, and
// is not already doing so, then show a 'write a reply' placeholder.
if (this.viewingEnd && (!app.session.user || this.discussion.canReply())) {
items.push(
<div className="PostStream-item" key="reply">
{ReplyPlaceholder.component({discussion: this.discussion})}
</div>
);
}
return (
<div className="PostStream">
{this.posts().map((post, i) => {
let content;
const attrs = {'data-index': this.visibleStart + i};
if (post) {
const time = post.time();
const PostComponent = app.postComponents[post.contentType()];
content = PostComponent ? PostComponent.component({post}) : '';
attrs.key = 'post' + post.id();
attrs.config = fadeIn;
attrs['data-time'] = time.toISOString();
attrs['data-number'] = post.number();
attrs['data-id'] = post.id();
// If the post before this one was more than 4 hours ago, we will
// display a 'time gap' indicating how long it has been in between
// the posts.
const dt = time - lastTime;
if (dt > 1000 * 60 * 60 * 24 * 4) {
content = [
<div className="PostStream-timeGap">
<span>{app.trans('core.period_later', {period: moment.duration(dt).humanize()})}</span>
</div>,
content
];
}
lastTime = time;
} else {
attrs.key = this.visibleStart + i;
content = PostLoading.component();
}
return <div className="PostStream-item" {...attrs}>{content}</div>;
})}
{
// If we're viewing the end of the discussion, the user can reply, and
// is not already doing so, then show a 'write a reply' placeholder.
this.viewingEnd &&
(!app.session.user || this.discussion.canReply())
? (
<div className="PostStream-item" key="reply">
{ReplyPlaceholder.component({discussion: this.discussion})}
</div>
) : ''
}
{items}
</div>
);
}
@@ -277,7 +292,7 @@ class PostStream extends mixin(Component, evented) {
const marginTop = this.getMarginTop();
const viewportHeight = $(window).height() - marginTop;
const viewportTop = top + marginTop;
const loadAheadDistance = 500;
const loadAheadDistance = 300;
if (this.visibleStart > 0) {
const $item = this.$('.PostStream-item[data-index=' + this.visibleStart + ']');
@@ -391,7 +406,7 @@ class PostStream extends mixin(Component, evented) {
this.discussion.postIds().slice(start, end).forEach(id => {
const post = app.store.getById('posts', id);
if (post && post.discussion()) {
if (post && post.discussion() && typeof post.canEdit() !== 'undefined') {
loaded.push(post);
} else {
loadIds.push(id);
@@ -421,7 +436,7 @@ class PostStream extends mixin(Component, evented) {
return app.store.find('posts', {
filter: {discussion: this.discussion.id()},
page: {near: number}
}).then(this.init.bind(this));
}).then(this.show.bind(this));
}
/**
@@ -442,7 +457,7 @@ class PostStream extends mixin(Component, evented) {
this.reset(start, end);
return this.loadRange(start, end).then(this.init.bind(this));
return this.loadRange(start, end).then(this.show.bind(this));
}
/**
@@ -464,7 +479,7 @@ class PostStream extends mixin(Component, evented) {
if (top + height > scrollTop) {
if (!startNumber) {
startNumber = $item.data('number');
startNumber = endNumber = $item.data('number');
}
if (top + height < scrollTop + viewportHeight) {
@@ -583,4 +598,6 @@ class PostStream extends mixin(Component, evented) {
*/
PostStream.loadCount = 20;
Object.assign(PostStream.prototype, evented);
export default PostStream;

View File

@@ -15,9 +15,7 @@ import formatNumber from 'flarum/utils/formatNumber';
* - `className`
*/
export default class PostStreamScrubber extends Component {
constructor(...args) {
super(...args);
init() {
this.handlers = {};
/**
@@ -41,16 +39,6 @@ export default class PostStreamScrubber extends Component {
*/
this.description = '';
/**
* The integer index of the last item that is visible in the viewport. This
* is displayed on the scrubber (i.e. X of 100 posts).
*
* @return {Integer}
*/
this.visibleIndex = computed('index', 'visible', 'count', function(index, visible, count) {
return Math.min(count, Math.ceil(Math.max(0, index) + visible));
});
// When the post stream begins loading posts at a certain index, we want our
// scrubber scrollbar to jump to that position.
this.props.stream.on('unpaused', this.handlers.streamWasUnpaused = this.streamWasUnpaused.bind(this));
@@ -68,12 +56,13 @@ export default class PostStreamScrubber extends Component {
view() {
const retain = this.subtree.retain();
const count = this.count();
const unreadCount = this.props.stream.discussion.unreadCount();
const unreadPercent = Math.min(this.count() - this.index, unreadCount) / this.count();
const unreadPercent = count ? Math.min(count - this.index, unreadCount) / count : 0;
const viewing = app.trans('core.viewing_posts', {
index: <span className="Scrubber-index">{retain || formatNumber(this.visibleIndex())}</span>,
count: <span className="Scrubber-count">{formatNumber(this.count())}</span>
const viewing = app.translator.transChoice('core.forum.post_scrubber.viewing_text', count, {
index: <span className="Scrubber-index">{retain || formatNumber(Math.min(Math.ceil(this.index + this.visible), count))}</span>,
count: <span className="Scrubber-count">{formatNumber(count)}</span>
});
function styleUnread(element, isInitialized, context) {
@@ -101,7 +90,7 @@ export default class PostStreamScrubber extends Component {
<div className="Dropdown-menu dropdown-menu">
<div className="Scrubber">
<a className="Scrubber-first" onclick={this.goToFirst.bind(this)}>
{icon('angle-double-up')} {app.trans('core.original_post')}
{icon('angle-double-up')} {app.translator.trans('core.forum.post_scrubber.original_post_link')}
</a>
<div className="Scrubber-scrollbar">
@@ -116,12 +105,12 @@ export default class PostStreamScrubber extends Component {
<div className="Scrubber-after"/>
<div className="Scrubber-unread" config={styleUnread}>
{app.trans('core.unread_posts', {count: unreadCount})}
{app.translator.trans('core.forum.post_scrubber.unread_text', {count: unreadCount})}
</div>
</div>
<a className="Scrubber-last" onclick={this.goToLast.bind(this)}>
{icon('angle-double-down')} {app.trans('core.now')}
{icon('angle-double-down')} {app.translator.trans('core.forum.post_scrubber.now_link')}
</a>
</div>
</div>
@@ -201,6 +190,7 @@ export default class PostStreamScrubber extends Component {
const marginTop = stream.getMarginTop();
const viewportTop = scrollTop + marginTop;
const viewportHeight = $(window).height() - marginTop;
const viewportBottom = viewportTop + viewportHeight;
// Before looping through all of the posts, we reset the scrollbar
// properties to a 'default' state. These values reflect what would be
@@ -220,32 +210,28 @@ export default class PostStreamScrubber extends Component {
const height = $this.outerHeight(true);
// If this item is above the top of the viewport, skip to the next
// post. If it's below the bottom of the viewport, break out of the
// one. If it's below the bottom of the viewport, break out of the
// loop.
if (top + height < viewportTop) {
visible = (top + height - viewportTop) / height;
index = parseFloat($this.data('index')) + 1 - visible;
return true;
}
if (top > viewportTop + viewportHeight) {
return false;
}
// If the bottom half of this item is visible at the top of the
// viewport, then set the start of the visible proportion as our index.
if (top <= viewportTop && top + height > viewportTop) {
visible = (top + height - viewportTop) / height;
index = parseFloat($this.data('index')) + 1 - visible;
//
// If the top half of this item is visible at the bottom of the
// viewport, then add the visible proportion to the visible
// counter.
} else if (top + height >= viewportTop + viewportHeight) {
visible += (viewportTop + viewportHeight - top) / height;
//
// If the whole item is visible in the viewport, then increment the
// visible counter.
} else visible++;
// Work out how many pixels of this item are visible inside the viewport.
// Then add the proportion of this item's total height to the index.
const visibleTop = Math.max(0, viewportTop - top);
const visibleBottom = Math.min(height, viewportTop + viewportHeight - top);
const visiblePost = visibleBottom - visibleTop;
if (top <= viewportTop) {
index = parseFloat($this.data('index')) + visibleTop / height;
}
if (visiblePost > 0) {
visible += visiblePost / height;
}
// If this item has a time associated with it, then set the
// scrollbar's current period to a formatted version of this time.
@@ -329,7 +315,7 @@ export default class PostStreamScrubber extends Component {
const visible = this.visible || 1;
const $scrubber = this.$();
$scrubber.find('.Scrubber-index').text(formatNumber(this.visibleIndex()));
$scrubber.find('.Scrubber-index').text(formatNumber(Math.ceil(index + visible)));
$scrubber.find('.Scrubber-description').text(this.description);
$scrubber.toggleClass('disabled', this.disabled());
@@ -410,7 +396,7 @@ export default class PostStreamScrubber extends Component {
// the index at which the drag was started, and then scroll there.
const deltaPixels = (e.clientY || e.originalEvent.touches[0].clientY) - this.mouseStart;
const deltaPercent = deltaPixels / this.$('.Scrubber-scrollbar').outerHeight() * 100;
const deltaIndex = deltaPercent / this.percentPerPost().index;
const deltaIndex = (deltaPercent / this.percentPerPost().index) || 0;
const newIndex = Math.min(this.indexStart + deltaIndex, this.count() - 1);
this.index = Math.max(0, newIndex);

View File

@@ -2,6 +2,7 @@ import Component from 'flarum/Component';
import UserCard from 'flarum/components/UserCard';
import avatar from 'flarum/helpers/avatar';
import username from 'flarum/helpers/username';
import userOnline from 'flarum/helpers/userOnline';
import listItems from 'flarum/helpers/listItems';
/**
@@ -12,9 +13,7 @@ import listItems from 'flarum/helpers/listItems';
* - `post`
*/
export default class PostUser extends Component {
constructor(...args) {
super(...args);
init() {
/**
* Whether or not the user hover card is visible.
*
@@ -47,6 +46,7 @@ export default class PostUser extends Component {
return (
<div className="PostUser">
{userOnline(user)}
<h3>
<a href={app.route.user(user)} config={m.route}>
{avatar(user, {className: 'PostUser-avatar'})}{' '}{username(user)}

View File

@@ -8,8 +8,8 @@ import CommentPost from 'flarum/components/CommentPost';
* profile.
*/
export default class PostsUserPage extends UserPage {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* Whether or not the activity feed is currently loading.
@@ -51,7 +51,7 @@ export default class PostsUserPage extends UserPage {
footer = (
<div className="PostsUserPage-loadMore">
{Button.component({
children: app.trans('core.load_more'),
children: app.translator.trans('core.forum.user.posts_load_more_button'),
className: 'Button',
onclick: this.loadMore.bind(this)
})}
@@ -65,9 +65,9 @@ export default class PostsUserPage extends UserPage {
{this.posts.map(post => (
<li>
<div className="PostsUserPage-discussion">
In <a href={app.route.post(post)} config={m.route}>{post.discussion().title()}</a>
{app.translator.trans('core.forum.user.in_discussion_text', {discussion: <a href={app.route.post(post)} config={m.route}>{post.discussion().title()}</a>})}
</div>
{CommentPost.component({post, showDiscussionTitle: true})}
{CommentPost.component({post})}
</li>
))}
</ul>
@@ -80,8 +80,8 @@ export default class PostsUserPage extends UserPage {
* Initialize the component with a user, and trigger the loading of their
* activity feed.
*/
init(user) {
super.init(user);
show(user) {
super.show(user);
this.refresh();
}
@@ -95,9 +95,7 @@ export default class PostsUserPage extends UserPage {
this.loading = true;
this.posts = [];
// Redraw, but only if we're not in the middle of a route change.
m.startComputation();
m.endComputation();
m.lazyRedraw();
this.loadResults().then(this.parseResults.bind(this));
}

View File

@@ -0,0 +1,70 @@
import Modal from 'flarum/components/Modal';
import Button from 'flarum/components/Button';
/**
* The 'RenameDiscussionModal' displays a modal dialog with an input to rename a discussion
*/
export default class RenameDiscussionModal extends Modal {
init() {
super.init();
this.discussion = this.props.discussion;
this.currentTitle = this.props.currentTitle;
this.newTitle = m.prop(this.currentTitle);
}
className() {
return 'RenameDiscussionModal Modal--small';
}
title() {
return app.translator.trans('core.forum.rename_discussion.title');
}
content() {
return (
<div className="Modal-body">
<div className="Form Form--centered">
<div className="Form-group">
<input className="FormControl" bidi={this.newTitle} type="text" />
</div>
<div className="Form-group">
{Button.component({
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.translator.trans('core.forum.rename_discussion.submit_button')
})}
</div>
</div>
</div>
)
}
onsubmit(e) {
e.preventDefault();
this.loading = true;
const title = this.newTitle;
const currentTitle = this.currentTitle;
// If the title is different to what it was before, then save it. After the
// save has completed, update the post stream as there will be a new post
// indicating that the discussion was renamed.
if (title && title !== currentTitle) {
return this.discussion.save({title}).then(() => {
if (app.viewingDiscussion(this.discussion)) {
app.current.stream.update();
}
m.redraw();
this.hide();
}).catch(() => {
this.loading = false;
m.redraw();
});
} else {
this.hide();
}
}
}

View File

@@ -2,6 +2,14 @@ import ComposerBody from 'flarum/components/ComposerBody';
import Alert from 'flarum/components/Alert';
import Button from 'flarum/components/Button';
import icon from 'flarum/helpers/icon';
import extractText from 'flarum/utils/extractText';
function minimizeComposerIfFullScreen(e) {
if (app.composer.isFullScreen()) {
app.composer.minimize();
e.stopPropagation();
}
}
/**
* The `ReplyComposer` component displays the composer content for replying to a
@@ -13,10 +21,12 @@ import icon from 'flarum/helpers/icon';
* - `discussion`
*/
export default class ReplyComposer extends ComposerBody {
constructor(...args) {
super(...args);
init() {
super.init();
this.editor.props.preview = e => {
minimizeComposerIfFullScreen(e);
this.editor.props.preview = () => {
m.route(app.route.discussion(this.props.discussion, 'reply'));
};
}
@@ -24,18 +34,25 @@ export default class ReplyComposer extends ComposerBody {
static initProps(props) {
super.initProps(props);
props.placeholder = props.placeholder || app.trans('core.write_a_reply');
props.submitLabel = props.submitLabel || app.trans('core.post_reply');
props.confirmExit = props.confirmExit || app.trans('core.confirm_discard_reply');
props.placeholder = props.placeholder || extractText(app.translator.trans('core.forum.composer_reply.body_placeholder'));
props.submitLabel = props.submitLabel || app.translator.trans('core.forum.composer_reply.submit_button');
props.confirmExit = props.confirmExit || extractText(app.translator.trans('core.forum.composer_reply.discard_confirmation'));
}
headerItems() {
const items = super.headerItems();
const discussion = this.props.discussion;
const routeAndMinimize = function(element, isInitialized) {
if (isInitialized) return;
$(element).on('click', minimizeComposerIfFullScreen);
m.route.apply(this, arguments);
};
items.add('title', (
<h3>
{icon('reply')}{' '}<a href={app.route.discussion(discussion)} config={m.route}>{discussion.title()}</a>
{icon('reply')} {' '}
<a href={app.route.discussion(discussion)} config={routeAndMinimize}>{discussion.title()}</a>
</h3>
));
@@ -75,7 +92,7 @@ export default class ReplyComposer extends ComposerBody {
let alert;
const viewButton = Button.component({
className: 'Button Button--link',
children: app.trans('core.view'),
children: app.translator.trans('core.forum.composer_reply.view_button'),
onclick: () => {
m.route(app.route.post(post));
app.alerts.dismiss(alert);
@@ -84,7 +101,7 @@ export default class ReplyComposer extends ComposerBody {
app.alerts.show(
alert = new Alert({
type: 'success',
message: app.trans('core.reply_posted'),
message: app.translator.trans('core.forum.composer_reply.posted_message'),
controls: [viewButton]
})
);
@@ -92,11 +109,7 @@ export default class ReplyComposer extends ComposerBody {
app.composer.hide();
},
response => {
this.loading = false;
m.redraw();
app.alertErrors(response.errors);
}
this.loaded.bind(this)
);
}
}

View File

@@ -31,20 +31,15 @@ export default class ReplyPlaceholder extends Component {
);
}
function triggerClick(e) {
$(this).trigger('click');
e.preventDefault();
}
const reply = () => {
DiscussionControls.replyAction.call(this.props.discussion, true);
};
return (
<article className="Post ReplyPlaceholder" onclick={reply} onmousedown={triggerClick}>
<article className="Post ReplyPlaceholder" onclick={reply}>
<header className="Post-header">
{avatar(app.session.user, {className: 'PostUser-avatar'})}{' '}
{app.trans('core.write_a_reply')}
{app.translator.trans('core.forum.post_stream.reply_placeholder')}
</header>
</article>
);

View File

@@ -2,6 +2,8 @@ import Component from 'flarum/Component';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
import ItemList from 'flarum/utils/ItemList';
import classList from 'flarum/utils/classList';
import extractText from 'flarum/utils/extractText';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import icon from 'flarum/helpers/icon';
import DiscussionsSearchSource from 'flarum/components/DiscussionsSearchSource';
import UsersSearchSource from 'flarum/components/UsersSearchSource';
@@ -16,15 +18,13 @@ import UsersSearchSource from 'flarum/components/UsersSearchSource';
* `clearSearch` method on the controller.
*/
export default class Search extends Component {
constructor(...args) {
super(...args);
init() {
/**
* The value of the search input.
*
* @type {Function}
*/
this.value = m.prop();
this.value = m.prop('');
/**
* Whether or not the search input has focus.
@@ -83,7 +83,8 @@ export default class Search extends Component {
})}>
<div className="Search-input">
<input className="FormControl"
placeholder={app.trans('core.search_forum')}
type="search"
placeholder={extractText(app.translator.trans('core.forum.header.search_placeholder'))}
value={this.value()}
oninput={m.withAttr('value', this.value)}
onfocus={() => this.hasFocus = true}
@@ -122,31 +123,18 @@ export default class Search extends Component {
);
});
// Handle navigation key events on the search input.
this.$('input')
.on('keydown', e => {
switch (e.which) {
case 40: case 38: // Down/Up
this.setIndex(this.getCurrentNumericIndex() + (e.which === 40 ? 1 : -1), true);
e.preventDefault();
break;
const $input = this.$('input');
case 13: // Return
m.route(this.getItem(this.index).find('a').attr('href'));
this.$('input').blur();
break;
this.navigator = new KeyboardNavigatable();
this.navigator
.onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
.onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
.onSelect(this.selectResult.bind(this))
.onCancel(this.clear.bind(this))
.bindTo($input);
case 27: // Escape
this.clear();
break;
default:
// no default
}
})
// Handle input key events on the search input, triggering results to
// load.
// Handle input key events on the search input, triggering results to load.
$input
.on('input focus', function() {
const query = this.value.toLowerCase();
@@ -172,6 +160,10 @@ export default class Search extends Component {
search.searched.push(query);
m.redraw();
}, 250);
})
.on('focus', function() {
$(this).one('mouseup', e => e.preventDefault()).select();
});
}
@@ -184,6 +176,19 @@ export default class Search extends Component {
return app.current && typeof app.current.searching === 'function' && app.current.searching();
}
/**
* Navigate to the currently selected search result and close the list.
*/
selectResult() {
if (this.value()) {
m.route(this.getItem(this.index).find('a').attr('href'));
} else {
this.clear();
}
this.$('input').blur();
}
/**
* Clear the search input and the current controller's active search.
*/

View File

@@ -47,7 +47,7 @@ export default class SessionDropdown extends Dropdown {
items.add('profile',
LinkButton.component({
icon: 'user',
children: app.trans('core.profile'),
children: app.translator.trans('core.forum.header.profile_button'),
href: app.route.user(user)
}),
100
@@ -56,7 +56,7 @@ export default class SessionDropdown extends Dropdown {
items.add('settings',
LinkButton.component({
icon: 'cog',
children: app.trans('core.settings'),
children: app.translator.trans('core.forum.header.settings_button'),
href: app.route('settings')
}),
50
@@ -66,7 +66,7 @@ export default class SessionDropdown extends Dropdown {
items.add('administration',
LinkButton.component({
icon: 'wrench',
children: app.trans('core.administration'),
children: app.translator.trans('core.forum.header.admin_button'),
href: app.forum.attribute('baseUrl') + '/admin',
target: '_blank',
config: () => {}
@@ -80,7 +80,7 @@ export default class SessionDropdown extends Dropdown {
items.add('logOut',
Button.component({
icon: 'sign-out',
children: app.trans('core.log_out'),
children: app.translator.trans('core.forum.header.log_out_button'),
onclick: app.session.logout.bind(app.session)
}),
-100

View File

@@ -13,11 +13,11 @@ import listItems from 'flarum/helpers/listItems';
* the context of their user profile.
*/
export default class SettingsPage extends UserPage {
constructor(...args) {
super(...args);
init() {
super.init();
this.init(app.session.user);
app.setTitle(app.trans('core.settings'));
this.show(app.session.user);
app.setTitle(app.translator.trans('core.forum.settings.title'));
}
content() {
@@ -38,7 +38,7 @@ export default class SettingsPage extends UserPage {
items.add('account',
FieldSet.component({
label: app.trans('core.account'),
label: app.translator.trans('core.forum.settings.account_heading'),
className: 'Settings-account',
children: this.accountItems().toArray()
})
@@ -46,15 +46,15 @@ export default class SettingsPage extends UserPage {
items.add('notifications',
FieldSet.component({
label: app.trans('core.notifications'),
label: app.translator.trans('core.forum.settings.notifications_heading'),
className: 'Settings-notifications',
children: [NotificationGrid.component({user: this.user})]
children: this.notificationsItems().toArray()
})
);
items.add('privacy',
FieldSet.component({
label: app.trans('core.privacy'),
label: app.translator.trans('core.forum.settings.privacy_heading'),
className: 'Settings-privacy',
children: this.privacyItems().toArray()
})
@@ -73,7 +73,7 @@ export default class SettingsPage extends UserPage {
items.add('changePassword',
Button.component({
children: app.trans('core.change_password'),
children: app.translator.trans('core.forum.settings.change_password_button'),
className: 'Button',
onclick: () => app.modal.show(new ChangePasswordModal())
})
@@ -81,7 +81,7 @@ export default class SettingsPage extends UserPage {
items.add('changeEmail',
Button.component({
children: app.trans('core.change_email'),
children: app.translator.trans('core.forum.settings.change_email_button'),
className: 'Button',
onclick: () => app.modal.show(new ChangeEmailModal())
})
@@ -90,6 +90,19 @@ export default class SettingsPage extends UserPage {
return items;
}
/**
* Build an item list for the user's notification settings.
*
* @return {ItemList}
*/
notificationsItems() {
const items = new ItemList();
items.add('notificationGrid', NotificationGrid.component({user: this.user}));
return items;
}
/**
* Generate a callback that will save a value to the given preference.
*
@@ -118,7 +131,7 @@ export default class SettingsPage extends UserPage {
items.add('discloseOnline',
Switch.component({
children: app.trans('core.disclose_online'),
children: app.translator.trans('core.forum.settings.privacy_disclose_online_label'),
state: this.user.preferences().discloseOnline,
onchange: (value, component) => {
this.user.pushAttributes({lastSeenTime: null});

View File

@@ -2,6 +2,8 @@ import Modal from 'flarum/components/Modal';
import LogInModal from 'flarum/components/LogInModal';
import avatar from 'flarum/helpers/avatar';
import Button from 'flarum/components/Button';
import LogInButtons from 'flarum/components/LogInButtons';
import extractText from 'flarum/utils/extractText';
/**
* The `SignUpModal` component displays a modal dialog with a singup form.
@@ -11,10 +13,11 @@ import Button from 'flarum/components/Button';
* - `username`
* - `email`
* - `password`
* - `token` An email token to sign up with.
*/
export default class SignUpModal extends Modal {
constructor(...args) {
super(...args);
init() {
super.init();
/**
* The value of the username input.
@@ -36,13 +39,6 @@ export default class SignUpModal extends Modal {
* @type {Function}
*/
this.password = m.prop(this.props.password || '');
/**
* The user that has been signed up and that should be welcomed.
*
* @type {null|User}
*/
this.welcomeUser = null;
}
className() {
@@ -50,7 +46,7 @@ export default class SignUpModal extends Modal {
}
title() {
return app.trans('core.sign_up');
return app.translator.trans('core.forum.sign_up.title');
}
content() {
@@ -65,84 +61,49 @@ export default class SignUpModal extends Modal {
}
body() {
const body = [(
return [
this.props.token ? '' : <LogInButtons/>,
<div className="Form Form--centered">
<div className="Form-group">
<input className="FormControl" name="username" placeholder={app.trans('core.username')}
<input className="FormControl" name="username" type="text" placeholder={extractText(app.translator.trans('core.forum.sign_up.username_placeholder'))}
value={this.username()}
onchange={m.withAttr('value', this.username)}
disabled={this.loading} />
</div>
<div className="Form-group">
<input className="FormControl" name="email" type="email" placeholder={app.trans('core.email')}
<input className="FormControl" name="email" type="email" placeholder={extractText(app.translator.trans('core.forum.sign_up.email_placeholder'))}
value={this.email()}
onchange={m.withAttr('value', this.email)}
disabled={this.loading} />
disabled={this.loading || (this.props.token && this.props.email)} />
</div>
<div className="Form-group">
<input className="FormControl" name="password" type="password" placeholder={app.trans('core.password')}
value={this.password()}
onchange={m.withAttr('value', this.password)}
disabled={this.loading} />
</div>
{this.props.token ? '' : (
<div className="Form-group">
<input className="FormControl" name="password" type="password" placeholder={extractText(app.translator.trans('core.forum.sign_up.password_placeholder'))}
value={this.password()}
onchange={m.withAttr('value', this.password)}
disabled={this.loading} />
</div>
)}
<div className="Form-group">
{Button.component({
className: 'Button Button--primary Button--block',
type: 'submit',
loading: this.loading,
children: app.trans('core.sign_up')
})}
<Button
className="Button Button--primary Button--block"
type="submit"
loading={this.loading}>
{app.translator.trans('core.forum.sign_up.submit_button')}
</Button>
</div>
</div>
)];
if (this.welcomeUser) {
const user = this.welcomeUser;
const emailProviderName = user.email().split('@')[1];
const fadeIn = (element, isInitialized) => {
if (isInitialized) return;
$(element).hide().fadeIn();
};
body.push(
<div className="SignUpModal-welcome" style={{background: user.color()}} config={fadeIn}>
<div className="darkenBackground">
<div className="container">
{avatar(user)}
<h3>{app.trans('core.welcome_user', {user})}</h3>
{!user.isActivated() ? [
<p>{app.trans('core.confirmation_email_sent', {email: <strong>{user.email()}</strong>})}</p>,
<p>
<a href={`http://${emailProviderName}`} className="Button Button--primary" target="_blank">
{app.trans('core.go_to', {location: emailProviderName})}
</a>
</p>
] : (
<p>
<button className="Button Button--primary" onclick={this.hide.bind(this)}>
{app.trans('core.dismiss')}
</button>
</p>
)}
</div>
</div>
</div>
);
}
return body;
];
}
footer() {
return [
<p className="SignUpModal-logIn">
{app.trans('core.before_log_in_link')}{' '}
<a onclick={this.logIn.bind(this)}>{app.trans('core.log_in')}</a>
{app.translator.trans('core.forum.sign_up.log_in_text', {a: <a onclick={this.logIn.bind(this)}/>})}
</p>
];
}
@@ -150,10 +111,12 @@ export default class SignUpModal extends Modal {
/**
* Open the log in modal, prefilling it with an email/username/password if
* the user has entered one.
*
* @public
*/
logIn() {
const props = {
email: this.email() || this.username(),
identification: this.email() || this.username(),
password: this.password()
};
@@ -161,10 +124,10 @@ export default class SignUpModal extends Modal {
}
onready() {
if (this.props.username) {
if (this.props.username && !this.props.email) {
this.$('[name=email]').select();
} else {
super.onready();
this.$('[name=username]').select();
}
}
@@ -173,22 +136,41 @@ export default class SignUpModal extends Modal {
this.loading = true;
const data = {
username: this.username(),
email: this.email(),
password: this.password()
};
const data = this.submitData();
app.store.createRecord('users').save(data).then(
user => {
this.welcomeUser = user;
this.loading = false;
m.redraw();
},
response => {
this.loading = false;
this.handleErrors(response.errors);
}
app.request({
url: app.forum.attribute('baseUrl') + '/register',
method: 'POST',
data,
errorHandler: this.onerror.bind(this)
}).then(
() => window.location.reload(),
this.loaded.bind(this)
);
}
/**
* Get the data that should be submitted in the sign-up request.
*
* @return {Object}
* @public
*/
submitData() {
const data = {
username: this.username(),
email: this.email()
};
if (this.props.token) {
data.token = this.props.token;
} else {
data.password = this.password();
}
if (this.props.avatarUrl) {
data.avatarUrl = this.props.avatarUrl;
}
return data;
}
}

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