From 82eae9625bd0b2efe1d7e54d46a7edb97ae4576c Mon Sep 17 00:00:00 2001 From: justjavac Date: Wed, 31 Dec 2014 11:49:05 +0800 Subject: [PATCH 01/27] =?UTF-8?q?=E6=B7=BB=E5=8A=A0QQ=E7=BE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 404 +++++++++++++++++++++++++++--------------------------- 1 file changed, 203 insertions(+), 201 deletions(-) diff --git a/README.md b/README.md index 13a2471..f8c9598 100644 --- a/README.md +++ b/README.md @@ -1,201 +1,203 @@ -源项目地址:https://github.com/flarum/core - -------------------- - -**来吧,让我们一起构建一个让小伙伴们都惊呆的 PHP 论坛。** - -我是 [Toby Zerner](http://tobyzerner.com), [esoTalk](http://esotalk.org) 的开发者。 -年前,我构建了一个新颖的,轻量级的 esoTalk 论坛的替代者。 -esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上](http://bbs.justjavac.com/6)(译注:esoTalk 的底层框架重新发明了轮子)。 - -**基于此,我们需要一个现代的,优雅的,简洁的,强大的论坛软件,并且易于使用和托管。** -这就是 Flarum。但我是一个全职学生,我没有足够的时间独立完成这个项目。 -于是我将采用开放协作的方式来开发 Flarum —— 让全世界的开发者一起开发 Flarum。 - -## 开发理念 - -I have a vision for Flarum — one that has grown over years of playing with forums, developing esoTalk, and learning from mistakes. It is captured by the following four points: - -- **Modern design.** Beautiful, clean, customizable. Forum design hasn’t evolved with the rest of the web; let's finally bring it up to speed. -- **Unopinionated feature-set.** Every community is different, and forum software should be able to adapt to — rather than define — how a community is run. As such, Flarum will have a lightweight core, and most features will be implemented as optional extensions. -- **High performance.** Flarum should perform well on scales big and small, and on a large range of devices. -- **Sustainable architecture.** Flarum should be built for the future, on a strong foundation which can evolve with the technologies that power it. - -Flarum is open-source software released under the [MIT license](https://github.com/flarum/core/blob/master/LICENSE.txt). - -## Technology - -I've carefully considered which frameworks to use to build Flarum. Here's the reasoning: - -### Laravel - -PHP remains the most user-friendly language for deploying web scripts, especially on shared hosting. The Laravel framework will allow for rapid development of Flarum’s API, and has a large community which will encourage collaboration and evolution. - -### Ember.js - -Ember.js is a mature JavaScript framework which will power Flarum’s front-end. Use of a JavaScript framework allows us to build a fast, dynamic interface which feels more like an app than a simple web page. - -> Don't like the fact that Flarum is an Ember.js app? Take a look at [FluxBB 2](https://github.com/fluxbb/fluxbb/tree/2.0), which is being developed in a more traditional manner with Laravel. - -## Current State - -I’ve been working on a prototype for some time in-between my studies. In addition to interface design, most of my time has been spent building out the architecture: making decisions about which frameworks to use, the most effective way to componentize everything, standardizing the API, etc. - -You might notice that a lot of the code is a bit of a mess right now. This is because I wanted to get things working on a superficial level in order to record material for the Kickstarter video, and since then I haven't yet had sufficient time to clean it up. Please be aware that the mess is not at all indicative of the standard that we're aiming for! - -### What’s Done - -- [x] The basic technology stack (Laravel and Ember – see above) -- [x] The [architectural foundation](https://github.com/flarum/core/wiki/Architecture) (core/API/web layers) -- [x] Some of the API (discussion and post read + write) -- [x] Discussion list view and basic search functionality -- [x] Discussion viewing and scrolling - -### What’s Next - -The priority at the moment is to build out a lightweight core, and only start building [Extensions](https://github.com/flarum/core/wiki/Extensions) when it is relatively stable. Below is a list of the things to work on immediately, with links to the relevant discussion. - -- [ ] Interface redesign ([#1](https://github.com/flarum/core/issues/1)) -- [ ] Upgrade to Laravel 5 ([#2](https://github.com/flarum/core/issues/2)) -- [ ] Set up testing frameworks in both Laravel ([#3](https://github.com/flarum/core/issues/3)) and Ember ([#4](https://github.com/flarum/core/issues/4)) -- [ ] Further consolidation of Extension interfaces (see [Extensions](https://github.com/flarum/core/wiki/Extensions)) -- [ ] Develop user authentication strategy ([#5](https://github.com/flarum/core/issues/5)) -- [ ] Implement replying, post editing, discussion creation ([#6](https://github.com/flarum/core/issues/6)) -- [ ] Implement discussion title editing ([#7](https://github.com/flarum/core/issues/7)) -- [ ] Implement post deletion ([#8](https://github.com/flarum/core/issues/8)) -- [ ] Implement discussion deletion ([#9](https://github.com/flarum/core/issues/9)) -- [ ] Design user profile interface ([#10](https://github.com/flarum/core/issues/10)) -- [ ] Design admin interfaces ([#11](https://github.com/flarum/core/issues/11)) -- [ ] Build Notifications system ([#12](https://github.com/flarum/core/issues/12)) - -For a full list of planned features, see [Features](https://github.com/flarum/core/wiki/Features). - -## Installation - -Currently Flarum is in its very early stages, and it isn’t pretty. **It is far from usable.** Set it up only if you know what you’re doing, and expect it to break a lot. - -1. Make sure you have [Composer](http://getcomposer.org), [ember-cli](http://www.ember-cli.com), and [Bower](http://bower.io) installed globally. -2. Create a new [Laravel 4](http://laravel.com/docs/4.2/quick) project. -3. Clone the Flarum repository into the workbench and install dependencies: - - ``` - git clone https://github.com/flarum/core.git workbench/flarum/core - cd workbench/flarum/core - composer install - cd ../../.. - php artisan dump-autoload - ``` - -5. Create a new MySQL database and configure your database details in `app/config/database.php`. -6. Run the Flarum migrations and database seeder to generate dummy data: - - ``` - php artisan migrate --bench="flarum/core" - php artisan db:seed --class="Flarum\Core\Support\Seeders\DatabaseSeeder" - ``` - -7. Add the Flarum service providers to the `providers` array in `app/config/app.php`: - - ``` - 'Flarum\Core\CoreServiceProvider', - 'Flarum\Api\ApiServiceProvider', - 'Flarum\Web\WebServiceProvider' - ``` - -8. Remove the default route in `app/routes.php`. -9. Run the following commands to compile the Ember app: - - ``` - cd workbench/flarum/core/ember - npm install - bower install - ember serve --output-path="../public" - ``` - -10. Visit your Laravel application in a browser. - - > Note: You must access the Laravel application so that it is at the top level (i.e. not under any sub-directories.) To do this, you can either set your web server's document root to the `public` folder of your application, or you can [configure a virtual host](http://davidwalsh.name/create-virtual-host) pointing to the `public` folder. - -If you’re having trouble, **do not** create a new issue — instead, get help on the [Flarum Development Forum](http://discuss.flarum.org) or hop on the IRC channel (#flarum on irc.freenode.net). - -## Contributing - -Building Flarum is going to be a team effort, and we'd love for you to help! All contributions are welcomed. - -### What Can I Do? - -- **Contribute code.** Start by becoming familiar with Flarum's source code and its [Architecture](https://github.com/flarum/core/wiki/Architecture). Then have a look at what needs to be done in the list above, and see if there's anything you can help out with. See below for instructions on submitting a Pull Request. - -- **Participate in discussion.** Review the [wiki](https://github.com/flarum/core/wiki) and [issues](https://github.com/flarum/core/issues) and contribute your constructive thoughts. We'd also love to hear general feedback on the [Flarum Development Forum](http://discuss.flarum.org), on IRC (#flarum on irc.freenode.net), and on [Twitter](http://twitter.com/flarum). - -- **Spread the word.** Know someone who could help out? Please share this project with them! - -> In this early stage of development, bug reports won't be particularly helpful, because things will be constantly changing and breaking. - -### Process - -1. Review the [Flarum Contributor License Agreement](#contributor-license-agreement). ([Why?](https://julien.ponge.org/blog/in-defense-of-contributor-license-agreements/)) - -2. Install Flarum as detailed in the instructions above. - -3. Create a new branch. - - ``` - git checkout -b new-flarum-branch - ``` - - > Please implement only one feature/bugfix per branch to keep pull requests clean and focused. - -4. Code. - - Follow the coding style: [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). - - Include tests and make sure they pass (subject to [#3](https://github.com/flarum/core/issues/3) and [#4](https://github.com/flarum/core/issues/4)). - -5. Commit. - - Commit messages are **required**. - - They should include a short description of the changes on the first line, then a blank line, then more details if necessary. - -6. Clean up. Squash together minor commits. - - ``` - git rebase -i - ``` - -7. Update your branch so that it is based on top of the latest code from the Flarum repository. - - ``` - git fetch origin - git rebase origin/master - ``` - -8. Fork your repository on GitHub and push to it. - - ``` - git remote add mine git@github.com:/flarum.git - git push mine new-flarum-branch - ``` - -9. Submit a pull request. - - Go to the Flarum repository you just pushed to (e.g. https://github.com/your-user-name/flarum). - - Click "Pull Request". - - Write your branch name in the branch field. - - Click "Update Commit Range". - - Ensure that the correct commits and files changes are included. - - Fill in a descriptive title and other details about your pull request. - - Click "Send pull request". - -10. Respond to feedback. - - We may suggest changes to your code. Maintaining a high standard of code quality is important for the longevity of this project — use it as an opportunity to improve your own skills and learn something new! - -### Core Team - -Currently the only person on the core development team is Toby Zerner ([@tobscure](http://twitter.com/tobscure)). Over time, judged by display of commitment to the project, and quantity/quality of contributions, I will be looking for more people to join the core development team. Please do not email me asking to be on the core team; rather, demonstrate initiative and commitment to the project, and I will more than likely notice! - -### Contributor License Agreement - -By contributing your code to Flarum you grant Toby Zerner a non-exclusive, irrevocable, worldwide, royalty-free, sublicenseable, transferable license under all of Your relevant intellectual property rights (including copyright, patent, and any other rights), to use, copy, prepare derivative works of, distribute and publicly perform and display the Contributions on any licensing terms, including without limitation: (a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the licenses granted herein, You reserve all right, title, and interest in and to the Contribution. - -You confirm that you are able to grant us these rights. You represent that You are legally entitled to grant the above license. If Your employer has rights to intellectual property that You create, You represent that You have received permission to make the Contributions on behalf of that employer, or that Your employer has waived such rights for the Contributions. - -You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license. - -Toby Zerner acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +源项目地址:https://github.com/flarum/core + +------------------- + +**来吧,让我们一起构建一个让小伙伴们都惊呆的 PHP 论坛。** + +QQ 交流群:188723593 + +我是 [Toby Zerner](http://tobyzerner.com), [esoTalk](http://esotalk.org) 的开发者。 +年前,我构建了一个新颖的,轻量级的 esoTalk 论坛的替代者。 +esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上](http://bbs.justjavac.com/6)(译注:esoTalk 的底层框架重新发明了轮子)。 + +**基于此,我们需要一个现代的,优雅的,简洁的,强大的论坛软件,并且易于使用和托管。** +这就是 Flarum。但我是一个全职学生,我没有足够的时间独立完成这个项目。 +于是我将采用开放协作的方式来开发 Flarum —— 让全世界的开发者一起开发 Flarum。 + +## 开发理念 + +I have a vision for Flarum — one that has grown over years of playing with forums, developing esoTalk, and learning from mistakes. It is captured by the following four points: + +- **Modern design.** Beautiful, clean, customizable. Forum design hasn’t evolved with the rest of the web; let's finally bring it up to speed. +- **Unopinionated feature-set.** Every community is different, and forum software should be able to adapt to — rather than define — how a community is run. As such, Flarum will have a lightweight core, and most features will be implemented as optional extensions. +- **High performance.** Flarum should perform well on scales big and small, and on a large range of devices. +- **Sustainable architecture.** Flarum should be built for the future, on a strong foundation which can evolve with the technologies that power it. + +Flarum is open-source software released under the [MIT license](https://github.com/flarum/core/blob/master/LICENSE.txt). + +## Technology + +I've carefully considered which frameworks to use to build Flarum. Here's the reasoning: + +### Laravel + +PHP remains the most user-friendly language for deploying web scripts, especially on shared hosting. The Laravel framework will allow for rapid development of Flarum’s API, and has a large community which will encourage collaboration and evolution. + +### Ember.js + +Ember.js is a mature JavaScript framework which will power Flarum’s front-end. Use of a JavaScript framework allows us to build a fast, dynamic interface which feels more like an app than a simple web page. + +> Don't like the fact that Flarum is an Ember.js app? Take a look at [FluxBB 2](https://github.com/fluxbb/fluxbb/tree/2.0), which is being developed in a more traditional manner with Laravel. + +## Current State + +I’ve been working on a prototype for some time in-between my studies. In addition to interface design, most of my time has been spent building out the architecture: making decisions about which frameworks to use, the most effective way to componentize everything, standardizing the API, etc. + +You might notice that a lot of the code is a bit of a mess right now. This is because I wanted to get things working on a superficial level in order to record material for the Kickstarter video, and since then I haven't yet had sufficient time to clean it up. Please be aware that the mess is not at all indicative of the standard that we're aiming for! + +### What’s Done + +- [x] The basic technology stack (Laravel and Ember – see above) +- [x] The [architectural foundation](https://github.com/flarum/core/wiki/Architecture) (core/API/web layers) +- [x] Some of the API (discussion and post read + write) +- [x] Discussion list view and basic search functionality +- [x] Discussion viewing and scrolling + +### What’s Next + +The priority at the moment is to build out a lightweight core, and only start building [Extensions](https://github.com/flarum/core/wiki/Extensions) when it is relatively stable. Below is a list of the things to work on immediately, with links to the relevant discussion. + +- [ ] Interface redesign ([#1](https://github.com/flarum/core/issues/1)) +- [ ] Upgrade to Laravel 5 ([#2](https://github.com/flarum/core/issues/2)) +- [ ] Set up testing frameworks in both Laravel ([#3](https://github.com/flarum/core/issues/3)) and Ember ([#4](https://github.com/flarum/core/issues/4)) +- [ ] Further consolidation of Extension interfaces (see [Extensions](https://github.com/flarum/core/wiki/Extensions)) +- [ ] Develop user authentication strategy ([#5](https://github.com/flarum/core/issues/5)) +- [ ] Implement replying, post editing, discussion creation ([#6](https://github.com/flarum/core/issues/6)) +- [ ] Implement discussion title editing ([#7](https://github.com/flarum/core/issues/7)) +- [ ] Implement post deletion ([#8](https://github.com/flarum/core/issues/8)) +- [ ] Implement discussion deletion ([#9](https://github.com/flarum/core/issues/9)) +- [ ] Design user profile interface ([#10](https://github.com/flarum/core/issues/10)) +- [ ] Design admin interfaces ([#11](https://github.com/flarum/core/issues/11)) +- [ ] Build Notifications system ([#12](https://github.com/flarum/core/issues/12)) + +For a full list of planned features, see [Features](https://github.com/flarum/core/wiki/Features). + +## Installation + +Currently Flarum is in its very early stages, and it isn’t pretty. **It is far from usable.** Set it up only if you know what you’re doing, and expect it to break a lot. + +1. Make sure you have [Composer](http://getcomposer.org), [ember-cli](http://www.ember-cli.com), and [Bower](http://bower.io) installed globally. +2. Create a new [Laravel 4](http://laravel.com/docs/4.2/quick) project. +3. Clone the Flarum repository into the workbench and install dependencies: + + ``` + git clone https://github.com/flarum/core.git workbench/flarum/core + cd workbench/flarum/core + composer install + cd ../../.. + php artisan dump-autoload + ``` + +5. Create a new MySQL database and configure your database details in `app/config/database.php`. +6. Run the Flarum migrations and database seeder to generate dummy data: + + ``` + php artisan migrate --bench="flarum/core" + php artisan db:seed --class="Flarum\Core\Support\Seeders\DatabaseSeeder" + ``` + +7. Add the Flarum service providers to the `providers` array in `app/config/app.php`: + + ``` + 'Flarum\Core\CoreServiceProvider', + 'Flarum\Api\ApiServiceProvider', + 'Flarum\Web\WebServiceProvider' + ``` + +8. Remove the default route in `app/routes.php`. +9. Run the following commands to compile the Ember app: + + ``` + cd workbench/flarum/core/ember + npm install + bower install + ember serve --output-path="../public" + ``` + +10. Visit your Laravel application in a browser. + + > Note: You must access the Laravel application so that it is at the top level (i.e. not under any sub-directories.) To do this, you can either set your web server's document root to the `public` folder of your application, or you can [configure a virtual host](http://davidwalsh.name/create-virtual-host) pointing to the `public` folder. + +If you’re having trouble, **do not** create a new issue — instead, get help on the [Flarum Development Forum](http://discuss.flarum.org) or hop on the IRC channel (#flarum on irc.freenode.net). + +## Contributing + +Building Flarum is going to be a team effort, and we'd love for you to help! All contributions are welcomed. + +### What Can I Do? + +- **Contribute code.** Start by becoming familiar with Flarum's source code and its [Architecture](https://github.com/flarum/core/wiki/Architecture). Then have a look at what needs to be done in the list above, and see if there's anything you can help out with. See below for instructions on submitting a Pull Request. + +- **Participate in discussion.** Review the [wiki](https://github.com/flarum/core/wiki) and [issues](https://github.com/flarum/core/issues) and contribute your constructive thoughts. We'd also love to hear general feedback on the [Flarum Development Forum](http://discuss.flarum.org), on IRC (#flarum on irc.freenode.net), and on [Twitter](http://twitter.com/flarum). + +- **Spread the word.** Know someone who could help out? Please share this project with them! + +> In this early stage of development, bug reports won't be particularly helpful, because things will be constantly changing and breaking. + +### Process + +1. Review the [Flarum Contributor License Agreement](#contributor-license-agreement). ([Why?](https://julien.ponge.org/blog/in-defense-of-contributor-license-agreements/)) + +2. Install Flarum as detailed in the instructions above. + +3. Create a new branch. + + ``` + git checkout -b new-flarum-branch + ``` + + > Please implement only one feature/bugfix per branch to keep pull requests clean and focused. + +4. Code. + - Follow the coding style: [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). + - Include tests and make sure they pass (subject to [#3](https://github.com/flarum/core/issues/3) and [#4](https://github.com/flarum/core/issues/4)). + +5. Commit. + - Commit messages are **required**. + - They should include a short description of the changes on the first line, then a blank line, then more details if necessary. + +6. Clean up. Squash together minor commits. + + ``` + git rebase -i + ``` + +7. Update your branch so that it is based on top of the latest code from the Flarum repository. + + ``` + git fetch origin + git rebase origin/master + ``` + +8. Fork your repository on GitHub and push to it. + + ``` + git remote add mine git@github.com:/flarum.git + git push mine new-flarum-branch + ``` + +9. Submit a pull request. + - Go to the Flarum repository you just pushed to (e.g. https://github.com/your-user-name/flarum). + - Click "Pull Request". + - Write your branch name in the branch field. + - Click "Update Commit Range". + - Ensure that the correct commits and files changes are included. + - Fill in a descriptive title and other details about your pull request. + - Click "Send pull request". + +10. Respond to feedback. + - We may suggest changes to your code. Maintaining a high standard of code quality is important for the longevity of this project — use it as an opportunity to improve your own skills and learn something new! + +### Core Team + +Currently the only person on the core development team is Toby Zerner ([@tobscure](http://twitter.com/tobscure)). Over time, judged by display of commitment to the project, and quantity/quality of contributions, I will be looking for more people to join the core development team. Please do not email me asking to be on the core team; rather, demonstrate initiative and commitment to the project, and I will more than likely notice! + +### Contributor License Agreement + +By contributing your code to Flarum you grant Toby Zerner a non-exclusive, irrevocable, worldwide, royalty-free, sublicenseable, transferable license under all of Your relevant intellectual property rights (including copyright, patent, and any other rights), to use, copy, prepare derivative works of, distribute and publicly perform and display the Contributions on any licensing terms, including without limitation: (a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the licenses granted herein, You reserve all right, title, and interest in and to the Contribution. + +You confirm that you are able to grant us these rights. You represent that You are legally entitled to grant the above license. If Your employer has rights to intellectual property that You create, You represent that You have received permission to make the Contributions on behalf of that employer, or that Your employer has waived such rights for the Contributions. + +You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license. + +Toby Zerner acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. From 7c388125d30b5027a0af6405b2e088b5755fb719 Mon Sep 17 00:00:00 2001 From: justjavac Date: Wed, 31 Dec 2014 11:51:02 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E4=BA=A4=E6=B5=81=E8=AE=BA=E5=9D=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f8c9598..d8b4ce2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ 源项目地址:https://github.com/flarum/core -------------------- - **来吧,让我们一起构建一个让小伙伴们都惊呆的 PHP 论坛。** +------------------- + QQ 交流群:188723593 +中文论坛(基于esoTalk):http://discuss.flarum.org.cn + +-------------------- + 我是 [Toby Zerner](http://tobyzerner.com), [esoTalk](http://esotalk.org) 的开发者。 年前,我构建了一个新颖的,轻量级的 esoTalk 论坛的替代者。 esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上](http://bbs.justjavac.com/6)(译注:esoTalk 的底层框架重新发明了轮子)。 From c3aa796c4d6bb5851ba012854b493a07232cb403 Mon Sep 17 00:00:00 2001 From: justjavac Date: Wed, 31 Dec 2014 12:28:31 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E7=BF=BB=E8=AF=91=20readme=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d8b4ce2..2e532a2 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,13 @@ I’ve been working on a prototype for some time in-between my studies. In addit You might notice that a lot of the code is a bit of a mess right now. This is because I wanted to get things working on a superficial level in order to record material for the Kickstarter video, and since then I haven't yet had sufficient time to clean it up. Please be aware that the mess is not at all indicative of the standard that we're aiming for! -### What’s Done +### 已经完成的 -- [x] The basic technology stack (Laravel and Ember – see above) -- [x] The [architectural foundation](https://github.com/flarum/core/wiki/Architecture) (core/API/web layers) -- [x] Some of the API (discussion and post read + write) -- [x] Discussion list view and basic search functionality -- [x] Discussion viewing and scrolling +- [x] 基础技术栈 (Laravel 和 Ember) +- [x] 三层架构的 [体系结构](http://discuss.flarum.org.cn/17-flarum-architecture) (core/API/web layers) +- [x] 部分 API (discussion 和 post 的读写) +- [x] Discussion 的列表和初级的搜索功能 +- [x] Discussion 详情页和滚动 ### What’s Next From 0c5be287f357fe38fd9a347e759636c12766521b Mon Sep 17 00:00:00 2001 From: justjavac Date: Wed, 31 Dec 2014 13:09:01 +0800 Subject: [PATCH 04/27] =?UTF-8?q?=E7=BF=BB=E8=AF=91=20What=E2=80=99s=20Nex?= =?UTF-8?q?t=20=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2e532a2..1e40742 100644 --- a/README.md +++ b/README.md @@ -61,18 +61,18 @@ You might notice that a lot of the code is a bit of a mess right now. This is be The priority at the moment is to build out a lightweight core, and only start building [Extensions](https://github.com/flarum/core/wiki/Extensions) when it is relatively stable. Below is a list of the things to work on immediately, with links to the relevant discussion. -- [ ] Interface redesign ([#1](https://github.com/flarum/core/issues/1)) -- [ ] Upgrade to Laravel 5 ([#2](https://github.com/flarum/core/issues/2)) -- [ ] Set up testing frameworks in both Laravel ([#3](https://github.com/flarum/core/issues/3)) and Ember ([#4](https://github.com/flarum/core/issues/4)) -- [ ] Further consolidation of Extension interfaces (see [Extensions](https://github.com/flarum/core/wiki/Extensions)) -- [ ] Develop user authentication strategy ([#5](https://github.com/flarum/core/issues/5)) -- [ ] Implement replying, post editing, discussion creation ([#6](https://github.com/flarum/core/issues/6)) -- [ ] Implement discussion title editing ([#7](https://github.com/flarum/core/issues/7)) -- [ ] Implement post deletion ([#8](https://github.com/flarum/core/issues/8)) -- [ ] Implement discussion deletion ([#9](https://github.com/flarum/core/issues/9)) -- [ ] Design user profile interface ([#10](https://github.com/flarum/core/issues/10)) -- [ ] Design admin interfaces ([#11](https://github.com/flarum/core/issues/11)) -- [ ] Build Notifications system ([#12](https://github.com/flarum/core/issues/12)) +- [ ] 界面重新设计 ([界面设计预览](http://discuss.flarum.org.cn/16-flarum)) +- [ ] 升级到 Laravel 5 +- [ ] 为 Laravel 和 Ember 设置测试框架 +- [ ] 进一步开发扩展接口 (vi [Extensions](https://github.com/flarum/core/wiki/Extensions)) +- [ ] 开发用户认证策略 +- [ ] 实现回复主题(discussion)、编辑帖子(post)、创建主题(discussion) +- [ ] 实现编辑主题(discussion)标题 +- [ ] 实现删除帖子(post) +- [ ] 实现删除主题(discussion) +- [ ] 设计用户中心界面 +- [ ] 设计管理界面 +- [ ] 创建通知(notification)系统 For a full list of planned features, see [Features](https://github.com/flarum/core/wiki/Features). From c6635551c45ef356fbaace4a93ab34adec7b0d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Thu, 22 Jan 2015 11:41:15 +0800 Subject: [PATCH 05/27] =?UTF-8?q?Flarum=20=E6=88=AA=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1e40742..28e4c1d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上]( 这就是 Flarum。但我是一个全职学生,我没有足够的时间独立完成这个项目。 于是我将采用开放协作的方式来开发 Flarum —— 让全世界的开发者一起开发 Flarum。 +![Flarum 截图](https://pbs.twimg.com/media/B7eOB3bCMAE88_f.png:large) + ## 开发理念 I have a vision for Flarum — one that has grown over years of playing with forums, developing esoTalk, and learning from mistakes. It is captured by the following four points: From 562dd48f032f1a064d980bdff29ddf2b211aadb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Mon, 11 May 2015 14:48:49 +0800 Subject: [PATCH 06/27] Update README.md --- README.md | 55 ++++++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 28e4c1d..64dc1b0 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Ember.js is a mature JavaScript framework which will power Flarum’s front-end. > Don't like the fact that Flarum is an Ember.js app? Take a look at [FluxBB 2](https://github.com/fluxbb/fluxbb/tree/2.0), which is being developed in a more traditional manner with Laravel. +注:Flarum 前端现在已经使用 Mithril.js 替换了 Ember.js http://discuss.flarum.org.cn/26-flarum-mithril-js-ember-js + ## Current State I’ve been working on a prototype for some time in-between my studies. In addition to interface design, most of my time has been spent building out the architecture: making decisions about which frameworks to use, the most effective way to componentize everything, standardizing the API, etc. @@ -78,53 +80,24 @@ The priority at the moment is to build out a lightweight core, and only start bu For a full list of planned features, see [Features](https://github.com/flarum/core/wiki/Features). -## Installation - -Currently Flarum is in its very early stages, and it isn’t pretty. **It is far from usable.** Set it up only if you know what you’re doing, and expect it to break a lot. - -1. Make sure you have [Composer](http://getcomposer.org), [ember-cli](http://www.ember-cli.com), and [Bower](http://bower.io) installed globally. -2. Create a new [Laravel 4](http://laravel.com/docs/4.2/quick) project. -3. Clone the Flarum repository into the workbench and install dependencies: - - ``` - git clone https://github.com/flarum/core.git workbench/flarum/core - cd workbench/flarum/core - composer install - cd ../../.. - php artisan dump-autoload - ``` - -5. Create a new MySQL database and configure your database details in `app/config/database.php`. -6. Run the Flarum migrations and database seeder to generate dummy data: - - ``` - php artisan migrate --bench="flarum/core" - php artisan db:seed --class="Flarum\Core\Support\Seeders\DatabaseSeeder" - ``` +## 安装 -7. Add the Flarum service providers to the `providers` array in `app/config/app.php`: +**Flarum is currently in development and will be ready to use later this year.** ([Roadmap](http://tobyzerner.com/flarum/)) If you want to give the development version a spin or are interested in contributing, for now you can install Flarum's Vagrant image. An easier installation process will become a priority once Flarum is more stable. - ``` - 'Flarum\Core\CoreServiceProvider', - 'Flarum\Api\ApiServiceProvider', - 'Flarum\Web\WebServiceProvider' - ``` +1. Install [Vagrant](https://www.vagrantup.com) and [VirtualBox](https://www.virtualbox.org). +2. Clone this repository and set up the Vagrant box: -8. Remove the default route in `app/routes.php`. -9. Run the following commands to compile the Ember app: - - ``` - cd workbench/flarum/core/ember - npm install - bower install - ember serve --output-path="../public" - ``` + ```sh + git clone --recursive https://github.com/flarum/flarum.git + cd flarum + vagrant up + ``` -10. Visit your Laravel application in a browser. +3. Add an entry to your /etc/hosts file: - > Note: You must access the Laravel application so that it is at the top level (i.e. not under any sub-directories.) To do this, you can either set your web server's document root to the `public` folder of your application, or you can [configure a virtual host](http://davidwalsh.name/create-virtual-host) pointing to the `public` folder. + ```192.168.29.29 flarum.dev``` -If you’re having trouble, **do not** create a new issue — instead, get help on the [Flarum Development Forum](http://discuss.flarum.org) or hop on the IRC channel (#flarum on irc.freenode.net). +4. Visit flarum.dev in a browser. ## Contributing From fc64b293113d0ddb1e66072f336a059b66814e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Mon, 11 May 2015 14:49:44 +0800 Subject: [PATCH 07/27] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 64dc1b0..7c27845 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ QQ 交流群:188723593 中文论坛(基于esoTalk):http://discuss.flarum.org.cn From 2c18019120a52b4edc895d2cb105e85bb911c9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Fri, 15 May 2015 11:24:40 +0800 Subject: [PATCH 09/27] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Gitter=20=E4=BA=A4?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c037edc..cb38a6a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ QQ 交流群: Date: Fri, 19 Jun 2015 09:20:04 +0800 Subject: [PATCH 13/27] Update README.md --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dc77b6a..be4f9d8 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,11 @@ esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上]( ## Contributing -Building Flarum is going to be a team effort, and we'd love for you to help! All contributions are welcomed. +Interested in contributing to Flarum? Read the [Contribution Guide](https://github.com/flarum/flarum/blob/master/CONTRIBUTING.md)! -### Contributor License Agreement +Bug reports should go in [flarum/core](https://github.com/flarum/core/issues) or the [relevant extension repository](https://github.com/flarum). -By contributing your code to Flarum you grant Toby Zerner a non-exclusive, irrevocable, worldwide, royalty-free, sublicenseable, transferable license under all of Your relevant intellectual property rights (including copyright, patent, and any other rights), to use, copy, prepare derivative works of, distribute and publicly perform and display the Contributions on any licensing terms, including without limitation: (a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the licenses granted herein, You reserve all right, title, and interest in and to the Contribution. +### 核心团队 -You confirm that you are able to grant us these rights. You represent that You are legally entitled to grant the above license. If Your employer has rights to intellectual property that You create, You represent that You have received permission to make the Contributions on behalf of that employer, or that Your employer has waived such rights for the Contributions. - -You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license. - -Toby Zerner acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +- Toby Zerner ([esoTalk](http://esotalk.org), [@tobscure](http://twitter.com/tobscure)) +- Franz Liedke ([FluxBB](http://fluxbb.org), [@franzliedke](http://twitter.com/franzliedke)) From bb04edf5d75eedce9cee051d64c5aac6eb89aa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Fri, 19 Jun 2015 09:20:37 +0800 Subject: [PATCH 14/27] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index be4f9d8..c4ebb25 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上]( 这就是 Flarum。但我是一个全职学生,我没有足够的时间独立完成这个项目。 于是我将采用开放协作的方式来开发 Flarum —— 让全世界的开发者一起开发 Flarum。 -![Flarum 截图](https://pbs.twimg.com/media/B7eOB3bCMAE88_f.png:large) - ## 安装 **Flarum is currently in development and will be ready to use later this year.** ([Roadmap](http://tobyzerner.com/flarum/)) If you want to give the development version a spin or are interested in contributing, for now you can install Flarum's Vagrant image. An easier installation process will become a priority once Flarum is more stable. From c37f4617072bbfa5f57fa88c9c7796d88fb9d0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Fri, 19 Jun 2015 09:26:09 +0800 Subject: [PATCH 15/27] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c4ebb25..084ad2b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -源项目地址:https://github.com/flarum/core +源项目地址:https://github.com/flarum/flarum + +中文语言包:https://github.com/justjavac/flarum-i18n-zh QQ 交流群:188723593 -中文论坛(基于esoTalk):http://discuss.flarum.org.cn +中文开发者论坛:http://discuss.flarum.org.cn Live Demo http://demo.flarum.org @@ -20,7 +22,7 @@ esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上]( **Flarum is currently in development and will be ready to use later this year.** ([Roadmap](http://tobyzerner.com/flarum/)) If you want to give the development version a spin or are interested in contributing, for now you can install Flarum's Vagrant image. An easier installation process will become a priority once Flarum is more stable. -1. Install [Vagrant](https://www.vagrantup.com) and [VirtualBox](https://www.virtualbox.org). +1. 安装 [Vagrant](https://www.vagrantup.com) 和 [VirtualBox](https://www.virtualbox.org). 2. Clone this repository and set up the Vagrant box: ```sh From f7535dea930699e6549bd2969d0d90a3e1ffea54 Mon Sep 17 00:00:00 2001 From: justjavac Date: Fri, 19 Jun 2015 09:47:48 +0800 Subject: [PATCH 16/27] init --- .gitignore | 5 +- .travis.yml | 12 - codeception.yml | 17 - composer.json | 32 - composer.lock | 1963 ----------------- ember/.bowerrc | 4 - ember/.editorconfig | 33 - ember/.ember-cli | 9 - ember/.gitignore | 17 - ember/.jshintrc | 32 - ember/.travis.yml | 20 - ember/Brocfile.js | 18 - ember/app/adapters/application.js | 37 - ember/app/app.js | 77 - ember/app/components/.gitkeep | 0 ember/app/components/button-item.js | 18 - ember/app/components/item-collection.js | 6 - ember/app/components/loading-indicator.js | 13 - ember/app/components/menu-item-separator.js | 5 - ember/app/components/menu-item.js | 31 - ember/app/components/menu-list.js | 6 - ember/app/components/menu-split.js | 15 - ember/app/components/nav-item.js | 34 - ember/app/components/notification-message.js | 9 - ember/app/components/search-input.js | 38 - ember/app/components/select-input.js | 9 - ember/app/controllers/.gitkeep | 0 ember/app/controllers/application.js | 48 - ember/app/controllers/composer.js | 17 - ember/app/controllers/discussion.js | 103 - ember/app/controllers/discussions.js | 175 -- ember/app/controllers/discussions/index.js | 5 - ember/app/controllers/login.js | 11 - ember/app/helpers/.gitkeep | 0 ember/app/helpers/abbreviate-number.js | 6 - ember/app/helpers/abbreviate-time.js | 31 - ember/app/helpers/fa-icon.js | 6 - ember/app/helpers/highlight-words.js | 18 - ember/app/helpers/render-hook.js | 6 - ember/app/helpers/user-avatar.js | 53 - ember/app/index.html | 25 - ember/app/mixins/post-stream.js | 33 - ember/app/models/.gitkeep | 0 ember/app/models/discussion-result.js | 12 - ember/app/models/discussion-state.js | 7 - ember/app/models/discussion.js | 81 - ember/app/models/group.js | 10 - ember/app/models/post-result.js | 20 - ember/app/models/post-stream.js | 201 -- ember/app/models/post.js | 37 - ember/app/models/result-stream.js | 44 - ember/app/models/user.js | 21 - ember/app/router.js | 24 - ember/app/routes/.gitkeep | 0 ember/app/routes/application.js | 30 - ember/app/routes/discussion.js | 64 - ember/app/routes/discussions.js | 36 - ember/app/routes/discussions/index.js | 20 - ember/app/serializers/application.js | 12 - ember/app/styles/.gitkeep | 0 ember/app/styles/app.less | 14 - ember/app/styles/config.less | 32 - .../styles/flarum/bootstrap/bootstrap.less | 50 - .../styles/flarum/bootstrap/variables.less | 832 ------- ember/app/styles/flarum/discussion.less | 357 --- ember/app/styles/flarum/discussions.less | 454 ---- ember/app/styles/flarum/global.less | 858 ------- ember/app/templates/.gitkeep | 0 ember/app/templates/application.hbs | 56 - ember/app/templates/components/.gitkeep | 0 .../templates/components/item-collection.hbs | 3 - ember/app/templates/components/menu-list.hbs | 3 - ember/app/templates/components/menu-split.hbs | 13 - .../components/notification-message.hbs | 9 - .../components/post-type-comment.hbs | 26 - .../templates/components/post-type-title.hbs | 4 - .../app/templates/components/search-input.hbs | 2 - .../app/templates/components/text-editor.hbs | 9 - ember/app/templates/composer.hbs | 17 - ember/app/templates/discussion-header.hbs | 9 - ember/app/templates/discussion-post.hbs | 13 - ember/app/templates/discussion-scrollbar.hbs | 20 - ember/app/templates/discussion.hbs | 19 - ember/app/templates/discussions-header.hbs | 7 - ember/app/templates/discussions-nav.hbs | 4 - ember/app/templates/discussions-result.hbs | 60 - ember/app/templates/discussions.hbs | 46 - ember/app/templates/error.hbs | 5 - ember/app/templates/loading.hbs | 1 - ember/app/templates/login.hbs | 27 - ember/app/templates/session.hbs | 36 - ember/app/transitions.js | 12 - ember/app/transitions/slide-left.js | 2 - ember/app/transitions/slide-right.js | 2 - ember/app/transitions/slide.js | 48 - ember/app/utils/.gitkeep | 0 ember/app/utils/menu.js | 33 - ember/app/utils/named-container-view.js | 65 - ember/app/utils/plugin.js | 5 - ember/app/utils/scrollbar.js | 62 - ember/app/views/.gitkeep | 0 ember/app/views/application.js | 9 - ember/app/views/composer.js | 37 - ember/app/views/discussion-item.js | 132 -- ember/app/views/discussion-post.js | 55 - ember/app/views/discussion-scrollbar.js | 280 --- ember/app/views/discussion-sidebar.js | 50 - ember/app/views/discussion.js | 237 -- ember/app/views/discussions-nav.js | 22 - ember/app/views/discussions-result.js | 154 -- ember/app/views/discussions-sidebar.js | 127 -- ember/app/views/discussions.js | 57 - ember/app/views/discussions/index.js | 12 - ember/app/views/login.js | 29 - ember/app/views/session.js | 5 - ember/bower.json | 22 - ember/config/environment.js | 47 - ember/package.json | 39 - ember/public/avatars/001.jpg | Bin 5064 -> 0 bytes ember/public/avatars/002.jpg | Bin 5232 -> 0 bytes ember/public/avatars/003.jpg | Bin 5207 -> 0 bytes ember/public/avatars/004.jpg | Bin 3601 -> 0 bytes ember/public/avatars/005.jpg | Bin 5524 -> 0 bytes ember/public/avatars/006.jpg | Bin 7417 -> 0 bytes ember/public/avatars/007.jpg | Bin 8987 -> 0 bytes ember/public/avatars/008.jpg | Bin 4266 -> 0 bytes ember/public/avatars/009.jpg | Bin 3695 -> 0 bytes ember/public/avatars/010.jpg | Bin 2937 -> 0 bytes ember/public/avatars/011.jpg | Bin 15768 -> 0 bytes ember/public/avatars/012.jpg | Bin 5252 -> 0 bytes ember/public/avatars/013.jpg | Bin 5494 -> 0 bytes ember/public/avatars/014.jpg | Bin 3195 -> 0 bytes ember/public/avatars/015.jpg | Bin 6919 -> 0 bytes ember/public/avatars/016.jpg | Bin 5188 -> 0 bytes ember/public/avatars/017.jpg | Bin 4493 -> 0 bytes ember/public/avatars/018.jpg | Bin 6111 -> 0 bytes ember/public/avatars/019.jpg | Bin 3258 -> 0 bytes ember/public/avatars/020.jpg | Bin 2064 -> 0 bytes ember/testem.json | 11 - ember/tests/.jshintrc | 74 - ember/tests/helpers/resolver.js | 11 - ember/tests/helpers/start-app.js | 19 - ember/tests/index.html | 49 - ember/tests/test-helper.js | 12 - ember/tests/unit/.gitkeep | 0 src/Flarum/Api/Actions/Base.php | 215 -- src/Flarum/Api/Actions/Discussions/Create.php | 35 - src/Flarum/Api/Actions/Discussions/Delete.php | 26 - src/Flarum/Api/Actions/Discussions/Index.php | 84 - src/Flarum/Api/Actions/Discussions/Show.php | 29 - src/Flarum/Api/Actions/Discussions/Update.php | 51 - src/Flarum/Api/Actions/Groups/Index.php | 18 - src/Flarum/Api/Actions/Posts/Create.php | 39 - src/Flarum/Api/Actions/Posts/Delete.php | 26 - src/Flarum/Api/Actions/Posts/Index.php | 62 - src/Flarum/Api/Actions/Posts/Show.php | 39 - src/Flarum/Api/Actions/Posts/Update.php | 39 - src/Flarum/Api/Actions/Users/Create.php | 39 - src/Flarum/Api/Actions/Users/Delete.php | 26 - src/Flarum/Api/Actions/Users/Index.php | 83 - src/Flarum/Api/Actions/Users/Show.php | 26 - src/Flarum/Api/Actions/Users/Update.php | 40 - src/Flarum/Api/ApiServiceProvider.php | 46 - .../Api/Serializers/ActivitySerializer.php | 19 - src/Flarum/Api/Serializers/BaseSerializer.php | 102 - .../Serializers/DiscussionBasicSerializer.php | 50 - .../Api/Serializers/DiscussionSerializer.php | 137 -- .../Api/Serializers/GroupSerializer.php | 48 - .../Api/Serializers/PostBasicSerializer.php | 85 - src/Flarum/Api/Serializers/PostSerializer.php | 109 - .../Api/Serializers/UserAdminSerializer.php | 21 - .../Api/Serializers/UserBasicSerializer.php | 50 - .../Api/Serializers/UserCurrentSerializer.php | 29 - src/Flarum/Api/Serializers/UserSerializer.php | 52 - src/Flarum/Core/Activity/Activity.php | 36 - src/Flarum/Core/CoreServiceProvider.php | 96 - .../Commands/DeleteDiscussionCommand.php | 14 - .../DeleteDiscussionCommandHandler.php | 33 - .../Commands/DeleteDiscussionValidator.php | 7 - .../Commands/EditDiscussionCommand.php | 16 - .../Commands/EditDiscussionCommandHandler.php | 38 - .../Commands/EditDiscussionValidator.php | 7 - .../Commands/ReadDiscussionCommand.php | 17 - .../Commands/ReadDiscussionCommandHandler.php | 35 - .../Commands/ReadDiscussionValidator.php | 19 - .../Commands/StartDiscussionCommand.php | 17 - .../StartDiscussionCommandHandler.php | 59 - .../Commands/StartDiscussionValidator.php | 7 - src/Flarum/Core/Discussions/Discussion.php | 190 -- .../Core/Discussions/DiscussionFinder.php | 258 --- .../Core/Discussions/DiscussionRepository.php | 43 - .../Core/Discussions/DiscussionState.php | 49 - .../Events/DiscussionWasDeleted.php | 13 - .../Discussions/Events/DiscussionWasRead.php | 13 - .../Events/DiscussionWasRenamed.php | 17 - .../Events/DiscussionWasStarted.php | 13 - src/Flarum/Core/Entity.php | 81 - src/Flarum/Core/Forum.php | 26 - src/Flarum/Core/Groups/Group.php | 18 - src/Flarum/Core/Groups/GroupRepository.php | 15 - .../Listeners/DiscussionMetadataUpdater.php | 63 - src/Flarum/Core/Listeners/PostFormatter.php | 48 - .../Core/Listeners/TitleChangePostCreator.php | 28 - .../Core/Listeners/UserMetadataUpdater.php | 70 - src/Flarum/Core/Permissions/Manager.php | 42 - src/Flarum/Core/Permissions/Permission.php | 7 - .../Core/Permissions/PermissionRepository.php | 20 - .../Core/Posts/Commands/DeletePostCommand.php | 14 - .../Commands/DeletePostCommandHandler.php | 33 - .../Posts/Commands/DeletePostValidator.php | 7 - .../Core/Posts/Commands/EditPostCommand.php | 18 - .../Posts/Commands/EditPostCommandHandler.php | 44 - .../Core/Posts/Commands/EditPostValidator.php | 7 - .../Core/Posts/Commands/PostReplyCommand.php | 17 - .../Commands/PostReplyCommandHandler.php | 53 - .../Posts/Commands/PostReplyValidator.php | 7 - src/Flarum/Core/Posts/CommentPost.php | 63 - .../Core/Posts/Events/PostWasDeleted.php | 13 - .../Core/Posts/Events/PostWasHidden.php | 13 - .../Core/Posts/Events/PostWasRestored.php | 13 - .../Core/Posts/Events/PostWasRevised.php | 13 - .../Core/Posts/Events/ReplyWasPosted.php | 13 - src/Flarum/Core/Posts/Post.php | 113 - src/Flarum/Core/Posts/PostRepository.php | 31 - src/Flarum/Core/Posts/TitleChangePost.php | 25 - .../Core/Search/FulltextSearchDriver.php | 48 - .../Core/Search/SearchDriverInterface.php | 8 - src/Flarum/Core/Search/SphinxSearchDriver.php | 34 - src/Flarum/Core/Search/Tokenizer.php | 17 - src/Flarum/Core/Search/Tokens/AuthorToken.php | 25 - .../Core/Search/Tokens/TokenAbstract.php | 27 - .../Core/Search/Tokens/TokenInterface.php | 12 - src/Flarum/Core/Support/CommandValidator.php | 44 - .../Exceptions/PermissionDeniedException.php | 7 - .../Exceptions/ValidationFailureException.php | 40 - .../Core/Support/Extensions/Extension.php | 6 - .../Core/Support/Extensions/Manager.php | 6 - .../Support/Seeders/ConfigTableSeeder.php | 17 - .../Core/Support/Seeders/DatabaseSeeder.php | 40 - .../Support/Seeders/DiscussionTableSeeder.php | 134 -- .../Core/Support/Seeders/UserTableSeeder.php | 76 - .../Core/Users/Commands/DeleteUserCommand.php | 14 - .../Commands/DeleteUserCommandHandler.php | 33 - .../Users/Commands/DeleteUserValidator.php | 7 - .../Core/Users/Commands/EditUserCommand.php | 20 - .../Users/Commands/EditUserCommandHandler.php | 46 - .../Core/Users/Commands/EditUserValidator.php | 7 - .../Users/Commands/RegisterUserCommand.php | 20 - .../Commands/RegisterUserCommandHandler.php | 49 - .../Users/Commands/RegisterUserValidator.php | 7 - .../Core/Users/Events/EmailWasChanged.php | 13 - .../Core/Users/Events/PasswordWasChanged.php | 13 - .../Core/Users/Events/UserWasDeleted.php | 13 - .../Core/Users/Events/UserWasRegistered.php | 13 - .../Core/Users/Events/UserWasRenamed.php | 13 - src/Flarum/Core/Users/Guest.php | 34 - src/Flarum/Core/Users/User.php | 240 -- src/Flarum/Core/Users/UserFinder.php | 167 -- src/Flarum/Core/Users/UserRepository.php | 48 - src/Flarum/Core/Users/UsernameValidator.php | 10 - src/Flarum/Web/AssetManager.php | 90 - src/Flarum/Web/WebServiceProvider.php | 67 - src/config/.gitkeep | 0 src/config/config.php | 13 - src/lang/.gitkeep | 0 src/lang/en/reminders.php | 22 - src/lang/en/validation.php | 93 - src/lang/zh_CN/reminders.php | 17 - src/lang/zh_CN/validation.php | 88 - src/lang/zh_TW/reminders.php | 17 - src/lang/zh_TW/validation.php | 89 - src/migrations/.gitkeep | 0 .../2014_01_14_231259_create_config_table.php | 32 - ..._01_14_231321_create_discussions_table.php | 45 - .../2014_01_14_231334_create_groups_table.php | 32 - ..._01_14_231343_create_permissions_table.php | 34 - .../2014_01_14_231350_create_posts_table.php | 49 - ...014_01_14_231357_create_sessions_table.php | 33 - .../2014_01_14_231404_create_users_table.php | 39 - ..._231455_create_users_discussions_table.php | 35 - ...01_14_231503_create_users_groups_table.php | 33 - ...014_01_19_232631_create_activity_table.php | 38 - src/routes.api.php | 178 -- src/routes.php | 7 - src/views/.gitkeep | 0 src/views/index.blade.php | 25 - tests/.gitkeep | 0 tests/_bootstrap.php | 2 - tests/_data/dump.sql | 1 - tests/_support/AcceptanceHelper.php | 10 - tests/_support/FunctionalHelper.php | 10 - tests/_support/UnitHelper.php | 10 - tests/acceptance.suite.yml | 14 - tests/acceptance/AcceptanceTester.php | 1587 ------------- tests/acceptance/_bootstrap.php | 2 - tests/functional.suite.yml | 9 - tests/functional/FunctionalTester.php | 360 --- tests/functional/_bootstrap.php | 2 - tests/unit.suite.yml | 6 - tests/unit/UnitTester.php | 268 --- tests/unit/_bootstrap.php | 2 - 301 files changed, 1 insertion(+), 16524 deletions(-) delete mode 100644 .travis.yml delete mode 100644 codeception.yml delete mode 100644 composer.json delete mode 100644 composer.lock delete mode 100644 ember/.bowerrc delete mode 100644 ember/.editorconfig delete mode 100644 ember/.ember-cli delete mode 100644 ember/.gitignore delete mode 100644 ember/.jshintrc delete mode 100644 ember/.travis.yml delete mode 100644 ember/Brocfile.js delete mode 100644 ember/app/adapters/application.js delete mode 100644 ember/app/app.js delete mode 100644 ember/app/components/.gitkeep delete mode 100644 ember/app/components/button-item.js delete mode 100644 ember/app/components/item-collection.js delete mode 100644 ember/app/components/loading-indicator.js delete mode 100644 ember/app/components/menu-item-separator.js delete mode 100644 ember/app/components/menu-item.js delete mode 100644 ember/app/components/menu-list.js delete mode 100644 ember/app/components/menu-split.js delete mode 100644 ember/app/components/nav-item.js delete mode 100644 ember/app/components/notification-message.js delete mode 100644 ember/app/components/search-input.js delete mode 100644 ember/app/components/select-input.js delete mode 100644 ember/app/controllers/.gitkeep delete mode 100644 ember/app/controllers/application.js delete mode 100644 ember/app/controllers/composer.js delete mode 100644 ember/app/controllers/discussion.js delete mode 100644 ember/app/controllers/discussions.js delete mode 100644 ember/app/controllers/discussions/index.js delete mode 100644 ember/app/controllers/login.js delete mode 100644 ember/app/helpers/.gitkeep delete mode 100644 ember/app/helpers/abbreviate-number.js delete mode 100644 ember/app/helpers/abbreviate-time.js delete mode 100644 ember/app/helpers/fa-icon.js delete mode 100644 ember/app/helpers/highlight-words.js delete mode 100644 ember/app/helpers/render-hook.js delete mode 100644 ember/app/helpers/user-avatar.js delete mode 100644 ember/app/index.html delete mode 100644 ember/app/mixins/post-stream.js delete mode 100644 ember/app/models/.gitkeep delete mode 100644 ember/app/models/discussion-result.js delete mode 100644 ember/app/models/discussion-state.js delete mode 100644 ember/app/models/discussion.js delete mode 100644 ember/app/models/group.js delete mode 100644 ember/app/models/post-result.js delete mode 100644 ember/app/models/post-stream.js delete mode 100644 ember/app/models/post.js delete mode 100644 ember/app/models/result-stream.js delete mode 100644 ember/app/models/user.js delete mode 100644 ember/app/router.js delete mode 100644 ember/app/routes/.gitkeep delete mode 100644 ember/app/routes/application.js delete mode 100644 ember/app/routes/discussion.js delete mode 100644 ember/app/routes/discussions.js delete mode 100644 ember/app/routes/discussions/index.js delete mode 100644 ember/app/serializers/application.js delete mode 100644 ember/app/styles/.gitkeep delete mode 100644 ember/app/styles/app.less delete mode 100644 ember/app/styles/config.less delete mode 100644 ember/app/styles/flarum/bootstrap/bootstrap.less delete mode 100644 ember/app/styles/flarum/bootstrap/variables.less delete mode 100644 ember/app/styles/flarum/discussion.less delete mode 100644 ember/app/styles/flarum/discussions.less delete mode 100644 ember/app/styles/flarum/global.less delete mode 100644 ember/app/templates/.gitkeep delete mode 100644 ember/app/templates/application.hbs delete mode 100644 ember/app/templates/components/.gitkeep delete mode 100644 ember/app/templates/components/item-collection.hbs delete mode 100644 ember/app/templates/components/menu-list.hbs delete mode 100644 ember/app/templates/components/menu-split.hbs delete mode 100644 ember/app/templates/components/notification-message.hbs delete mode 100644 ember/app/templates/components/post-type-comment.hbs delete mode 100644 ember/app/templates/components/post-type-title.hbs delete mode 100644 ember/app/templates/components/search-input.hbs delete mode 100644 ember/app/templates/components/text-editor.hbs delete mode 100644 ember/app/templates/composer.hbs delete mode 100644 ember/app/templates/discussion-header.hbs delete mode 100644 ember/app/templates/discussion-post.hbs delete mode 100644 ember/app/templates/discussion-scrollbar.hbs delete mode 100644 ember/app/templates/discussion.hbs delete mode 100644 ember/app/templates/discussions-header.hbs delete mode 100644 ember/app/templates/discussions-nav.hbs delete mode 100644 ember/app/templates/discussions-result.hbs delete mode 100644 ember/app/templates/discussions.hbs delete mode 100644 ember/app/templates/error.hbs delete mode 100644 ember/app/templates/loading.hbs delete mode 100644 ember/app/templates/login.hbs delete mode 100644 ember/app/templates/session.hbs delete mode 100644 ember/app/transitions.js delete mode 100644 ember/app/transitions/slide-left.js delete mode 100644 ember/app/transitions/slide-right.js delete mode 100644 ember/app/transitions/slide.js delete mode 100644 ember/app/utils/.gitkeep delete mode 100644 ember/app/utils/menu.js delete mode 100644 ember/app/utils/named-container-view.js delete mode 100644 ember/app/utils/plugin.js delete mode 100644 ember/app/utils/scrollbar.js delete mode 100644 ember/app/views/.gitkeep delete mode 100644 ember/app/views/application.js delete mode 100644 ember/app/views/composer.js delete mode 100644 ember/app/views/discussion-item.js delete mode 100644 ember/app/views/discussion-post.js delete mode 100644 ember/app/views/discussion-scrollbar.js delete mode 100644 ember/app/views/discussion-sidebar.js delete mode 100644 ember/app/views/discussion.js delete mode 100644 ember/app/views/discussions-nav.js delete mode 100644 ember/app/views/discussions-result.js delete mode 100644 ember/app/views/discussions-sidebar.js delete mode 100644 ember/app/views/discussions.js delete mode 100644 ember/app/views/discussions/index.js delete mode 100644 ember/app/views/login.js delete mode 100644 ember/app/views/session.js delete mode 100644 ember/bower.json delete mode 100644 ember/config/environment.js delete mode 100644 ember/package.json delete mode 100644 ember/public/avatars/001.jpg delete mode 100644 ember/public/avatars/002.jpg delete mode 100644 ember/public/avatars/003.jpg delete mode 100644 ember/public/avatars/004.jpg delete mode 100644 ember/public/avatars/005.jpg delete mode 100644 ember/public/avatars/006.jpg delete mode 100644 ember/public/avatars/007.jpg delete mode 100644 ember/public/avatars/008.jpg delete mode 100644 ember/public/avatars/009.jpg delete mode 100644 ember/public/avatars/010.jpg delete mode 100644 ember/public/avatars/011.jpg delete mode 100644 ember/public/avatars/012.jpg delete mode 100644 ember/public/avatars/013.jpg delete mode 100644 ember/public/avatars/014.jpg delete mode 100644 ember/public/avatars/015.jpg delete mode 100644 ember/public/avatars/016.jpg delete mode 100644 ember/public/avatars/017.jpg delete mode 100644 ember/public/avatars/018.jpg delete mode 100644 ember/public/avatars/019.jpg delete mode 100644 ember/public/avatars/020.jpg delete mode 100644 ember/testem.json delete mode 100644 ember/tests/.jshintrc delete mode 100644 ember/tests/helpers/resolver.js delete mode 100644 ember/tests/helpers/start-app.js delete mode 100644 ember/tests/index.html delete mode 100644 ember/tests/test-helper.js delete mode 100644 ember/tests/unit/.gitkeep delete mode 100644 src/Flarum/Api/Actions/Base.php delete mode 100644 src/Flarum/Api/Actions/Discussions/Create.php delete mode 100644 src/Flarum/Api/Actions/Discussions/Delete.php delete mode 100644 src/Flarum/Api/Actions/Discussions/Index.php delete mode 100644 src/Flarum/Api/Actions/Discussions/Show.php delete mode 100644 src/Flarum/Api/Actions/Discussions/Update.php delete mode 100644 src/Flarum/Api/Actions/Groups/Index.php delete mode 100644 src/Flarum/Api/Actions/Posts/Create.php delete mode 100644 src/Flarum/Api/Actions/Posts/Delete.php delete mode 100644 src/Flarum/Api/Actions/Posts/Index.php delete mode 100644 src/Flarum/Api/Actions/Posts/Show.php delete mode 100644 src/Flarum/Api/Actions/Posts/Update.php delete mode 100644 src/Flarum/Api/Actions/Users/Create.php delete mode 100644 src/Flarum/Api/Actions/Users/Delete.php delete mode 100644 src/Flarum/Api/Actions/Users/Index.php delete mode 100644 src/Flarum/Api/Actions/Users/Show.php delete mode 100644 src/Flarum/Api/Actions/Users/Update.php delete mode 100644 src/Flarum/Api/ApiServiceProvider.php delete mode 100644 src/Flarum/Api/Serializers/ActivitySerializer.php delete mode 100644 src/Flarum/Api/Serializers/BaseSerializer.php delete mode 100644 src/Flarum/Api/Serializers/DiscussionBasicSerializer.php delete mode 100644 src/Flarum/Api/Serializers/DiscussionSerializer.php delete mode 100644 src/Flarum/Api/Serializers/GroupSerializer.php delete mode 100644 src/Flarum/Api/Serializers/PostBasicSerializer.php delete mode 100644 src/Flarum/Api/Serializers/PostSerializer.php delete mode 100644 src/Flarum/Api/Serializers/UserAdminSerializer.php delete mode 100644 src/Flarum/Api/Serializers/UserBasicSerializer.php delete mode 100644 src/Flarum/Api/Serializers/UserCurrentSerializer.php delete mode 100644 src/Flarum/Api/Serializers/UserSerializer.php delete mode 100644 src/Flarum/Core/Activity/Activity.php delete mode 100644 src/Flarum/Core/CoreServiceProvider.php delete mode 100644 src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommand.php delete mode 100644 src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php delete mode 100644 src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php delete mode 100644 src/Flarum/Core/Discussions/Commands/EditDiscussionCommand.php delete mode 100644 src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php delete mode 100644 src/Flarum/Core/Discussions/Commands/EditDiscussionValidator.php delete mode 100644 src/Flarum/Core/Discussions/Commands/ReadDiscussionCommand.php delete mode 100644 src/Flarum/Core/Discussions/Commands/ReadDiscussionCommandHandler.php delete mode 100644 src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php delete mode 100644 src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php delete mode 100644 src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php delete mode 100644 src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php delete mode 100644 src/Flarum/Core/Discussions/Discussion.php delete mode 100644 src/Flarum/Core/Discussions/DiscussionFinder.php delete mode 100644 src/Flarum/Core/Discussions/DiscussionRepository.php delete mode 100644 src/Flarum/Core/Discussions/DiscussionState.php delete mode 100644 src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php delete mode 100644 src/Flarum/Core/Discussions/Events/DiscussionWasRead.php delete mode 100644 src/Flarum/Core/Discussions/Events/DiscussionWasRenamed.php delete mode 100644 src/Flarum/Core/Discussions/Events/DiscussionWasStarted.php delete mode 100644 src/Flarum/Core/Entity.php delete mode 100644 src/Flarum/Core/Forum.php delete mode 100644 src/Flarum/Core/Groups/Group.php delete mode 100644 src/Flarum/Core/Groups/GroupRepository.php delete mode 100644 src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php delete mode 100644 src/Flarum/Core/Listeners/PostFormatter.php delete mode 100644 src/Flarum/Core/Listeners/TitleChangePostCreator.php delete mode 100644 src/Flarum/Core/Listeners/UserMetadataUpdater.php delete mode 100644 src/Flarum/Core/Permissions/Manager.php delete mode 100644 src/Flarum/Core/Permissions/Permission.php delete mode 100644 src/Flarum/Core/Permissions/PermissionRepository.php delete mode 100644 src/Flarum/Core/Posts/Commands/DeletePostCommand.php delete mode 100644 src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php delete mode 100644 src/Flarum/Core/Posts/Commands/DeletePostValidator.php delete mode 100644 src/Flarum/Core/Posts/Commands/EditPostCommand.php delete mode 100644 src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php delete mode 100644 src/Flarum/Core/Posts/Commands/EditPostValidator.php delete mode 100644 src/Flarum/Core/Posts/Commands/PostReplyCommand.php delete mode 100644 src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php delete mode 100644 src/Flarum/Core/Posts/Commands/PostReplyValidator.php delete mode 100644 src/Flarum/Core/Posts/CommentPost.php delete mode 100644 src/Flarum/Core/Posts/Events/PostWasDeleted.php delete mode 100644 src/Flarum/Core/Posts/Events/PostWasHidden.php delete mode 100644 src/Flarum/Core/Posts/Events/PostWasRestored.php delete mode 100644 src/Flarum/Core/Posts/Events/PostWasRevised.php delete mode 100644 src/Flarum/Core/Posts/Events/ReplyWasPosted.php delete mode 100644 src/Flarum/Core/Posts/Post.php delete mode 100644 src/Flarum/Core/Posts/PostRepository.php delete mode 100644 src/Flarum/Core/Posts/TitleChangePost.php delete mode 100644 src/Flarum/Core/Search/FulltextSearchDriver.php delete mode 100644 src/Flarum/Core/Search/SearchDriverInterface.php delete mode 100644 src/Flarum/Core/Search/SphinxSearchDriver.php delete mode 100644 src/Flarum/Core/Search/Tokenizer.php delete mode 100644 src/Flarum/Core/Search/Tokens/AuthorToken.php delete mode 100644 src/Flarum/Core/Search/Tokens/TokenAbstract.php delete mode 100644 src/Flarum/Core/Search/Tokens/TokenInterface.php delete mode 100644 src/Flarum/Core/Support/CommandValidator.php delete mode 100644 src/Flarum/Core/Support/Exceptions/PermissionDeniedException.php delete mode 100644 src/Flarum/Core/Support/Exceptions/ValidationFailureException.php delete mode 100644 src/Flarum/Core/Support/Extensions/Extension.php delete mode 100644 src/Flarum/Core/Support/Extensions/Manager.php delete mode 100644 src/Flarum/Core/Support/Seeders/ConfigTableSeeder.php delete mode 100644 src/Flarum/Core/Support/Seeders/DatabaseSeeder.php delete mode 100644 src/Flarum/Core/Support/Seeders/DiscussionTableSeeder.php delete mode 100644 src/Flarum/Core/Support/Seeders/UserTableSeeder.php delete mode 100644 src/Flarum/Core/Users/Commands/DeleteUserCommand.php delete mode 100644 src/Flarum/Core/Users/Commands/DeleteUserCommandHandler.php delete mode 100644 src/Flarum/Core/Users/Commands/DeleteUserValidator.php delete mode 100644 src/Flarum/Core/Users/Commands/EditUserCommand.php delete mode 100644 src/Flarum/Core/Users/Commands/EditUserCommandHandler.php delete mode 100644 src/Flarum/Core/Users/Commands/EditUserValidator.php delete mode 100644 src/Flarum/Core/Users/Commands/RegisterUserCommand.php delete mode 100644 src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php delete mode 100644 src/Flarum/Core/Users/Commands/RegisterUserValidator.php delete mode 100644 src/Flarum/Core/Users/Events/EmailWasChanged.php delete mode 100644 src/Flarum/Core/Users/Events/PasswordWasChanged.php delete mode 100644 src/Flarum/Core/Users/Events/UserWasDeleted.php delete mode 100644 src/Flarum/Core/Users/Events/UserWasRegistered.php delete mode 100644 src/Flarum/Core/Users/Events/UserWasRenamed.php delete mode 100644 src/Flarum/Core/Users/Guest.php delete mode 100644 src/Flarum/Core/Users/User.php delete mode 100644 src/Flarum/Core/Users/UserFinder.php delete mode 100644 src/Flarum/Core/Users/UserRepository.php delete mode 100644 src/Flarum/Core/Users/UsernameValidator.php delete mode 100644 src/Flarum/Web/AssetManager.php delete mode 100644 src/Flarum/Web/WebServiceProvider.php delete mode 100644 src/config/.gitkeep delete mode 100644 src/config/config.php delete mode 100644 src/lang/.gitkeep delete mode 100644 src/lang/en/reminders.php delete mode 100644 src/lang/en/validation.php delete mode 100644 src/lang/zh_CN/reminders.php delete mode 100644 src/lang/zh_CN/validation.php delete mode 100644 src/lang/zh_TW/reminders.php delete mode 100644 src/lang/zh_TW/validation.php delete mode 100644 src/migrations/.gitkeep delete mode 100644 src/migrations/2014_01_14_231259_create_config_table.php delete mode 100644 src/migrations/2014_01_14_231321_create_discussions_table.php delete mode 100644 src/migrations/2014_01_14_231334_create_groups_table.php delete mode 100644 src/migrations/2014_01_14_231343_create_permissions_table.php delete mode 100644 src/migrations/2014_01_14_231350_create_posts_table.php delete mode 100644 src/migrations/2014_01_14_231357_create_sessions_table.php delete mode 100644 src/migrations/2014_01_14_231404_create_users_table.php delete mode 100644 src/migrations/2014_01_14_231455_create_users_discussions_table.php delete mode 100644 src/migrations/2014_01_14_231503_create_users_groups_table.php delete mode 100644 src/migrations/2014_01_19_232631_create_activity_table.php delete mode 100644 src/routes.api.php delete mode 100644 src/routes.php delete mode 100644 src/views/.gitkeep delete mode 100644 src/views/index.blade.php delete mode 100644 tests/.gitkeep delete mode 100644 tests/_bootstrap.php delete mode 100644 tests/_data/dump.sql delete mode 100644 tests/_support/AcceptanceHelper.php delete mode 100644 tests/_support/FunctionalHelper.php delete mode 100644 tests/_support/UnitHelper.php delete mode 100644 tests/acceptance.suite.yml delete mode 100644 tests/acceptance/AcceptanceTester.php delete mode 100644 tests/acceptance/_bootstrap.php delete mode 100644 tests/functional.suite.yml delete mode 100644 tests/functional/FunctionalTester.php delete mode 100644 tests/functional/_bootstrap.php delete mode 100644 tests/unit.suite.yml delete mode 100644 tests/unit/UnitTester.php delete mode 100644 tests/unit/_bootstrap.php diff --git a/.gitignore b/.gitignore index 64d8b8d..10e079a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -/vendor -composer.phar .DS_Store Thumbs.db -public/* -tests/_output/* +images/Thumbs.db diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0a1c1cb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - -before_script: - - curl -s http://getcomposer.org/installer | php - - php composer.phar install --dev - -script: phpunit \ No newline at end of file diff --git a/codeception.yml b/codeception.yml deleted file mode 100644 index 306e739..0000000 --- a/codeception.yml +++ /dev/null @@ -1,17 +0,0 @@ -actor: Tester -paths: - tests: tests - log: tests/_output - data: tests/_data - helpers: tests/_support -settings: - bootstrap: _bootstrap.php - colors: true - memory_limit: 1024M -modules: - config: - Db: - dsn: '' - user: '' - password: '' - dump: tests/_data/dump.sql diff --git a/composer.json b/composer.json deleted file mode 100644 index 60d34a3..0000000 --- a/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "flarum/core", - "description": "", - "authors": [ - { - "name": "Toby Zerner", - "email": "toby@flarum.org" - } - ], - "require": { - "php": ">=5.4.0", - "illuminate/support": "4.2.*", - "laracasts/commander": "1.1.*", - "tobscure/json-api": "dev-master", - "tobscure/permissible": "dev-master" - }, - "require-dev": { - "fzaninotto/faker": "1.4.0", - "codeception/codeception": "~2.0.0" - }, - "autoload": { - "classmap": [ - "src/migrations" - ], - "psr-4": { - "Flarum\\Core\\": "src/Flarum/Core", - "Flarum\\Api\\": "src/Flarum/Api", - "Flarum\\Web\\": "src/Flarum/Web" - } - }, - "minimum-stability": "dev" -} diff --git a/composer.lock b/composer.lock deleted file mode 100644 index aff38d4..0000000 --- a/composer.lock +++ /dev/null @@ -1,1963 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "8625c653b9f1cea84c409f2da632a32a", - "packages": [ - { - "name": "illuminate/container", - "version": "4.2.x-dev", - "target-dir": "Illuminate/Container", - "source": { - "type": "git", - "url": "https://github.com/illuminate/container.git", - "reference": "8db091c1b4e503ef8dcd4586d5c63e3997bc4e89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/8db091c1b4e503ef8dcd4586d5c63e3997bc4e89", - "reference": "8db091c1b4e503ef8dcd4586d5c63e3997bc4e89", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-0": { - "Illuminate\\Container": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "time": "2014-12-17 20:39:51" - }, - { - "name": "illuminate/database", - "version": "4.2.x-dev", - "target-dir": "Illuminate/Database", - "source": { - "type": "git", - "url": "https://github.com/illuminate/database.git", - "reference": "5d07587a51b13224cdda6417fbc25e9c13d276c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/5d07587a51b13224cdda6417fbc25e9c13d276c9", - "reference": "5d07587a51b13224cdda6417fbc25e9c13d276c9", - "shasum": "" - }, - "require": { - "illuminate/container": "4.2.*", - "illuminate/events": "4.2.*", - "illuminate/support": "4.2.*", - "nesbot/carbon": "~1.0", - "php": ">=5.4.0" - }, - "require-dev": { - "illuminate/cache": "4.2.*", - "illuminate/console": "4.2.*", - "illuminate/filesystem": "4.2.*", - "illuminate/pagination": "4.2.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-0": { - "Illuminate\\Database": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "keywords": [ - "database", - "laravel", - "orm", - "sql" - ], - "time": "2014-12-22 20:55:49" - }, - { - "name": "illuminate/events", - "version": "4.2.x-dev", - "target-dir": "Illuminate/Events", - "source": { - "type": "git", - "url": "https://github.com/illuminate/events.git", - "reference": "a8471d3f6c3e87c50e25e18f13908fffaef159df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/events/zipball/a8471d3f6c3e87c50e25e18f13908fffaef159df", - "reference": "a8471d3f6c3e87c50e25e18f13908fffaef159df", - "shasum": "" - }, - "require": { - "illuminate/container": "4.2.*", - "illuminate/support": "4.2.*", - "php": ">=5.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-0": { - "Illuminate\\Events": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "time": "2014-10-02 19:49:50" - }, - { - "name": "illuminate/support", - "version": "4.2.x-dev", - "target-dir": "Illuminate/Support", - "source": { - "type": "git", - "url": "https://github.com/illuminate/support.git", - "reference": "015f51f4d8cb3d9a29e7fe7932843acf15232941" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/015f51f4d8cb3d9a29e7fe7932843acf15232941", - "reference": "015f51f4d8cb3d9a29e7fe7932843acf15232941", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "jeremeamia/superclosure": "~1.0.1", - "patchwork/utf8": "1.1.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-0": { - "Illuminate\\Support": "" - }, - "files": [ - "Illuminate/Support/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "time": "2014-12-19 18:02:33" - }, - { - "name": "laracasts/commander", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/laracasts/Commander.git", - "reference": "89b47ceb08e26d6beae35781010522e7110aded3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laracasts/Commander/zipball/89b47ceb08e26d6beae35781010522e7110aded3", - "reference": "89b47ceb08e26d6beae35781010522e7110aded3", - "shasum": "" - }, - "require": { - "illuminate/support": "~4.0", - "php": ">=5.4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Laracasts\\Commander": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jeffrey Way", - "email": "jeffrey@laracasts.com" - } - ], - "description": "Commands and domain events in Laravel", - "time": "2014-07-03 13:05:27" - }, - { - "name": "nesbot/carbon", - "version": "1.13.0", - "source": { - "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "5cb6e71055f7b0b57956b73d324cc4de31278f42" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/5cb6e71055f7b0b57956b73d324cc4de31278f42", - "reference": "5cb6e71055f7b0b57956b73d324cc4de31278f42", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Carbon": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Nesbitt", - "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" - } - ], - "description": "A simple API extension for DateTime.", - "homepage": "https://github.com/briannesbitt/Carbon", - "keywords": [ - "date", - "datetime", - "time" - ], - "time": "2014-09-26 02:52:02" - }, - { - "name": "tobscure/json-api", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/tobscure/json-api.git", - "reference": "9a87425ad1f5e95506787df194a5d3e7a7d27224" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tobscure/json-api/zipball/9a87425ad1f5e95506787df194a5d3e7a7d27224", - "reference": "9a87425ad1f5e95506787df194a5d3e7a7d27224", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "squizlabs/php_codesniffer": "1.5.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Tobscure\\JsonApi\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "authors": [ - { - "name": "Toby Zerner", - "email": "toby.zerner@gmail.com" - } - ], - "description": "JSON-API responses in PHP.", - "time": "2014-08-23 10:19:16" - }, - { - "name": "tobscure/permissible", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/tobscure/permissible.git", - "reference": "223a62784672981a45170d2192c9786971b3abee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tobscure/permissible/zipball/223a62784672981a45170d2192c9786971b3abee", - "reference": "223a62784672981a45170d2192c9786971b3abee", - "shasum": "" - }, - "require": { - "illuminate/database": "4.2.*", - "php": ">=5.4.0" - }, - "require-dev": { - "squizlabs/php_codesniffer": "1.5.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Tobscure\\Permissible\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "authors": [ - { - "name": "Toby Zerner", - "email": "toby.zerner@gmail.com" - } - ], - "description": "Powerful, flexible, relational permissions using Eloquent.", - "time": "2014-07-26 05:00:03" - } - ], - "packages-dev": [ - { - "name": "codeception/codeception", - "version": "2.0.x-dev", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Codeception.git", - "reference": "24e6927a1ae17a432e7acbd6048d1e4898bab6ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/24e6927a1ae17a432e7acbd6048d1e4898bab6ae", - "reference": "24e6927a1ae17a432e7acbd6048d1e4898bab6ae", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "facebook/webdriver": "~0.4", - "guzzlehttp/guzzle": "~4.0|~5.0", - "php": ">=5.4.0", - "phpunit/phpunit": "~4.0", - "symfony/browser-kit": "~2.4", - "symfony/console": "~2.4", - "symfony/css-selector": "~2.4", - "symfony/dom-crawler": "~2.4,!=2.4.5", - "symfony/event-dispatcher": "~2.4", - "symfony/finder": "~2.4", - "symfony/yaml": "~2.4" - }, - "require-dev": { - "codeception/specify": "~0.3", - "codegyre/robo-ci": "@dev", - "facebook/php-sdk": "~3.2", - "flow/jsonpath": "~0.1", - "monolog/monolog": "~1.8", - "pda/pheanstalk": "~2.0", - "phpseclib/phpseclib": "~0.3.6", - "videlalvaro/php-amqplib": "~2.4" - }, - "suggest": { - "codeception/phpbuiltinserver": "Extension to start and stop PHP built-in web server for your tests", - "codeception/specify": "BDD-style code blocks", - "codeception/verify": "BDD-style assertions", - "monolog/monolog": "Log test steps", - "phpseclib/phpseclib": "Extension required to use the SFTP option in the FTP Module." - }, - "bin": [ - "codecept" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "psr-0": { - "Codeception": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert@mail.ua", - "homepage": "http://codegyre.com" - } - ], - "description": "BDD-style testing framework", - "homepage": "http://codeception.com/", - "keywords": [ - "BDD", - "TDD", - "acceptance testing", - "functional testing", - "unit testing" - ], - "time": "2014-12-23 02:04:40" - }, - { - "name": "doctrine/instantiator", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "40efa2d8c9e2948b29fdf5d782babaacb08d0c86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/40efa2d8c9e2948b29fdf5d782babaacb08d0c86", - "reference": "40efa2d8c9e2948b29fdf5d782babaacb08d0c86", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2014-12-12 16:47:07" - }, - { - "name": "facebook/webdriver", - "version": "v0.5.1", - "source": { - "type": "git", - "url": "https://github.com/facebook/php-webdriver.git", - "reference": "bbcb697efb394d17bd9ec3d467e7da847cde4509" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bbcb697efb394d17bd9ec3d467e7da847cde4509", - "reference": "bbcb697efb394d17bd9ec3d467e7da847cde4509", - "shasum": "" - }, - "require": { - "php": ">=5.3.19" - }, - "require-dev": { - "phpdocumentor/phpdocumentor": "2.*", - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "autoload": { - "classmap": [ - "lib/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "A php client for WebDriver", - "homepage": "https://github.com/facebook/php-webdriver", - "keywords": [ - "facebook", - "php", - "selenium", - "webdriver" - ], - "time": "2014-11-05 20:53:09" - }, - { - "name": "fzaninotto/faker", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "010c7efedd88bf31141a02719f51fb44c732d5a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/010c7efedd88bf31141a02719f51fb44c732d5a0", - "reference": "010c7efedd88bf31141a02719f51fb44c732d5a0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "type": "library", - "extra": { - "branch-alias": [] - }, - "autoload": { - "psr-0": { - "Faker": "src/", - "Faker\\PHPUnit": "test/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2014-06-04 14:43:02" - }, - { - "name": "guzzlehttp/guzzle", - "version": "5.1.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "f1085bb4e023766a66b7b051914ec73bdf7202b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f1085bb4e023766a66b7b051914ec73bdf7202b5", - "reference": "f1085bb4e023766a66b7b051914ec73bdf7202b5", - "shasum": "" - }, - "require": { - "guzzlehttp/ringphp": "~1.0", - "php": ">=5.4.0" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0", - "psr/log": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "time": "2014-12-19 20:27:15" - }, - { - "name": "guzzlehttp/ringphp", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/guzzle/RingPHP.git", - "reference": "a903f51b692427318bc813217c0e6505287e79a4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/a903f51b692427318bc813217c0e6505287e79a4", - "reference": "a903f51b692427318bc813217c0e6505287e79a4", - "shasum": "" - }, - "require": { - "guzzlehttp/streams": "~3.0", - "php": ">=5.4.0", - "react/promise": "~2.0" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Ring\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "time": "2014-12-11 05:50:32" - }, - { - "name": "guzzlehttp/streams", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/guzzle/streams.git", - "reference": "48be63a56ea8a98ec620eb59ef76bcab77feba55" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/streams/zipball/48be63a56ea8a98ec620eb59ef76bcab77feba55", - "reference": "48be63a56ea8a98ec620eb59ef76bcab77feba55", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Provides a simple abstraction over streams of data", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "stream" - ], - "time": "2014-10-12 19:45:36" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "fd0ac2007401505fb596fdfb859ec4e103d69e55" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/fd0ac2007401505fb596fdfb859ec4e103d69e55", - "reference": "fd0ac2007401505fb596fdfb859ec4e103d69e55", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2014-09-02 14:26:20" - }, - { - "name": "phpspec/prophecy", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9ca52329bcdd1500de24427542577ebf3fc2f1c9", - "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "~1.0,>=1.0.2", - "phpdocumentor/reflection-docblock": "~2.0" - }, - "require-dev": { - "phpspec/phpspec": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "http://phpspec.org", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2014-11-17 16:23:49" - }, - { - "name": "phpunit/php-code-coverage", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "de8096a74c459eb7279b6410dcf09e26e2cd88aa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/de8096a74c459eb7279b6410dcf09e26e2cd88aa", - "reference": "de8096a74c459eb7279b6410dcf09e26e2cd88aa", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "~1.0", - "sebastian/version": "~1.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "dev-master" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2014-12-04 09:42:06" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.3.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "File/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2013-10-10 15:34:57" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "Text/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2014-01-30 17:20:04" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2013-08-02 07:42:54" - }, - { - "name": "phpunit/php-token-stream", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "f8d5d08c56de5cfd592b3340424a81733259a876" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/f8d5d08c56de5cfd592b3340424a81733259a876", - "reference": "f8d5d08c56de5cfd592b3340424a81733259a876", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2014-08-31 06:12:13" - }, - { - "name": "phpunit/phpunit", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2a1f8746a5bc0ff5d0cd24b356123fcf0fd1fb64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2a1f8746a5bc0ff5d0cd24b356123fcf0fd1fb64", - "reference": "2a1f8746a5bc0ff5d0cd24b356123fcf0fd1fb64", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "~1.3.1", - "phpunit/php-code-coverage": "~3.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "~1.0", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.2", - "sebastian/exporter": "~1.0", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.6.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2014-12-22 16:18:11" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "96c5b81f9842f38fe6c73ad0020306cc4862a9e3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/96c5b81f9842f38fe6c73ad0020306cc4862a9e3", - "reference": "96c5b81f9842f38fe6c73ad0020306cc4862a9e3", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "~1.0,>=1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "4.4.*@dev" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2014-10-04 10:04:20" - }, - { - "name": "react/promise", - "version": "v2.1.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "937b04f1b0ee8f6d180e75a0830aac778ca4bcd6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/937b04f1b0ee8f6d180e75a0830aac778ca4bcd6", - "reference": "937b04f1b0ee8f6d180e75a0830aac778ca4bcd6", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@googlemail.com" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "time": "2014-10-15 20:05:57" - }, - { - "name": "sebastian/comparator", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "4c7f82f0599413ed5521e464071ee8460c96ce89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/4c7f82f0599413ed5521e464071ee8460c96ce89", - "reference": "4c7f82f0599413ed5521e464071ee8460c96ce89", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.1", - "sebastian/exporter": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2014-12-04 15:00:21" - }, - { - "name": "sebastian/diff", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "f38057b48125c2b421361da224a8aa800d70aeca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/f38057b48125c2b421361da224a8aa800d70aeca", - "reference": "f38057b48125c2b421361da224a8aa800d70aeca", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "http://www.github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2014-11-22 06:25:40" - }, - { - "name": "sebastian/environment", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "a4420fdad8ef5e525cdeb66adbb5ed9e873e6128" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a4420fdad8ef5e525cdeb66adbb5ed9e873e6128", - "reference": "a4420fdad8ef5e525cdeb66adbb5ed9e873e6128", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2014-12-15 10:20:32" - }, - { - "name": "sebastian/exporter", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", - "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2014-09-10 00:51:36" - }, - { - "name": "sebastian/global-state", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "231d48620efde984fd077ee92916099a3ece9a59" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/231d48620efde984fd077ee92916099a3ece9a59", - "reference": "231d48620efde984fd077ee92916099a3ece9a59", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2014-10-06 09:49:11" - }, - { - "name": "sebastian/version", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", - "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2014-03-07 15:35:33" - }, - { - "name": "symfony/browser-kit", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/BrowserKit", - "source": { - "type": "git", - "url": "https://github.com/symfony/BrowserKit.git", - "reference": "35a5c4dc5d94da28668173cf5d3d193975086ea1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/35a5c4dc5d94da28668173cf5d3d193975086ea1", - "reference": "35a5c4dc5d94da28668173cf5d3d193975086ea1", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/dom-crawler": "~2.0,>=2.0.5" - }, - "require-dev": { - "symfony/css-selector": "~2.0,>=2.0.5", - "symfony/process": "~2.0,>=2.0.5" - }, - "suggest": { - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\BrowserKit\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony BrowserKit Component", - "homepage": "http://symfony.com", - "time": "2014-12-16 20:28:45" - }, - { - "name": "symfony/console", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/Console", - "source": { - "type": "git", - "url": "https://github.com/symfony/Console.git", - "reference": "77966eddd2982c312e53a768b9e0eec210698081" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/77966eddd2982c312e53a768b9e0eec210698081", - "reference": "77966eddd2982c312e53a768b9e0eec210698081", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/process": "~2.1" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Console\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Console Component", - "homepage": "http://symfony.com", - "time": "2014-12-23 11:23:33" - }, - { - "name": "symfony/css-selector", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/CssSelector", - "source": { - "type": "git", - "url": "https://github.com/symfony/CssSelector.git", - "reference": "b03a0c013b30dc28bed02f7f2cb19298e915061f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/CssSelector/zipball/b03a0c013b30dc28bed02f7f2cb19298e915061f", - "reference": "b03a0c013b30dc28bed02f7f2cb19298e915061f", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\CssSelector\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony CssSelector Component", - "homepage": "http://symfony.com", - "time": "2014-12-22 16:45:18" - }, - { - "name": "symfony/dom-crawler", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/DomCrawler", - "source": { - "type": "git", - "url": "https://github.com/symfony/DomCrawler.git", - "reference": "3a04196caa24c1be9cb4f79aecf961f8c6aad1e7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/3a04196caa24c1be9cb4f79aecf961f8c6aad1e7", - "reference": "3a04196caa24c1be9cb4f79aecf961f8c6aad1e7", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "symfony/css-selector": "~2.3" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\DomCrawler\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "http://symfony.com", - "time": "2014-12-22 16:45:18" - }, - { - "name": "symfony/event-dispatcher", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/EventDispatcher", - "source": { - "type": "git", - "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "b27ae60d9bf9b4768b3a6f155a4309f2cd991845" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/b27ae60d9bf9b4768b3a6f155a4309f2cd991845", - "reference": "b27ae60d9bf9b4768b3a6f155a4309f2cd991845", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.6", - "symfony/expression-language": "~2.6", - "symfony/stopwatch": "~2.3" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "http://symfony.com", - "time": "2014-12-22 16:45:18" - }, - { - "name": "symfony/finder", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/Finder", - "source": { - "type": "git", - "url": "https://github.com/symfony/Finder.git", - "reference": "3163e335375f3433569996fd68c89887e4a82d29" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/3163e335375f3433569996fd68c89887e4a82d29", - "reference": "3163e335375f3433569996fd68c89887e4a82d29", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Finder\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Finder Component", - "homepage": "http://symfony.com", - "time": "2014-12-22 16:45:18" - }, - { - "name": "symfony/yaml", - "version": "2.7.x-dev", - "target-dir": "Symfony/Component/Yaml", - "source": { - "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "4acdf4ff8dedd856ee5fd1e4e0e16f7f42dca463" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/4acdf4ff8dedd856ee5fd1e4e0e16f7f42dca463", - "reference": "4acdf4ff8dedd856ee5fd1e4e0e16f7f42dca463", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Yaml\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Yaml Component", - "homepage": "http://symfony.com", - "time": "2014-12-22 16:45:18" - } - ], - "aliases": [], - "minimum-stability": "dev", - "stability-flags": { - "tobscure/json-api": 20, - "tobscure/permissible": 20 - }, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=5.4.0" - }, - "platform-dev": [] -} diff --git a/ember/.bowerrc b/ember/.bowerrc deleted file mode 100644 index 959e169..0000000 --- a/ember/.bowerrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "directory": "bower_components", - "analytics": false -} diff --git a/ember/.editorconfig b/ember/.editorconfig deleted file mode 100644 index 5d5dea4..0000000 --- a/ember/.editorconfig +++ /dev/null @@ -1,33 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 2 - -[*.js] -indent_style = space -indent_size = 2 - -[*.hbs] -indent_style = space -indent_size = 2 - -[*.css] -indent_style = space -indent_size = 2 - -[*.html] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false diff --git a/ember/.ember-cli b/ember/.ember-cli deleted file mode 100644 index ee64cfe..0000000 --- a/ember/.ember-cli +++ /dev/null @@ -1,9 +0,0 @@ -{ - /** - Ember CLI sends analytics information by default. The data is completely - anonymous, but there are times when you might want to disable this behavior. - - Setting `disableAnalytics` to true will prevent any data from being sent. - */ - "disableAnalytics": false -} diff --git a/ember/.gitignore b/ember/.gitignore deleted file mode 100644 index 86fceae..0000000 --- a/ember/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp - -# dependencies -/node_modules -/bower_components - -# misc -/.sass-cache -/connect.lock -/coverage/* -/libpeerconnection.log -npm-debug.log -testem.log diff --git a/ember/.jshintrc b/ember/.jshintrc deleted file mode 100644 index 08096ef..0000000 --- a/ember/.jshintrc +++ /dev/null @@ -1,32 +0,0 @@ -{ - "predef": [ - "document", - "window", - "-Promise" - ], - "browser": true, - "boss": true, - "curly": true, - "debug": false, - "devel": true, - "eqeqeq": true, - "evil": true, - "forin": false, - "immed": false, - "laxbreak": false, - "newcap": true, - "noarg": true, - "noempty": false, - "nonew": false, - "nomen": false, - "onevar": false, - "plusplus": false, - "regexp": false, - "undef": true, - "sub": true, - "strict": false, - "white": false, - "eqnull": true, - "esnext": true, - "unused": true -} diff --git a/ember/.travis.yml b/ember/.travis.yml deleted file mode 100644 index cf23938..0000000 --- a/ember/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -language: node_js - -sudo: false - -cache: - directories: - - node_modules - -before_install: - - "npm config set spin false" - - "npm install -g npm@^2" - -install: - - npm install -g bower - - npm install - - bower install - -script: - - npm test diff --git a/ember/Brocfile.js b/ember/Brocfile.js deleted file mode 100644 index 9f03d0d..0000000 --- a/ember/Brocfile.js +++ /dev/null @@ -1,18 +0,0 @@ -/* global require, module */ - -var EmberApp = require('ember-cli/lib/broccoli/ember-app'); - -var app = new EmberApp(); - -app.import('bower_components/bootstrap/dist/js/bootstrap.js'); -app.import('bower_components/spin.js/spin.js'); -app.import('bower_components/spin.js/jquery.spin.js'); -app.import('bower_components/moment/moment.js'); - -app.import('bower_components/font-awesome/fonts/fontawesome-webfont.eot'); -app.import('bower_components/font-awesome/fonts/fontawesome-webfont.svg'); -app.import('bower_components/font-awesome/fonts/fontawesome-webfont.ttf'); -app.import('bower_components/font-awesome/fonts/fontawesome-webfont.woff'); -app.import('bower_components/font-awesome/fonts/FontAwesome.otf'); - -module.exports = app.toTree(); diff --git a/ember/app/adapters/application.js b/ember/app/adapters/application.js deleted file mode 100644 index da2a2d7..0000000 --- a/ember/app/adapters/application.js +++ /dev/null @@ -1,37 +0,0 @@ -import JsonApiAdapter from 'ember-json-api/json-api-adapter'; -export default JsonApiAdapter.extend({ - host: '/api', - - findQuery: function(store, type, query) { - var ids = null; - if (query.ids) { - ids = query.ids.join(','); - delete query.ids; - } - return this.ajax(this.buildURL(type.typeKey, ids), 'GET', {data: query}); - }, -}); - -// export default DS.JsonApiAdapter.extend({ -// host: '/api', - -// // xhr: [], - -// // ajax: function(url, type, hash) { -// // var adapter = this; - -// // return new Ember.RSVP.Promise(function(resolve, reject) { -// // hash = adapter.ajaxOptions(url, type, hash); - -// // hash.success = function(json) { -// // Ember.run(null, resolve, json); -// // }; - -// // hash.error = function(jqXHR, textStatus, errorThrown) { -// // Ember.run(null, reject, adapter.ajaxError(jqXHR)); -// // }; - -// // adapter.xhr.push(Ember.$.ajax(hash)); -// // }, "DS: RestAdapter#ajax " + type + " to " + url); -// // }, -// }); diff --git a/ember/app/app.js b/ember/app/app.js deleted file mode 100644 index 601bc88..0000000 --- a/ember/app/app.js +++ /dev/null @@ -1,77 +0,0 @@ -import Ember from 'ember'; -import Resolver from 'ember/resolver'; -import loadInitializers from 'ember/load-initializers'; -import config from './config/environment'; - -Ember.MODEL_FACTORY_INJECTIONS = true; - -var App = Ember.Application.extend({ - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver: Resolver -}); - -loadInitializers(App, config.modulePrefix); - -//----------------------------------------- -// TODO: Move all this to an initializer - -/* -import User from 'flarum/models/user'; - -// Authentication - -import BaseAuthenticator from 'simple-auth/authenticators/base'; - -var FlarumAuthenticator = BaseAuthenticator.extend({ - restore: function(data) { - // return Ember.RSVP.Promise.resolve(data); - }, - authenticate: function(credentials) { - return new Ember.RSVP.Promise(function(resolve, reject) { - Ember.$.ajax({ - url: 'http://localhost/public/Flarum/flarum/public/api/auth', - type: 'POST', - data: { type: 'password', identification: credentials.identification, password: credentials.password } - }).then(function(response) { - resolve({ token: response.token, userId: response.user.id }); - }, function(xhr, status, error) { - reject(xhr.responseText); - }); - }); - }, - // invalidate: function() { - // return Ember.RSVP.Promise.resolve(); - // } -}); - -import BaseAuthorizer from 'simple-auth/authorizers/base'; - -var FlarumAuthorizer = BaseAuthorizer.extend({ - -}); - -App.initializer({ - name: 'authentication', - initialize: function(container, application) { - container.register('authenticator:flarum', FlarumAuthenticator); - container.register('authorizer:flarum', FlarumAuthorizer); - - // customize the session so that it allows access to the account object - Ember.SimpleAuth.Session.reopen({ - user: function() { - var userId = this.get('userId'); - if (!userId) return; - return container.lookup('store:main').find('user', userId); - }.property('userId') - }); - - Ember.SimpleAuth.setup(container, application, { - authorizerFactory: 'authorizer:flarum', - routeAfterAuthentication: 'discussions' - }); - } -}); -*/ - -export default App; diff --git a/ember/app/components/.gitkeep b/ember/app/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/components/button-item.js b/ember/app/components/button-item.js deleted file mode 100644 index 687ac85..0000000 --- a/ember/app/components/button-item.js +++ /dev/null @@ -1,18 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - title: '', - icon: '', - class: '', - action: null, - - tagName: 'a', - classNames: ['btn'], - classNameBindings: ['class', 'disabled'], - - layout: Ember.Handlebars.compile('{{#if view.icon}}{{fa-icon view.icon class="fa-fw"}} {{/if}}{{view.title}}'), - - click: function() { - this.action(); - } -}); diff --git a/ember/app/components/item-collection.js b/ember/app/components/item-collection.js deleted file mode 100644 index a9a01a5..0000000 --- a/ember/app/components/item-collection.js +++ /dev/null @@ -1,6 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - tagName: 'ul', - layoutName: 'components/item-collection', -}); diff --git a/ember/app/components/loading-indicator.js b/ember/app/components/loading-indicator.js deleted file mode 100644 index 6da8878..0000000 --- a/ember/app/components/loading-indicator.js +++ /dev/null @@ -1,13 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - - classNames: ['loading'], - - layout: Ember.Handlebars.compile(' '), - - didInsertElement: function() { - this.$().spin(this.get('size')); - } - -}); diff --git a/ember/app/components/menu-item-separator.js b/ember/app/components/menu-item-separator.js deleted file mode 100644 index ec7c493..0000000 --- a/ember/app/components/menu-item-separator.js +++ /dev/null @@ -1,5 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - liClass: 'divider' -}); diff --git a/ember/app/components/menu-item.js b/ember/app/components/menu-item.js deleted file mode 100644 index b80d608..0000000 --- a/ember/app/components/menu-item.js +++ /dev/null @@ -1,31 +0,0 @@ -import Ember from 'ember'; - -var MenuItem = Ember.Component.extend({ - title: '', - icon: '', - className: '', - action: null, - divider: false, - - tagName: 'a', - attributeBindings: ['href'], - classNameBindings: ['className'], - href: '#', - layout: Ember.Handlebars.compile('{{#if icon}}{{fa-icon icon class="fa-fw"}} {{/if}}{{title}}'), - - click: function(e) { - e.preventDefault(); - // this.sendAction('action'); - this.get('action')(); - } -}); - -MenuItem.reopenClass({ - separator: function() { - return this.create({ - divider: true - }); - } -}) - -export default MenuItem; diff --git a/ember/app/components/menu-list.js b/ember/app/components/menu-list.js deleted file mode 100644 index c059697..0000000 --- a/ember/app/components/menu-list.js +++ /dev/null @@ -1,6 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - tagName: 'ul', - layoutName: 'components/menu-list', -}); diff --git a/ember/app/components/menu-split.js b/ember/app/components/menu-split.js deleted file mode 100644 index cddbcf4..0000000 --- a/ember/app/components/menu-split.js +++ /dev/null @@ -1,15 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - items: null, // NamedContainerView/Menu - layoutName: 'components/menu-split', - show: 1, - - visibleItems: function() { - return this.get('items').slice(0, this.get('show')); - }.property('items'), - - hiddenItems: function() { - return this.get('items').slice(this.get('show')); - }.property('items'), -}); diff --git a/ember/app/components/nav-item.js b/ember/app/components/nav-item.js deleted file mode 100644 index 6528dff..0000000 --- a/ember/app/components/nav-item.js +++ /dev/null @@ -1,34 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - icon: '', - title: '', - action: null, - badge: '', - badgeAction: null, - // active: false, - - tagName: 'li', - classNameBindings: ['active'], - active: function() { - return this.get('childViews').anyBy('active'); - }.property('childViews.@each.active'), - - layout: function() { - return Ember.Handlebars.compile('{{badge}}\ - {{#link-to '+this.get('linkTo')+'}}'+this.get('iconTemplate')+'{{title}}{{/link-to}}'); - }.property('linkTo', 'iconTemplate'), - - iconTemplate: function() { - return '{{fa-icon icon}}'; - }.property(), - - actions: { - main: function() { - this.get('action')(); - }, - badge: function() { - this.get('badgeAction')(); - } - } -}); diff --git a/ember/app/components/notification-message.js b/ember/app/components/notification-message.js deleted file mode 100644 index b9fd204..0000000 --- a/ember/app/components/notification-message.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - - close: function() { - this.sendAction('closeAction'); - } - -}); diff --git a/ember/app/components/search-input.js b/ember/app/components/search-input.js deleted file mode 100644 index dea3d19..0000000 --- a/ember/app/components/search-input.js +++ /dev/null @@ -1,38 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - classNames: ['search-input'], - classNameBindings: ['active', 'value:clearable'], - - didInsertElement: function() { - var self = this; - this.$().find('input').on('keydown', function(e) { - if (e.which == 27) { - self.clear(); - } - }); - this.$().find('.clear').on('mousedown', function(e) { - e.preventDefault(); - }).on('click', function(e) { - e.preventDefault(); - self.clear(); - }) - }, - - clear: function() { - this.set('value', ''); - this.sendAction('action', ''); - this.$().find('input').focus(); - }, - - willDestroyElement: function() { - this.$().find('input').off('keydown'); - this.$().find('.clear').off('mousedown click'); - }, - - actions: { - search: function() { - this.sendAction('action', this.get('value')); - } - } -}); diff --git a/ember/app/components/select-input.js b/ember/app/components/select-input.js deleted file mode 100644 index d01c5e7..0000000 --- a/ember/app/components/select-input.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - - tagName: 'span', - classNames: ['select'], - layout: Ember.Handlebars.compile('{{view Ember.Select content=view.content optionValuePath=view.optionValuePath optionLabelPath=view.optionLabelPath value=view.value}} {{fa-icon "sort"}}') - -}); diff --git a/ember/app/controllers/.gitkeep b/ember/app/controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/controllers/application.js b/ember/app/controllers/application.js deleted file mode 100644 index c5b925c..0000000 --- a/ember/app/controllers/application.js +++ /dev/null @@ -1,48 +0,0 @@ -import Ember from 'ember'; - -// import NotificationMessage from '../models/notification-message'; - -export default Ember.Controller.extend({ - - needs: ['discussions'], - - // The title of the forum. - // TODO: Preload this value in the index.html payload from Laravel config. - forumTitle: 'Ninetech Support Forum', - // forumTitle: ' TV Addicts', - // forumTitle: '', - // forumTitle: '  Med Students Forum', - pageTitle: '', - documentTitle: function() { - return this.get('pageTitle') || this.get('forumTitle'); - }.property('pageTitle', 'forumTitle'), - - _updateTitle: function() { - var parts = [this.get('forumTitle')]; - var pageTitle = this.get('pageTitle'); - if (pageTitle) parts.unshift(pageTitle); - document.title = parts.join(' - '); - }.observes('pageTitle', 'forumTitle'), - - searchQuery: '', - searchActive: false, - - showDiscussionStream: false, - - // notificationMessage: NotificationMessage.create({text: 'Sorry, you do not have permission to do that!', class: 'message-warning'}), // currently displaying notification message object - - currentUser: null, - - actions: { - - hideMessage: function() { - this.set('notificationMessage', null); - }, - - search: function(query) { - this.transitionToRoute('discussions', {queryParams: {searchQuery: query, sort: query ? 'relevance' : 'recent'}}); - }, - - } - -}); diff --git a/ember/app/controllers/composer.js b/ember/app/controllers/composer.js deleted file mode 100644 index 199a258..0000000 --- a/ember/app/controllers/composer.js +++ /dev/null @@ -1,17 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Controller.extend({ - - needs: ['discussions'], - - showing: false, - - title: 'Replying to Some Discussion Title', - - actions: { - close: function() { - this.set('showing', false); - } - } - -}); diff --git a/ember/app/controllers/discussion.js b/ember/app/controllers/discussion.js deleted file mode 100644 index 79dc86e..0000000 --- a/ember/app/controllers/discussion.js +++ /dev/null @@ -1,103 +0,0 @@ -import Ember from 'ember'; - -import PostStream from '../models/post-stream'; - -export default Ember.ObjectController.extend(Ember.Evented, { - - needs: ['application', 'composer'], - - queryParams: ['start'], - start: '1', - searchQuery: '', - - loaded: false, - postStream: null, - - setup: function(discussion) { - this.set('model', discussion); - - // Set up the post stream object. It needs to know about the discussion - // its representing the posts for, and we also need to inject the Ember - // data store. - var postStream = PostStream.create(); - postStream.set('discussion', discussion); - postStream.set('store', this.get('store')); - this.set('postStream', postStream); - - // Next, we need to load a list of the discussion's post IDs into the - // post stream object. If we don't already have this information, we'll - // need to reload the discussion model. - var promise = discussion.get('posts') ? Ember.RSVP.resolve(discussion) : discussion.reload(); - - // When we know we have the post IDs, we can set up the post stream with - // them. Then we're ready to load some posts! - var controller = this; - promise.then(function(discussion) { - postStream.setup(discussion.get('postIds')); - controller.set('loaded', true); - controller.send('jumpToNumber', controller.get('start')); - }); - }, - - actions: { - - reply: function() { - this.set('controllers.composer.showing', true); - this.set('controllers.composer.title', 'Replying to '+this.get('model.title')+''); - }, - - jumpToNumber: function(number) { - // In some instances, we might be given a placeholder start index - // value. We need to convert this into a numerical value. - switch (number) { - case 'last': - number = this.get('model.lastPostNumber'); - break; - - case 'unread': - number = this.get('model.readNumber') + 1; - break; - } - - number = Math.max(number, 1); - - // Let's start by telling our listeners that we're going to load - // posts near this number. The discussion view will listen and - // consequently scroll down to the appropriate position in the - // discussion. - this.trigger('loadingNumber', number); - - // Now we have to actually make sure the posts around this new start - // position are loaded. We will tell our listeners when they are. - // Again, the view will scroll down to the appropriate post. - var controller = this; - this.get('postStream').loadNearNumber(number).then(function() { - Ember.run.scheduleOnce('afterRender', function() { - controller.trigger('loadedNumber', number); - }); - }); - }, - - jumpToIndex: function(index) { - // Let's start by telling our listeners that we're going to load - // posts at this index. The discussion view will listen and - // consequently scroll down to the appropriate position in the - // discussion. - this.trigger('loadingIndex', index); - - // Now we have to actually make sure the posts around this index are - // loaded. We will tell our listeners when they are. Again, the view - // will scroll down to the appropriate post. - var controller = this; - this.get('postStream').loadNearIndex(index).then(function() { - Ember.run.scheduleOnce('afterRender', function() { - controller.trigger('loadedIndex', index); - }); - }); - }, - - loadRange: function(start, end, backwards) { - this.get('postStream').loadRange(start, end, backwards); - } - } -}); diff --git a/ember/app/controllers/discussions.js b/ember/app/controllers/discussions.js deleted file mode 100644 index 2083083..0000000 --- a/ember/app/controllers/discussions.js +++ /dev/null @@ -1,175 +0,0 @@ -import Ember from 'ember'; - -import DiscussionResult from '../models/discussion-result'; -import PostResult from '../models/post-result'; - -export default Ember.ArrayController.extend(Ember.Evented, { - - needs: ['application', 'composer'], - - paned: false, - paneShowing: false, - paneTimeout: null, - panePinned: false, - - current: null, - - index: function() { - var index = '?'; - var id = this.get('current.id'); - this.get('model').some(function(result, i) { - if (result.get('id') == id) { - index = i + 1; - return true; - } - }); - return index; - }.property('current', 'model.@each'), - - count: function() { - return this.get('model.length'); - }.property('model.@each'), - - previous: function() { - var result = this.get('model').objectAt(this.get('index') - 2); - return result && result.get('content'); - }.property('index'), - - next: function() { - var result = this.get('model').objectAt(this.get('index')); - return result && result.get('content'); - }.property('index'), - - queryParams: ['sort', 'show', {searchQuery: 'q'}, 'filter'], - sort: 'recent', - show: 'discussions', - filter: '', - - searchQuery: '', - loadingMore: false, - - sortOptions: [ - {sort: 'recent', label: 'Recent'}, - {sort: 'replies', label: 'Replies'}, - {sort: 'newest', label: 'Newest'}, - {sort: 'oldest', label: 'Oldest'}, - ], - - displayStartUsers: function() { - return ['newest', 'oldest'].indexOf(this.get('sort')) != -1; - }.property('sort'), - - discussionsCount: function() { - return this.get('model.length'); - }.property('@each'), - - resultsLoading: false, - - start: 0, - - moreResults: function() { - return !! this.get('meta.moreUrl'); - }.property('meta.moreUrl'), - - meta: null, - - getResults: function(start) { - var sort = this.get('sort'); - // var order = this.get('order'); - var order; - var show = this.get('show'); - var searchQuery = this.get('searchQuery'); - - if (sort == 'newest') sort = 'created'; - else if (sort == 'oldest') { - sort = 'created'; - order = 'asc'; - } - else if (sort == 'recent') { - sort = ''; - } - else if (sort == 'replies') { - order = 'desc'; - } - - var params = { - sort: (order == 'desc' ? '-' : '')+sort, - q: searchQuery, - start: start - }; - - if (show == 'posts') { - if (searchQuery) params.include = 'relevantPosts'; - else if (sort == 'created') params.include = 'startPost,startUser'; - else params.include = 'lastPost,lastUser'; - } - - return this.store.find('discussion', params).then(function(discussions) { - var results = Em.A(); - discussions.forEach(function(discussion) { - var relevantPosts = Em.A(); - discussion.get('relevantPosts.content').forEach(function(post) { - relevantPosts.pushObject(PostResult.create(post)); - }); - results.pushObject(DiscussionResult.create({ - content: discussion, - relevantPosts: relevantPosts, - lastPost: PostResult.create(discussion.get('lastPost')), - startPost: PostResult.create(discussion.get('startPost')) - })); - results.set('meta', discussions.get('meta')); - }); - return results; - }); - }, - - actions: { - showDiscussionPane: function() { - this.set('paneShowing', true); - }, - - hideDiscussionPane: function() { - this.set('paneShowing', false); - }, - - togglePinned: function() { - this.set('panePinned', ! this.get('panePinned')); - }, - - loadMore: function() { - var self = this; - this.set('start', this.get('length')); - this.set('loadingMore', true); - - this.getResults(this.get('start')).then(function(results) { - self.get('model').addObjects(results); - self.set('meta', results.get('meta')); - // self.set('moreResults', !! results.get('meta.moreUrl')); - self.set('loadingMore', false); - }); - }, - - delete: function(discussion) { - alert('are you sure you want to delete discusn: '+discussion.get('title')); - } - }, - - queryDidChange: function(q) { - this.get('controllers.application').set('searchQuery', this.get('searchQuery')); - this.get('controllers.application').set('searchActive', !! this.get('searchQuery')); - - var sortOptions = this.get('sortOptions'); - - if (this.get('searchQuery') && sortOptions[0].sort != 'relevance') { - sortOptions.unshiftObject({sort: 'relevance', label: 'Relevance'}); - } - else if ( ! this.get('searchQuery') && sortOptions[0].sort == 'relevance') { - sortOptions.shiftObject(); - } - }.observes('searchQuery'), - - paramsDidChange: function(show) { - this.set('start', 0); - }.observes('show', 'sort', 'searchQuery') - -}); diff --git a/ember/app/controllers/discussions/index.js b/ember/app/controllers/discussions/index.js deleted file mode 100644 index 11f03b5..0000000 --- a/ember/app/controllers/discussions/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import Ember from 'ember'; - -export default Ember.ArrayController.extend({ - needs: ['application', 'composer'] -}); diff --git a/ember/app/controllers/login.js b/ember/app/controllers/login.js deleted file mode 100644 index ee3e931..0000000 --- a/ember/app/controllers/login.js +++ /dev/null @@ -1,11 +0,0 @@ -import Ember from 'ember'; - -// import NotificationMessage from '../models/notification-message'; - -// export default Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, Ember.Evented, { - -// authenticatorFactory: 'authenticator:flarum' - -// }); - -export default Ember.Controller.extend(); \ No newline at end of file diff --git a/ember/app/helpers/.gitkeep b/ember/app/helpers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/helpers/abbreviate-number.js b/ember/app/helpers/abbreviate-number.js deleted file mode 100644 index e48eaaa..0000000 --- a/ember/app/helpers/abbreviate-number.js +++ /dev/null @@ -1,6 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Handlebars.makeBoundHelper(function(number, options) { - return new Ember.Handlebars.SafeString(''+number); -}); - diff --git a/ember/app/helpers/abbreviate-time.js b/ember/app/helpers/abbreviate-time.js deleted file mode 100644 index 2e916e6..0000000 --- a/ember/app/helpers/abbreviate-time.js +++ /dev/null @@ -1,31 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Handlebars.makeBoundHelper(function(time) { - var m = moment(time); - var datetime = m.format(), - full = m.format('LLLL'); - - var second = 1e3; - var minute = 6e4; - var hour = 36e5; - var day = 864e5; - var week = 6048e5; - var ago = null; - - var diff = Math.abs(m.diff(moment())); - - if (diff < 60 * minute) { - ago = moment.duration(diff).minutes()+'m'; - } else if (diff < 24 * hour) { - ago = moment.duration(diff).hours()+'h'; - } else if (diff < 30 * day) { - ago = moment.duration(diff).days()+'d'; - } else if (m.year() == moment().year()) { - ago = m.format('D MMM'); - } else { - ago = m.format('MMM \'YY'); - } - - return new Handlebars.SafeString(''); -}); - diff --git a/ember/app/helpers/fa-icon.js b/ember/app/helpers/fa-icon.js deleted file mode 100644 index b1b464a..0000000 --- a/ember/app/helpers/fa-icon.js +++ /dev/null @@ -1,6 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Handlebars.makeBoundHelper(function(icon, options) { - return new Handlebars.SafeString(''); -}); - diff --git a/ember/app/helpers/highlight-words.js b/ember/app/helpers/highlight-words.js deleted file mode 100644 index 298c37d..0000000 --- a/ember/app/helpers/highlight-words.js +++ /dev/null @@ -1,18 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Handlebars.makeBoundHelper(function(text, phrase, options) { - if (phrase) { - var words = phrase.split(' '); - var replacement = function(matched) { - return ''+matched+''; - }; - words.forEach(function(word) { - text = text.replace( - new RegExp("\\b"+word+"\\b", 'gi'), - replacement - ); - }); - } - return new Handlebars.SafeString(text); -}); - diff --git a/ember/app/helpers/render-hook.js b/ember/app/helpers/render-hook.js deleted file mode 100644 index 3315962..0000000 --- a/ember/app/helpers/render-hook.js +++ /dev/null @@ -1,6 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Handlebars.makeBoundHelper(function(name, options) { - return new Handlebars.SafeString(''); -}); - diff --git a/ember/app/helpers/user-avatar.js b/ember/app/helpers/user-avatar.js deleted file mode 100644 index a06a4c5..0000000 --- a/ember/app/helpers/user-avatar.js +++ /dev/null @@ -1,53 +0,0 @@ -import Ember from 'ember'; - -function HSVtoRGB(h, s, v) { - var r, g, b, i, f, p, q, t; - if (h && s === undefined && v === undefined) { - s = h.s, v = h.v, h = h.h; - } - i = Math.floor(h * 6); - f = h * 6 - i; - p = v * (1 - s); - q = v * (1 - f * s); - t = v * (1 - (1 - f) * s); - switch (i % 6) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; - } - return { - r: Math.floor(r * 255), - g: Math.floor(g * 255), - b: Math.floor(b * 255) - }; -} - -export default Ember.Handlebars.makeBoundHelper(function(user, options) { - if (!user) return; - - var number; - if (number = user.get('avatarNumber')) { - number = number + ''; - var filename = number.length >= 3 ? number : new Array(3 - number.length + 1).join('0') + number; - return new Handlebars.SafeString(''); - } - - var username = user.get('username'); - if (!username) username = '?'; - - var letter = username.charAt(0).toUpperCase(); - - var num = 0; - for (var i = 0; i < username.length; i++) { - num += username.charCodeAt(i) * 13; - } - - var hue = num % 360; - var rgb = HSVtoRGB(hue / 360, 100 / 255, 200 / 255); - var bg = ''+rgb.r.toString(16)+rgb.g.toString(16)+rgb.b.toString(16); - return new Handlebars.SafeString(''+letter+''); -}); - diff --git a/ember/app/index.html b/ember/app/index.html deleted file mode 100644 index 5bfcca9..0000000 --- a/ember/app/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - Flarum - - - - {{content-for 'head'}} - - - - - {{content-for 'head-footer'}} - - - {{content-for 'body'}} - - - - - {{content-for 'body-footer'}} - - diff --git a/ember/app/mixins/post-stream.js b/ember/app/mixins/post-stream.js deleted file mode 100644 index bce952d..0000000 --- a/ember/app/mixins/post-stream.js +++ /dev/null @@ -1,33 +0,0 @@ -export default Ember.Mixin.create({ - // Find the DOM element of the item that is nearest to a post with a certain - // number. This will either be another post (if the requested post doesn't - // exist,) or a gap presumed to container the requested post. - findNearestToNumber: function(number) { - var nearestItem = $(); - $('.posts .item').each(function() { - var $this = $(this), - thisNumber = $this.data('number'); - if (thisNumber > number) { - return false; - } - nearestItem = $this; - }); - return nearestItem; - }, - - findNearestToIndex: function(index) { - var nearestItem = $('.posts .item[data-start='+index+'][data-end='+index+']'); - - if (! nearestItem.length) { - $('.posts .item').each(function() { - var $this = $(this); - if ($this.data('start') <= index && $this.data('end') >= index) { - nearestItem = $this; - return false; - } - }); - } - - return nearestItem; - } -}); diff --git a/ember/app/models/.gitkeep b/ember/app/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/models/discussion-result.js b/ember/app/models/discussion-result.js deleted file mode 100644 index 94f9e60..0000000 --- a/ember/app/models/discussion-result.js +++ /dev/null @@ -1,12 +0,0 @@ -import Ember from 'ember'; - -var DiscussionResult = Ember.ObjectProxy.extend({ - - relevantPosts: Em.A(), - - startPost: null, - lastPost: null - -}); - -export default DiscussionResult; diff --git a/ember/app/models/discussion-state.js b/ember/app/models/discussion-state.js deleted file mode 100644 index d2cb576..0000000 --- a/ember/app/models/discussion-state.js +++ /dev/null @@ -1,7 +0,0 @@ -import Ember from 'ember'; -import DS from 'ember-data'; - -export default DS.Model.extend({ - readTime: DS.attr('date'), - readNumber: DS.attr('number') -}); diff --git a/ember/app/models/discussion.js b/ember/app/models/discussion.js deleted file mode 100644 index fb9fdd6..0000000 --- a/ember/app/models/discussion.js +++ /dev/null @@ -1,81 +0,0 @@ -import Ember from 'ember'; -import DS from 'ember-data'; - -var Discussion = DS.Model.extend({ - - title: DS.attr('string'), - - slug: function() { - return this.get('title').toLowerCase().replace(/[^a-z0-9]/gi, '-').replace(/-+/g, '-'); - }.property('title'), - - canReply: DS.attr('boolean'), - canEdit: DS.attr('boolean'), - canDelete: DS.attr('boolean'), - - startTime: DS.attr('date'), - startUser: DS.belongsTo('user'), - startPost: DS.belongsTo('post'), - - lastTime: DS.attr('date'), - lastUser: DS.belongsTo('user'), - lastPost: DS.belongsTo('post'), - lastPostNumber: DS.attr('number'), - - relevantPosts: DS.hasMany('post'), - - postsCount: DS.attr('number'), - repliesCount: function() { - return Math.max(0, this.get('postsCount') - 1); - }.property('postsCount'), - - posts: DS.attr('string'), - postIds: function() { - return this.get('posts').split(','); - }.property('posts'), - - readNumber: DS.attr('number'), - unreadCount: function() { - return this.get('lastPostNumber') - this.get('readNumber'); - }.property('lastPostNumber', 'readNumber'), - - //-------------------------------- - // Prototype generated properties - - // category: function() { - // var categories = [null, 'Announcements', 'General', 'Support', 'Feedback', 'Core', 'Plugins', 'Themes']; - // return categories[Math.floor(Math.random() * categories.length)]; - // }.property(), - category: DS.attr('string'), - - _recent: function() { - var cutoff = new Date('September 19, 2014'); - return this.get('lastTime') > cutoff; - }.property('lastTime'), - - unread: function() { - return Math.round(Math.random() * (this.get('_recent') ? 0.8 : 0) * this.get('postsCount')); - }.property(), - - // sticky: function() { - // return Math.random() > (this.get('_recent') ? 0.95 : 0.99); - // }.property(), - sticky: DS.attr('boolean'), - - excerpt: function() { - // return 'I want to get your thoughts on this one TV Addicts: what new show have you been getting into this year, and why?'; - // return 'Here\'s the near-final game list, in no particular order. The list may be subject to amendments, as we\'re still chasing up copies of some games.'; - // return 'Nominating for the Annual General Meeting is easy. Read this to find out how.' - return 'There are many apps made with Ninetech in the Mac App Store. If you\'d like, take a moment to share your Nintech-made apps in this thread.'; - }.property(), - - locked: function() { - return Math.random() > 0.95; - }.property(), - - following: function() { - return Math.random() > 0.95; - }.property() -}); - -export default Discussion; diff --git a/ember/app/models/group.js b/ember/app/models/group.js deleted file mode 100644 index 35c1934..0000000 --- a/ember/app/models/group.js +++ /dev/null @@ -1,10 +0,0 @@ -import Ember from 'ember'; -import DS from 'ember-data'; - -export default DS.Model.extend({ - - name: DS.attr('string'), - - users: DS.hasMany('group'), - -}); diff --git a/ember/app/models/post-result.js b/ember/app/models/post-result.js deleted file mode 100644 index b343270..0000000 --- a/ember/app/models/post-result.js +++ /dev/null @@ -1,20 +0,0 @@ -import Ember from 'ember'; - -var PostResult = Ember.ObjectProxy.extend({ - - relevantContent: '' - -}); - -PostResult.reopenClass({ - create: function(post) { - if (!post) return null; - - var result = this._super(); - result.set('content', post); - result.set('relevantContent', post.get('content')); - return result; - } -}); - -export default PostResult; diff --git a/ember/app/models/post-stream.js b/ember/app/models/post-stream.js deleted file mode 100644 index c4d19fd..0000000 --- a/ember/app/models/post-stream.js +++ /dev/null @@ -1,201 +0,0 @@ -import Ember from 'ember'; - -// The post stream is an object which represents the posts in a discussion as -// they are displayed on the discussion page, from top to bottom. ... - -export default Ember.ArrayProxy.extend(Ember.Evented, { - - // An array of all of the post IDs, in chronological order, in the discussion. - ids: Em.A(), - content: Em.A(), - store: null, - discussion: null, - - postLoadCount: 20, - - _init: function() { - this.clear(); - }.on('init'), - - setup: function(ids) { - this.set('ids', ids); - this.clear(); - }, - - count: function() { - return this.get('ids.length'); - }.property('ids'), - - firstLoaded: function() { - var first = this.objectAt(0); - return first && ! first.gap; - }.property('content.@each'), - - lastLoaded: function() { - var last = this.objectAt(this.get('length') - 1); - return last && ! last.gap; - }.property('content.@each'), - - // Clear the contents of the post stream, resetting it to one big gap. - clear: function() { - var stream = this.get('content'); - stream.enumerableContentWillChange(); - stream.clear().pushObject(Em.Object.create({ - gap: true, - indexStart: 0, - indexEnd: this.get('count') - 1, - loading: true - })); - stream.enumerableContentDidChange(); - }, - - loadRange: function(start, end, backwards) { - var limit = this.get('postLoadCount'); - end = end || start + limit; - - // Find the appropriate gap objects in the post stream. When we find - // one, we will turn on its loading flag. - this.get('content').forEach(function(item) { - if (item.gap && ( - (item.indexStart >= start && item.indexStart <= end) - || (item.indexEnd >= start && item.indexEnd <= end) - )) { - item.set('loading', true); - item.set('direction', backwards ? 'up' : 'down'); - } - }); - - // Get a list of post numbers that we'll want to retrieve. If there are - // more post IDs than the number of posts we want to load, then take a - // slice of the array in the appropriate direction. - var ids = this.get('ids').slice(start, end + 1); - ids = backwards ? ids.slice(-limit) : ids.slice(0, limit); - - return this.loadPosts(ids); - }, - - loadPosts: function(ids) { - if (! ids.length) { - return Ember.RSVP.resolve(); - } - - var stream = this; - return this.store.find('post', {ids: ids}).then(function(posts) { - stream.addPosts(posts); - }); - }, - - loadNearNumber: function(number) { - // Find the item in the post stream which is nearest to this number. If - // it turns out the be the actual post we're trying to load, then we can - // return a resolved promise (i.e. we don't need to make an API - // request.) Or, if it's a gap, we'll switch on its loading flag. - var item = this.findNearestToNumber(number); - if (item) { - if (item.get('post.number') == number) { - return Ember.RSVP.resolve([item.get('post')]); - } else if (item.gap) { - item.set('direction', 'down').set('loading', true); - } - } - - var stream = this; - return this.store.find('post', { - discussions: this.get('discussion.id'), - near: number - }).then(function(posts) { - stream.addPosts(posts); - }); - }, - - loadNearIndex: function(index) { - // Find the item in the post stream which is nearest to this index. If - // it turns out the be the actual post we're trying to load, then we can - // return a resolved promise (i.e. we don't need to make an API - // request.) Or, if it's a gap, we'll switch on its loading flag. - var item = this.findNearestToIndex(index); - if (item) { - if (! item.gap) { - return Ember.RSVP.resolve([item.get('post')]); - } else { - item.set('direction', 'down').set('loading', true); - } - return this.loadRange(Math.max(item.indexStart, index - 10), item.indexEnd); - } - - return Ember.RSVP.reject(); - }, - - addPosts: function(posts) { - this.trigger('postsLoaded', posts); - - var stream = this; - posts.forEach(function(post) { - stream.addPost(post); - }); - - this.trigger('postsAdded'); - }, - - addPost: function(post) { - var stream = this; - var index = this.get('ids').indexOf(post.get('id')); - var content = this.get('content'); - - // Here we loop through each item in the post stream, and find the gap - // in which this post should be situated. When we find it, we can replace - // it with the post, and new gaps either side if appropriate. - content.some(function(item, i) { - if (item.indexStart <= index && item.indexEnd >= index) { - var newItems = []; - if (item.indexStart < index) { - newItems.push(Ember.Object.create({ - gap: true, - indexStart: item.indexStart, - indexEnd: index - 1 - })); - } - newItems.push(Ember.Object.create({ - indexStart: index, - indexEnd: index, - post: post - })); - if (item.indexEnd > index) { - newItems.push(Ember.Object.create({ - gap: true, - indexStart: index + 1, - indexEnd: item.indexEnd - })); - } - content.enumerableContentWillChange(); - content.replace(i, 1, newItems); - content.enumerableContentDidChange(); - return true; - } - }); - }, - - findNearestToNumber: function(number) { - var nearestItem; - this.get('content').some(function(item) { - var thisNumber = item.get('post.number'); - if (thisNumber > number) { - return true; - } - nearestItem = item; - }); - return nearestItem; - }, - - findNearestToIndex: function(index) { - var nearestItem; - this.get('content').some(function(item) { - if (item.indexStart <= index && item.indexEnd >= index) { - nearestItem = item; - return true; - } - }); - return nearestItem; - } - -}); diff --git a/ember/app/models/post.js b/ember/app/models/post.js deleted file mode 100644 index 9444a1c..0000000 --- a/ember/app/models/post.js +++ /dev/null @@ -1,37 +0,0 @@ -import Ember from 'ember'; -import DS from 'ember-data'; - -export default DS.Model.extend({ - - discussion: DS.belongsTo('discussion', {inverse: null}), - number: DS.attr('number'), - - time: DS.attr('string'), - user: DS.belongsTo('user'), - type: DS.attr('string'), - content: DS.attr('string'), - contentHtml: DS.attr('string'), - - editTime: DS.attr('string'), - editUser: DS.belongsTo('user'), - edited: Ember.computed.notEmpty('editTime'), - - deleteTime: DS.attr('string'), - deleteUser: DS.belongsTo('user'), - deleted: Ember.computed.notEmpty('deleteTime'), - - replyTo: DS.belongsTo('post', {inverse: 'replies'}), - replyToNumber: DS.attr('number'), - replyToUser: DS.belongsTo('user'), - - replies: DS.hasMany('post', {inverse: 'replyTo'}), - repliesCount: DS.attr('number'), - - canEdit: DS.attr('boolean'), - canDelete: DS.attr('boolean'), - - likes: function() { - return Math.floor(Math.random() * (Math.random() < 0.3 ? 10 : 1)); - }.property() - -}); diff --git a/ember/app/models/result-stream.js b/ember/app/models/result-stream.js deleted file mode 100644 index 7555f1d..0000000 --- a/ember/app/models/result-stream.js +++ /dev/null @@ -1,44 +0,0 @@ -import Ember from 'ember'; - -// Represents a collection of results (e.g. a list of discussions) - -export default Ember.Object.extend({ - - // An array of the results. - results: Em.A(), - - // The currently-active result. - currentResult: null, - - sort: null, - - // The index of the currently-active result (determined by ID.) Returns '?' - // if the currently-active result is not in the results list. - index: function() { - var index = '?'; - var id = this.get('currentResult.id'); - this.get('results').some(function(result, i) { - if (result.get('id') == id) { - index = i + 1; - return true; - } - }); - return index; - }.property('currentResult', 'results'), - - // The number of results. - count: function() { - return this.get('results.length'); - }.property('results'), - - // The previous result. - previous: function() { - return this.get('results').objectAt(this.get('index') - 2); - }.property('index'), - - // The next result. - next: function() { - return this.get('results').objectAt(this.get('index')); - }.property('index'), - -}); diff --git a/ember/app/models/user.js b/ember/app/models/user.js deleted file mode 100644 index 991c9bb..0000000 --- a/ember/app/models/user.js +++ /dev/null @@ -1,21 +0,0 @@ -import Ember from 'ember'; -import DS from 'ember-data'; - -export default DS.Model.extend({ - - username: DS.attr('string'), - avatarUrl: DS.attr('string'), - joinTime: DS.attr('date'), - lastSeenTime: DS.attr('date'), - discussionsCount: DS.attr('number'), - postsCount: DS.attr('number'), - - canEdit: DS.attr('boolean'), - canDelete: DS.attr('boolean'), - - groups: DS.hasMany('group'), - - avatarNumber: function() { - return Math.random() > 0.3 ? Math.floor(Math.random() * 19) + 1 : null; - }.property() -}); diff --git a/ember/app/router.js b/ember/app/router.js deleted file mode 100644 index db67dc3..0000000 --- a/ember/app/router.js +++ /dev/null @@ -1,24 +0,0 @@ -import Ember from 'ember'; -import config from './config/environment'; - -console.log(config.locationType); -var Router = Ember.Router.extend({ - location: config.locationType -}); - -Router.map(function() { - this.resource('categories', { path: '/categories' }); - - this.resource('discussions', { path: '/' }, function() { - this.resource('discussion', { path: '/:id/:slug' }); - }); - - this.resource('user', { path: '/user/:username' }, function() { - this.route('activity'); - this.route('posts'); - this.route('discussions'); - this.route('preferences'); - }); -}); - -export default Router; diff --git a/ember/app/routes/.gitkeep b/ember/app/routes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/routes/application.js b/ember/app/routes/application.js deleted file mode 100644 index a68d741..0000000 --- a/ember/app/routes/application.js +++ /dev/null @@ -1,30 +0,0 @@ -import Ember from 'ember'; -// import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin'; - -// export default Ember.Route.extend(ApplicationRouteMixin, { - -// actions: { - -// login: function() { -// return this.render('login', { -// into: 'application', -// outlet: 'modal' -// }); -// }, - -// doLogin: function() { -// this.get('session').authenticate('authenticator:custom', {}); -// }, - -// closeModal: function() { -// return this.disconnectOutlet({ -// outlet: 'modal', -// parentView: 'application' -// }); -// } - -// } - -// }); - -export default Ember.Route.extend(); \ No newline at end of file diff --git a/ember/app/routes/discussion.js b/ember/app/routes/discussion.js deleted file mode 100644 index d67ae36..0000000 --- a/ember/app/routes/discussion.js +++ /dev/null @@ -1,64 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Route.extend({ - - queryParams: { - start: {replace: true} - }, - - renderTemplate: function() { - this.render(); - this.render('discussion-sidebar', { - into: 'application', - outlet: 'sidebar' - }); - }, - - model: function(params) { - return this.store.find('discussion', params.id); - }, - - resetController: function(controller) { - // Whenever we exit the discussion view, or transition to a different - // discussion, we want to reset the query params so that they don't stick. - controller.set('start', '1'); - controller.set('searchQuery', ''); - controller.set('loaded', false); - controller.set('postStream', null); - }, - - setupController: function(controller, model) { - controller.setup(model); - - this.controllerFor('application').set('showDiscussionStream', true); - this.controllerFor('discussions').set('paned', true); - this.controllerFor('discussions').set('current', model); - }, - - actions: { - - queryParamsDidChange: function(params) { - // We're only interested in changes to the ?start param, and we're - // not interested if nothing has actually changed. If the start - // param has changed, we want to tell the controller to load posts - // near it. - if (! params.start || params.start == this.get('controller.start') || ! this.get('controller.loaded')) { - return; - } - this.get('controller').send('jumpToNumber', params.start); - }, - - willTransition: function(transition) { - // If we're going to transition out, we need to abort any unfinished - // AJAX requests. We need to do this because sometimes a transition - // to another discussion will happen very rapidly (i.e. when using - // the arrow buttons on the result stream.) If a previous - // discussion's posts finish loading while displaying a new - // discussion, strange things will happen. - this.store.adapterFor('discussion').xhr.forEach(function(xhr) { - xhr.abort(); - }); - } - - } -}); diff --git a/ember/app/routes/discussions.js b/ember/app/routes/discussions.js deleted file mode 100644 index b3f55af..0000000 --- a/ember/app/routes/discussions.js +++ /dev/null @@ -1,36 +0,0 @@ -import Ember from 'ember'; -import Discussion from '../models/discussion'; - -export default Ember.Route.extend({ - - setupController: function(controller, model) { - controller.set('model', model); - - if ( ! model.get('length')) { - controller.set('resultsLoading', true); - - controller.getResults().then(function(results) { - controller - .set('resultsLoading', false) - .set('meta', results.get('meta')) - .set('model.content', results); - }); - } - }, - - model: function(params) { - var model = Ember.ArrayProxy.create(); - - return Ember.RSVP.resolve(model); - }, - - actions: { - queryParamsDidChange: function(newParams, params) { - var self = this; - Ember.run.scheduleOnce('afterRender', function() { - self.refresh(); - }); - } - } - -}); diff --git a/ember/app/routes/discussions/index.js b/ember/app/routes/discussions/index.js deleted file mode 100644 index 1cf728b..0000000 --- a/ember/app/routes/discussions/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Route.extend({ - - renderTemplate: function() { - this.render(); - this.render('discussions-sidebar', { - into: 'application', - outlet: 'sidebar' - }); - }, - - setupController: function(controller, model) { - this.controllerFor('discussions').set('paneShowing', false); - this.controllerFor('discussions').set('paned', false); - this.controllerFor('application').set('showDiscussionStream', false); - this._super(controller, model); - } - -}); diff --git a/ember/app/serializers/application.js b/ember/app/serializers/application.js deleted file mode 100644 index 31200ac..0000000 --- a/ember/app/serializers/application.js +++ /dev/null @@ -1,12 +0,0 @@ -import JsonApiSerializer from 'ember-json-api/json-api-serializer'; -export default JsonApiSerializer.extend({ - normalize: function(type, hash, property) { - var json = {}; - - for (var prop in hash) { - json[prop.camelize()] = hash[prop]; - } - - return this._super(type, json, property); - } -}); \ No newline at end of file diff --git a/ember/app/styles/.gitkeep b/ember/app/styles/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/styles/app.less b/ember/app/styles/app.less deleted file mode 100644 index 3e9bf67..0000000 --- a/ember/app/styles/app.less +++ /dev/null @@ -1,14 +0,0 @@ -@import "config.less"; - -@flarum-base: "flarum/"; -@bootstrap-base: "../../bower_components/bootstrap/less/"; -@font-awesome-base: "../../bower_components/font-awesome/less/"; - -@import "@{flarum-base}bootstrap/bootstrap.less"; -@import "@{font-awesome-base}font-awesome.less"; - -@fa-font-path: "../font-awesome/fonts"; - -@import "@{flarum-base}global.less"; -@import "@{flarum-base}discussions.less"; -@import "@{flarum-base}discussion.less"; diff --git a/ember/app/styles/config.less b/ember/app/styles/config.less deleted file mode 100644 index 3831f28..0000000 --- a/ember/app/styles/config.less +++ /dev/null @@ -1,32 +0,0 @@ -// Default blue -@flarum-hue: 210; -@flarum-saturation: 30%; -@flarum-lightness: 90%; -@flarum-body-saturation: 30%; -@flarum-body-lightness: 99.99%; - -@flarum-primary-color: hsl(@flarum-hue, @flarum-saturation, 35%); - -@flarum-background-color: hsl(@flarum-hue, @flarum-saturation, @flarum-lightness); -@flarum-background-image: none; -@flarum-background-repeat: no-repeat; - -@flarum-bg-primary-color: contrast(@flarum-background-color, @flarum-primary-color, #fff); -@flarum-bg-secondary-color: contrast(@flarum-background-color, rgba(255, 255, 255, 0.1), darken(@flarum-background-color, 7%)); -@flarum-bg-text-color: @flarum-bg-primary-color; -@flarum-bg-link-color: @flarum-bg-primary-color; -@flarum-bg-muted-color: contrast(@flarum-background-color, rgba(255, 255, 255, 0.25), desaturate(darken(@flarum-bg-secondary-color, 25%), 10%)); - -// styles for a background image -// @flarum-bg-primary-color: rgba(255, 255, 255, 0.8); -// @flarum-bg-secondary-color: rgba(255, 255, 255, 0.2); -// @flarum-bg-text-color: @flarum-bg-primary-color; -// @flarum-bg-link-color: @flarum-bg-primary-color; -// @flarum-bg-muted-color: contrast(@flarum-background-color, lighten(@flarum-bg-secondary-color, 20%), darken(@flarum-bg-secondary-color, 20%)); - -@flarum-body-background-color: hsl(@flarum-hue, @flarum-body-saturation, @flarum-body-lightness); -@flarum-body-primary-color: contrast(@flarum-body-background-color, @flarum-primary-color, #fff); -@flarum-body-secondary-color: contrast(@flarum-body-background-color, lighten(@flarum-body-background-color, 13%), desaturate(darken(@flarum-body-background-color, 10%), 10%)); -@flarum-body-text-color: contrast(@flarum-body-background-color, #555, #fff); -@flarum-body-link-color: @flarum-body-primary-color; -@flarum-body-muted-color: contrast(@flarum-body-background-color, lighten(@flarum-body-secondary-color, 20%), darken(@flarum-body-secondary-color, 25%)); diff --git a/ember/app/styles/flarum/bootstrap/bootstrap.less b/ember/app/styles/flarum/bootstrap/bootstrap.less deleted file mode 100644 index 18e8347..0000000 --- a/ember/app/styles/flarum/bootstrap/bootstrap.less +++ /dev/null @@ -1,50 +0,0 @@ -// Core variables and mixins -@import "@{bootstrap-base}variables.less"; -@import "variables.less"; -@import "@{bootstrap-base}mixins.less"; - -// Reset -@import "@{bootstrap-base}normalize.less"; -@import "@{bootstrap-base}print.less"; - -// Core CSS -@import "@{bootstrap-base}scaffolding.less"; -@import "@{bootstrap-base}type.less"; -@import "@{bootstrap-base}code.less"; -@import "@{bootstrap-base}grid.less"; -@import "@{bootstrap-base}tables.less"; -@import "@{bootstrap-base}forms.less"; -@import "@{bootstrap-base}buttons.less"; - -// Components -@import "@{bootstrap-base}component-animations.less"; -// @import "@{bootstrap-base}glyphicons.less"; -@import "@{bootstrap-base}dropdowns.less"; -@import "@{bootstrap-base}button-groups.less"; -@import "@{bootstrap-base}input-groups.less"; -// @import "@{bootstrap-base}navs.less"; -// @import "@{bootstrap-base}navbar.less"; -// @import "@{bootstrap-base}breadcrumbs.less"; -@import "@{bootstrap-base}pagination.less"; -// @import "@{bootstrap-base}pager.less"; -// @import "@{bootstrap-base}labels.less"; -// @import "@{bootstrap-base}badges.less"; -// @import "@{bootstrap-base}jumbotron.less"; -// @import "@{bootstrap-base}thumbnails.less"; -// @import "@{bootstrap-base}alerts.less"; -@import "@{bootstrap-base}progress-bars.less"; -// @import "@{bootstrap-base}media.less"; -// @import "@{bootstrap-base}list-group.less"; -// @import "@{bootstrap-base}panels.less"; -// @import "@{bootstrap-base}wells.less"; -@import "@{bootstrap-base}close.less"; - -// Components w/ JavaScript -@import "@{bootstrap-base}modals.less"; -@import "@{bootstrap-base}tooltip.less"; -@import "@{bootstrap-base}popovers.less"; -// @import "@{bootstrap-base}carousel.less"; - -// Utility classes -@import "@{bootstrap-base}utilities.less"; -@import "@{bootstrap-base}responsive-utilities.less"; diff --git a/ember/app/styles/flarum/bootstrap/variables.less b/ember/app/styles/flarum/bootstrap/variables.less deleted file mode 100644 index 10fcaca..0000000 --- a/ember/app/styles/flarum/bootstrap/variables.less +++ /dev/null @@ -1,832 +0,0 @@ -// -// Variables -// -------------------------------------------------- - - -//== Colors -// -//## Gray and brand colors for use across Bootstrap. - -@gray-darker: lighten(#000, 13.5%); // #222 -@gray-dark: lighten(#000, 20%); // #333 -@gray: lighten(#000, 33.5%); // #555 -@gray-light: lighten(#000, 60%); // #999 -@gray-lighter: lighten(#000, 93.5%); // #eee - -@brand-primary: @flarum-body-primary-color; // CHANGED -@brand-success: #5cb85c; -@brand-info: #5bc0de; -@brand-warning: #f0ad4e; -@brand-danger: #d9534f; - - -//== Scaffolding -// -// ## Settings for some of the most global styles. - -//** Background color for ``. -@body-bg: @flarum-background-color; // CHANGED -//** Global text color on ``. -@text-color: @flarum-bg-text-color; // CHANGED - -//** Global textual link color. -@link-color: @brand-primary; -//** Link hover color set via `darken()` function. -@link-hover-color: darken(@link-color, 15%); - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -@font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; // CHANGED -@font-family-serif: Georgia, "Times New Roman", Times, serif; -//** Default monospace fonts for ``, ``, and `
`.
-@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
-@font-family-base:        @font-family-sans-serif;
-
-@font-size-base:          13px; // CHANGED
-@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
-@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
-
-@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
-@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
-@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
-@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
-@font-size-h5:            @font-size-base;
-@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
-
-//** Unit-less `line-height` for use in components like buttons.
-@line-height-base:        1.538461538; // 20/13 CHANGED
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
-
-//** By default, this inherits from the ``.
-@headings-font-family:    inherit;
-@headings-font-weight:    500;
-@headings-line-height:    1.1;
-@headings-color:          inherit;
-
-
-//-- Iconography
-//
-//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-@icon-font-path:          "../fonts/";
-@icon-font-name:          "glyphicons-halflings-regular";
-@icon-font-svg-id:        "glyphicons_halflingsregular";
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-@padding-base-vertical:     9px; // CHANGED
-@padding-base-horizontal:   15px; // CHANGED
-
-@padding-large-vertical:    10px;
-@padding-large-horizontal:  16px;
-
-@padding-small-vertical:    5px;
-@padding-small-horizontal:  10px;
-
-@padding-xs-vertical:       1px;
-@padding-xs-horizontal:     5px;
-
-@line-height-large:         1.33;
-@line-height-small:         1.5;
-
-@border-radius-base:        4px;
-@border-radius-large:       6px;
-@border-radius-small:       3px;
-
-//** Global color for active items (e.g., navs or dropdowns).
-@component-active-color:    #fff;
-//** Global background color for active items (e.g., navs or dropdowns).
-@component-active-bg:       @brand-primary;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-@caret-width-base:          4px;
-//** Carets increase slightly in size for larger components.
-@caret-width-large:         5px;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-@table-cell-padding:            8px;
-//** Padding for cells in `.table-condensed`.
-@table-condensed-cell-padding:  5px;
-
-//** Default background color used for all tables.
-@table-bg:                      transparent;
-//** Background color used for `.table-striped`.
-@table-bg-accent:               #f9f9f9;
-//** Background color used for `.table-hover`.
-@table-bg-hover:                #f5f5f5;
-@table-bg-active:               @table-bg-hover;
-
-//** Border color for table and cell borders.
-@table-border-color:            #ddd;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-@btn-font-weight:                normal;
-
-@btn-default-color:              @flarum-body-primary-color; // CHANGED
-@btn-default-bg:                 darken(@flarum-body-secondary-color, 7%); // CHANGED
-@btn-default-border:             @btn-default-bg; // CHANGED
-
-@btn-primary-color:              contrast(@brand-primary, @flarum-body-secondary-color, #fff); // CHANGED
-@btn-primary-bg:                 @brand-primary;
-@btn-primary-border:             @btn-primary-bg; // CHANGED
-
-@btn-success-color:              #fff;
-@btn-success-bg:                 @brand-success;
-@btn-success-border:             @btn-success-bg; // CHANGED
-
-@btn-info-color:                 #fff;
-@btn-info-bg:                    @brand-info;
-@btn-info-border:                @btn-info-bg; // CHANGED
-
-@btn-warning-color:              #fff;
-@btn-warning-bg:                 @brand-warning;
-@btn-warning-border:             @btn-warning-bg; // CHANGED
-
-@btn-danger-color:               #fff;
-@btn-danger-bg:                  @brand-danger;
-@btn-danger-border:              @btn-danger-bg; // CHANGED
-
-@btn-link-disabled-color:        @gray-light;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-@input-bg:                       #fff;
-//** `` background color
-@input-bg-disabled:              @gray-lighter;
-
-//** Text color for ``s
-@input-color:                    @gray;
-//** `` border color
-@input-border:                   #ccc;
-//** `` border radius
-@input-border-radius:            @border-radius-base;
-//** Border color for inputs on focus
-@input-border-focus:             #66afe9;
-
-//** Placeholder text color
-@input-color-placeholder:        @gray-light;
-
-//** Default `.form-control` height
-@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
-//** Large `.form-control` height
-@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
-//** Small `.form-control` height
-@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
-
-@legend-color:                   @gray-dark;
-@legend-border-color:            #e5e5e5;
-
-//** Background color for textual input addons
-@input-group-addon-bg:           @gray-lighter;
-//** Border color for textual input addons
-@input-group-addon-border-color: @input-border;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-@dropdown-bg:                    #fff;
-//** Dropdown menu `border-color`.
-@dropdown-border:                rgba(0,0,0,.15);
-//** Dropdown menu `border-color` **for IE8**.
-@dropdown-fallback-border:       #ccc;
-//** Divider color for between dropdown items.
-@dropdown-divider-bg:            #e5e5e5;
-
-//** Dropdown link text color.
-@dropdown-link-color:            @gray-dark;
-//** Hover color for dropdown links.
-@dropdown-link-hover-color:      darken(@gray-dark, 5%);
-//** Hover background for dropdown links.
-@dropdown-link-hover-bg:         #f5f5f5;
-
-//** Active dropdown menu item text color.
-@dropdown-link-active-color:     @component-active-color;
-//** Active dropdown menu item background color.
-@dropdown-link-active-bg:        @component-active-bg;
-
-//** Disabled dropdown menu item background color.
-@dropdown-link-disabled-color:   @gray-light;
-
-//** Text color for headers within dropdown menus.
-@dropdown-header-color:          @gray-light;
-
-// Note: Deprecated @dropdown-caret-color as of v3.1.0
-@dropdown-caret-color:           #000;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-@zindex-navbar:            1000;
-@zindex-dropdown:          1000;
-@zindex-popover:           1010;
-@zindex-tooltip:           1030;
-@zindex-navbar-fixed:      1030;
-@zindex-modal-background:  1040;
-@zindex-modal:             1050;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
-@screen-xs:                  480px;
-@screen-xs-min:              @screen-xs;
-@screen-phone:               @screen-xs-min;
-
-// Small screen / tablet
-// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
-@screen-sm:                  768px;
-@screen-sm-min:              @screen-sm;
-@screen-tablet:              @screen-sm-min;
-
-// Medium screen / desktop
-// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
-@screen-md:                  992px;
-@screen-md-min:              @screen-md;
-@screen-desktop:             @screen-md-min;
-
-// Large screen / wide desktop
-// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
-@screen-lg:                  1200px;
-@screen-lg-min:              @screen-lg;
-@screen-lg-desktop:          @screen-lg-min;
-
-// So media queries don't overlap when required, provide a maximum
-@screen-xs-max:              (@screen-sm-min - 1);
-@screen-sm-max:              (@screen-md-min - 1);
-@screen-md-max:              (@screen-lg-min - 1);
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-@grid-columns:              12;
-//** Padding between columns. Gets divided in half for the left and right.
-@grid-gutter-width:         15px;
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-@grid-float-breakpoint:     @screen-sm-min;
-//** Point at which the navbar begins collapsing.
-@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-@navbar-height:                    50px;
-@navbar-margin-bottom:             @line-height-computed;
-@navbar-border-radius:             @border-radius-base;
-@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
-@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
-@navbar-collapse-max-height:       340px;
-
-@navbar-default-color:             #777;
-@navbar-default-bg:                #f8f8f8;
-@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
-
-// Navbar links
-@navbar-default-link-color:                #777;
-@navbar-default-link-hover-color:          #333;
-@navbar-default-link-hover-bg:             transparent;
-@navbar-default-link-active-color:         #555;
-@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
-@navbar-default-link-disabled-color:       #ccc;
-@navbar-default-link-disabled-bg:          transparent;
-
-// Navbar brand label
-@navbar-default-brand-color:               @navbar-default-link-color;
-@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
-@navbar-default-brand-hover-bg:            transparent;
-
-// Navbar toggle
-@navbar-default-toggle-hover-bg:           #ddd;
-@navbar-default-toggle-icon-bar-bg:        #888;
-@navbar-default-toggle-border-color:       #ddd;
-
-
-// Inverted navbar
-// Reset inverted navbar basics
-@navbar-inverse-color:                      @gray-light;
-@navbar-inverse-bg:                         #222;
-@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
-
-// Inverted navbar links
-@navbar-inverse-link-color:                 @gray-light;
-@navbar-inverse-link-hover-color:           #fff;
-@navbar-inverse-link-hover-bg:              transparent;
-@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
-@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
-@navbar-inverse-link-disabled-color:        #444;
-@navbar-inverse-link-disabled-bg:           transparent;
-
-// Inverted navbar brand label
-@navbar-inverse-brand-color:                @navbar-inverse-link-color;
-@navbar-inverse-brand-hover-color:          #fff;
-@navbar-inverse-brand-hover-bg:             transparent;
-
-// Inverted navbar toggle
-@navbar-inverse-toggle-hover-bg:            #333;
-@navbar-inverse-toggle-icon-bar-bg:         #fff;
-@navbar-inverse-toggle-border-color:        #333;
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-@nav-link-padding:                          10px 15px;
-@nav-link-hover-bg:                         @gray-lighter;
-
-@nav-disabled-link-color:                   @gray-light;
-@nav-disabled-link-hover-color:             @gray-light;
-
-@nav-open-link-hover-color:                 #fff;
-
-//== Tabs
-@nav-tabs-border-color:                     #ddd;
-
-@nav-tabs-link-hover-border-color:          @gray-lighter;
-
-@nav-tabs-active-link-hover-bg:             @body-bg;
-@nav-tabs-active-link-hover-color:          @gray;
-@nav-tabs-active-link-hover-border-color:   #ddd;
-
-@nav-tabs-justified-link-border-color:            #ddd;
-@nav-tabs-justified-active-link-border-color:     @body-bg;
-
-//== Pills
-@nav-pills-border-radius:                   @border-radius-base;
-@nav-pills-active-link-hover-bg:            @component-active-bg;
-@nav-pills-active-link-hover-color:         @component-active-color;
-
-
-//== Pagination
-//
-//##
-
-@pagination-color:                     @link-color;
-@pagination-bg:                        #fff;
-@pagination-border:                    #ddd;
-
-@pagination-hover-color:               @link-hover-color;
-@pagination-hover-bg:                  @gray-lighter;
-@pagination-hover-border:              #ddd;
-
-@pagination-active-color:              #fff;
-@pagination-active-bg:                 @brand-primary;
-@pagination-active-border:             @brand-primary;
-
-@pagination-disabled-color:            @gray-light;
-@pagination-disabled-bg:               #fff;
-@pagination-disabled-border:           #ddd;
-
-
-//== Pager
-//
-//##
-
-@pager-bg:                             @pagination-bg;
-@pager-border:                         @pagination-border;
-@pager-border-radius:                  15px;
-
-@pager-hover-bg:                       @pagination-hover-bg;
-
-@pager-active-bg:                      @pagination-active-bg;
-@pager-active-color:                   @pagination-active-color;
-
-@pager-disabled-color:                 @pagination-disabled-color;
-
-
-//== Jumbotron
-//
-//##
-
-@jumbotron-padding:              30px;
-@jumbotron-color:                inherit;
-@jumbotron-bg:                   @gray-lighter;
-@jumbotron-heading-color:        inherit;
-@jumbotron-font-size:            ceil((@font-size-base * 1.5));
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-@state-success-text:             #3c763d;
-@state-success-bg:               #dff0d8;
-@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
-
-@state-info-text:                #31708f;
-@state-info-bg:                  #d9edf7;
-@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
-
-@state-warning-text:             #8a6d3b;
-@state-warning-bg:               #fcf8e3;
-@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
-
-@state-danger-text:              #a94442;
-@state-danger-bg:                #f2dede;
-@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-@tooltip-max-width:           200px;
-//** Tooltip text color
-@tooltip-color:               #fff;
-//** Tooltip background color
-@tooltip-bg:                  #000;
-@tooltip-opacity:             .9;
-
-//** Tooltip arrow width
-@tooltip-arrow-width:         5px;
-//** Tooltip arrow color
-@tooltip-arrow-color:         @tooltip-bg;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-@popover-bg:                          #fff;
-//** Popover maximum width
-@popover-max-width:                   276px;
-//** Popover border color
-@popover-border-color:                rgba(0,0,0,.2);
-//** Popover fallback border color
-@popover-fallback-border-color:       #ccc;
-
-//** Popover title background color
-@popover-title-bg:                    darken(@popover-bg, 3%);
-
-//** Popover arrow width
-@popover-arrow-width:                 10px;
-//** Popover arrow color
-@popover-arrow-color:                 #fff;
-
-//** Popover outer arrow width
-@popover-arrow-outer-width:           (@popover-arrow-width + 1);
-//** Popover outer arrow color
-@popover-arrow-outer-color:           rgba(0,0,0,.25);
-//** Popover outer arrow fallback color
-@popover-arrow-outer-fallback-color:  #999;
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-@label-default-bg:            @gray-light;
-//** Primary label background color
-@label-primary-bg:            @brand-primary;
-//** Success label background color
-@label-success-bg:            @brand-success;
-//** Info label background color
-@label-info-bg:               @brand-info;
-//** Warning label background color
-@label-warning-bg:            @brand-warning;
-//** Danger label background color
-@label-danger-bg:             @brand-danger;
-
-//** Default label text color
-@label-color:                 #fff;
-//** Default text color of a linked label
-@label-link-hover-color:      #fff;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-@modal-inner-padding:         20px;
-
-//** Padding applied to the modal title
-@modal-title-padding:         15px;
-//** Modal title line-height
-@modal-title-line-height:     @line-height-base;
-
-//** Background color of modal content area
-@modal-content-bg:                             #fff;
-//** Modal content border color
-@modal-content-border-color:                   rgba(0,0,0,.2);
-//** Modal content border color **for IE8**
-@modal-content-fallback-border-color:          #999;
-
-//** Modal backdrop background color
-@modal-backdrop-bg:           #000;
-//** Modal backdrop opacity
-@modal-backdrop-opacity:      .5;
-//** Modal header border color
-@modal-header-border-color:   #e5e5e5;
-//** Modal footer border color
-@modal-footer-border-color:   @modal-header-border-color;
-
-@modal-lg:                    900px;
-@modal-md:                    600px;
-@modal-sm:                    300px;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-@alert-padding:               15px;
-@alert-border-radius:         @border-radius-base;
-@alert-link-font-weight:      bold;
-
-@alert-success-bg:            @state-success-bg;
-@alert-success-text:          @state-success-text;
-@alert-success-border:        @state-success-border;
-
-@alert-info-bg:               @state-info-bg;
-@alert-info-text:             @state-info-text;
-@alert-info-border:           @state-info-border;
-
-@alert-warning-bg:            @state-warning-bg;
-@alert-warning-text:          @state-warning-text;
-@alert-warning-border:        @state-warning-border;
-
-@alert-danger-bg:             @state-danger-bg;
-@alert-danger-text:           @state-danger-text;
-@alert-danger-border:         @state-danger-border;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-@progress-bg:                 #f5f5f5;
-//** Progress bar text color
-@progress-bar-color:          #fff;
-
-//** Default progress bar color
-@progress-bar-bg:             @brand-primary;
-//** Success progress bar color
-@progress-bar-success-bg:     @brand-success;
-//** Warning progress bar color
-@progress-bar-warning-bg:     @brand-warning;
-//** Danger progress bar color
-@progress-bar-danger-bg:      @brand-danger;
-//** Info progress bar color
-@progress-bar-info-bg:        @brand-info;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-@list-group-bg:                 #fff;
-//** `.list-group-item` border color
-@list-group-border:             #ddd;
-//** List group border radius
-@list-group-border-radius:      @border-radius-base;
-
-//** Background color of single list elements on hover
-@list-group-hover-bg:           #f5f5f5;
-//** Text color of active list elements
-@list-group-active-color:       @component-active-color;
-//** Background color of active list elements
-@list-group-active-bg:          @component-active-bg;
-//** Border color of active list elements
-@list-group-active-border:      @list-group-active-bg;
-@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
-
-@list-group-link-color:         #555;
-@list-group-link-heading-color: #333;
-
-
-//== Panels
-//
-//##
-
-@panel-bg:                    #fff;
-@panel-body-padding:          15px;
-@panel-border-radius:         @border-radius-base;
-
-//** Border color for elements within panels
-@panel-inner-border:          #ddd;
-@panel-footer-bg:             #f5f5f5;
-
-@panel-default-text:          @gray-dark;
-@panel-default-border:        #ddd;
-@panel-default-heading-bg:    #f5f5f5;
-
-@panel-primary-text:          #fff;
-@panel-primary-border:        @brand-primary;
-@panel-primary-heading-bg:    @brand-primary;
-
-@panel-success-text:          @state-success-text;
-@panel-success-border:        @state-success-border;
-@panel-success-heading-bg:    @state-success-bg;
-
-@panel-info-text:             @state-info-text;
-@panel-info-border:           @state-info-border;
-@panel-info-heading-bg:       @state-info-bg;
-
-@panel-warning-text:          @state-warning-text;
-@panel-warning-border:        @state-warning-border;
-@panel-warning-heading-bg:    @state-warning-bg;
-
-@panel-danger-text:           @state-danger-text;
-@panel-danger-border:         @state-danger-border;
-@panel-danger-heading-bg:     @state-danger-bg;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-@thumbnail-padding:           4px;
-//** Thumbnail background color
-@thumbnail-bg:                @body-bg;
-//** Thumbnail border color
-@thumbnail-border:            #ddd;
-//** Thumbnail border radius
-@thumbnail-border-radius:     @border-radius-base;
-
-//** Custom text color for thumbnail captions
-@thumbnail-caption-color:     @text-color;
-//** Padding around the thumbnail caption
-@thumbnail-caption-padding:   9px;
-
-
-//== Wells
-//
-//##
-
-@well-bg:                     #f5f5f5;
-@well-border:                 darken(@well-bg, 7%);
-
-
-//== Badges
-//
-//##
-
-@badge-color:                 #fff;
-//** Linked badge text color on hover
-@badge-link-hover-color:      #fff;
-@badge-bg:                    @gray-light;
-
-//** Badge text color in active nav link
-@badge-active-color:          @link-color;
-//** Badge background color in active nav link
-@badge-active-bg:             #fff;
-
-@badge-font-weight:           bold;
-@badge-line-height:           1;
-@badge-border-radius:         10px;
-
-
-//== Breadcrumbs
-//
-//##
-
-@breadcrumb-padding-vertical:   8px;
-@breadcrumb-padding-horizontal: 15px;
-//** Breadcrumb background color
-@breadcrumb-bg:                 #f5f5f5;
-//** Breadcrumb text color
-@breadcrumb-color:              #ccc;
-//** Text color of current page in the breadcrumb
-@breadcrumb-active-color:       @gray-light;
-//** Textual separator for between breadcrumb elements
-@breadcrumb-separator:          "/";
-
-
-//== Carousel
-//
-//##
-
-@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
-
-@carousel-control-color:                      #fff;
-@carousel-control-width:                      15%;
-@carousel-control-opacity:                    .5;
-@carousel-control-font-size:                  20px;
-
-@carousel-indicator-active-bg:                #fff;
-@carousel-indicator-border-color:             #fff;
-
-@carousel-caption-color:                      #fff;
-
-
-//== Close
-//
-//##
-
-@close-font-weight:           bold;
-@close-color:                 #000;
-@close-text-shadow:           0 1px 0 #fff;
-
-
-//== Code
-//
-//##
-
-@code-color:                  #c7254e;
-@code-bg:                     #f9f2f4;
-
-@kbd-color:                   #fff;
-@kbd-bg:                      #333;
-
-@pre-bg:                      #f5f5f5;
-@pre-color:                   @gray-dark;
-@pre-border-color:            #ccc;
-@pre-scrollable-max-height:   340px;
-
-
-//== Type
-//
-//##
-
-//** Text muted color
-@text-muted:                  @gray-light;
-//** Abbreviations and acronyms border color
-@abbr-border-color:           @gray-light;
-//** Headings small color
-@headings-small-color:        @gray-light;
-//** Blockquote small color
-@blockquote-small-color:      @gray-light;
-//** Blockquote font size
-@blockquote-font-size:        (@font-size-base * 1.25);
-//** Blockquote border color
-@blockquote-border-color:     @gray-lighter;
-//** Page header border color
-@page-header-border-color:    @gray-lighter;
-//** Width of horizontal description list titles
-@dl-horizontal-offset:        @component-offset-horizontal;
-//** Horizontal line color.
-@hr-border:                   @gray-lighter;
-
-//== Miscellaneous
-//
-//##
-
-//** Horizontal line color.
-@hr-border:                   @gray-lighter;
-
-//** Horizontal offset for forms and lists.
-@component-offset-horizontal: 180px;
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-@container-tablet:             ((720px + @grid-gutter-width));
-//** For `@screen-sm-min` and up.
-@container-sm:                 @container-tablet;
-
-// Medium screen / desktop
-@container-desktop:            ((940px + @grid-gutter-width));
-//** For `@screen-md-min` and up.
-@container-md:                 @container-desktop;
-
-// Large screen / wide desktop
-@container-large-desktop:      ((1140px + @grid-gutter-width));
-//** For `@screen-lg-min` and up.
-@container-lg:                 @container-large-desktop;
diff --git a/ember/app/styles/flarum/discussion.less b/ember/app/styles/flarum/discussion.less
deleted file mode 100644
index d8f8947..0000000
--- a/ember/app/styles/flarum/discussion.less
+++ /dev/null
@@ -1,357 +0,0 @@
-.discussion-header, .post, .items div.gap:first-of-type:last-of-type {
-	max-width: 800px;
-}
-
-.posts {
-	// margin-bottom: 15px;
-}
-.posts .post {
-	padding-top: 25px;
-	padding-bottom: 25px;
-	border-bottom: 1px solid @flarum-body-secondary-color;
-}
-.item.highlight .post {
-	box-shadow: inset 0 -5px 0 rgba(255, 255, 0, 0.2), inset 0 5px 0 rgba(255, 255, 0, 0.2);
-}
-.items .item:first-of-type {
-	border-top: 0;
-}
-.items .item:last-of-type {
-	border-bottom: 0;
-}
-.post {
-	padding-left: 65px;
-	transition: 0.2s box-shadow;
-}
-.gap {
-	padding: 20px 0;
-	text-align: center;
-	color: @flarum-bg-muted-color;
-	margin: -1px -30px 0;
-	cursor: pointer;
-	border-top: 1px dashed @flarum-body-background-color !important;
-	border-bottom: 1px dashed @flarum-body-background-color !important;
-	transition: color 0.2s, padding 0.2s;
-	background: lighten(@flarum-background-color, 0%);
-	text-transform: uppercase;
-	font-size: 12px;
-	letter-spacing: 1px;
-	overflow: hidden;
-	position: relative;
-}
-.items div.gap:first-of-type {
-	margin-top: -30px;
-	position: relative;
-}
-.items div.gap:last-of-type {
-	margin-bottom: -30px;
-	border-bottom: 0;
-}
-.items div.gap:first-of-type:last-of-type {
-	margin: 0;
-	background: @flarum-body-background-color;
-	color: @flarum-body-muted-color;
-}
-.items div.gap:first-of-type:last-of-type:after {
-	display: none;
-}
-.gap.active, .gap:hover, .gap.loading {
-	padding: 50px 0;
-}
-.gap.loading {
-	color: @flarum-bg-muted-color;
-	transition: none;
-}
-.gap.down:after {
-	content: '\f078';
-	font-family: 'FontAwesome';
-	display: block;
-	opacity: 0;
-	transition: opacity 0.2s;
-	margin-bottom: -25px;
-	margin-top: 10px;
-	height: 15px;
-	color: @flarum-body-muted-color;
-}
-.gap.up:before {
-	content: '\f077';
-	font-family: 'FontAwesome';
-	display: block;
-	opacity: 0;
-	transition: opacity 0.4s;
-	margin-top: -25px;
-	margin-bottom: 10px;
-	height: 15px;
-}
-.gap:hover:before, .gap:hover:after, .gap.loading:before, .gap.loading:after, .gap.active:before, .gap.active:after {
-	opacity: 1;
-}
-
-
-
-.post {
-	line-height: 1.75em;
-	position: relative;
-}
-.item .controls {
-	// position: absolute;
-	// right: 10px;
-	// top: -2px;
-	// display: none;
-	float: right;
-	margin: 0 0 0 10px;
-}
-.item:hover .controls {
-	display: block;
-}
-.post .controls .btn {
-	margin-top: -2px;
-}
-.post header {
-	margin-bottom: 10px;
-}
-.post header, .post header a {
-	color: @flarum-body-muted-color;
-}
-.post .user {
-	margin: 0;
-	display: inline;
-}
-.post .user, .post .user a {
-	color: @flarum-body-primary-color;
-	font-weight: 600;
-	font-size: 16px;
-}
-.post .time {
-	font-size: 12px;
-	float: right;
-	&, & a {
-		color: @flarum-body-muted-color;
-	}
-}
-.post .reply-to {
-	margin-left: 5px;
-}
-
-.post {
-	// float: left;
-}
-.post-meta {
-	// float: left;
-	position: absolute;
-	left: 100%;
-	top: 25px;
-	margin-left: 30px;
-	width: 100px;
-	color: @flarum-body-muted-color;
-	// opacity: 0;
-	// transition: opacity 0s;
-}
-.post-icon {
-	float: left;
-
-}
-
-.post.deleted {
-
-	& .post-body,
-	& .post-meta,
-	& .avatar,
-	& .reply-to {
-		display: none;
-	}
-
-	& header {
-		margin-bottom: 0;
-	}
-
-	&, & header, & header a {
-		color: fadeout(@flarum-body-text-color, 50%);
-	}
-}
-
-@media screen and (max-width: 1300px) {
-	.post-meta {
-		position: static;
-		margin: 0;
-		width: auto;
-		margin-bottom: -15px;
-	}
-	.post-meta li {
-		display: inline;
-		margin-right: 15px;
-	}
-	.post-meta .reveal {
-		opacity: 1 !important;
-	}
-}
-.post-meta ul {
-	list-style-type: none;
-	margin: 0;
-	padding: 0;
-	line-height: 2em;
-}
-.post-meta a {
-	color: @flarum-body-muted-color;
-}
-.post-meta a:hover {
-	color: @flarum-body-primary-color;
-	text-decoration: none;
-}
-.post-meta .reveal {
-	opacity: 0;
-	transition: opacity 0.2s;
-}
-.item:hover .post-meta .reveal {
-	opacity: 1
-}
-
-.post header .avatar, .post .post-icon {
-	margin-left: -65px;
-	float: left;
-}
-.activity {
-	font-size: 16px;
-	// padding-top: 20px;
-	// padding-bottom: 20px;
-}
-.activity, .activity a {
-	color: @flarum-body-text-color;
-}
-.post .post-icon {
-	width: 48px;
-	text-align: right;
-	font-size: 22px;
-}
-.activity a {
-	font-weight: 600;
-}
-.discussion-header h4 {
-	font-size: 14px;
-	text-align: center;
-	margin: 0 0 15px 0;
-}
-.discussion-header h4, .discussion-header h4 a {
-	color: @flarum-body-muted-color;
-}
-.discussion-header .category {
-	padding: 3px 7px;
-	font-size: 13px;
-}
-.discussion-header h4 a {
-	font-weight: 600;
-}
-
-
-.discussion-footer {
-	margin-top: 15px;
-}
-.discussion-footer .list-inline {
-	margin-bottom: 0;
-}
-
-.discussion-controls {
-	margin: 0 auto;
-	margin-bottom: 30px;
-}
-.discussion-controls .btn {
-	// margin-top: 10px;
-}
-.discussion-controls .btn-group {
-	width: 100%;
-}
-
-.btn-group-suffix {
-	display: table;
-	border-spacing: 1px;
-}
-.btn-group-suffix .btn {
-	display: table-cell;
-	float: none;
-	width: 100%;
-}
-// .btn-group-suffix .dropdown-toggle {
-// 	float: right;
-// 	margin-right: -40px;
-// 	width: 39px;
-// }
-
-
-@media (min-width: @screen-md-min) {
-
-	.discussion-scrubber {
-		margin: 0 auto;
-		text-align: right;
-		margin-right: 3px;
-	}
-	.scrubber a {
-		color: @flarum-bg-muted-color;
-	}
-	.scrubber a i {
-		font-size: 14px;
-		margin-left: 5px;
-	}
-	.scrubber a:hover {
-		text-decoration: none;
-		color: @flarum-bg-link-color;
-	}
-	.scrubber .scrollbar {
-		margin: 10px 4px 10px 0;
-		position: relative;
-		cursor: pointer;
-	}
-	.scrubber .scrollbar-before, .scrubber .scrollbar-after {
-		border-right: 1px solid @flarum-bg-secondary-color;
-	}
-	.scrubber .scrollbar-slider {
-		position: relative;
-		width: 100%;
-		padding: 5px 0;
-	}
-	.scrubber .handle {
-		height: 100%;
-		width: 9px;
-		background: @flarum-bg-primary-color;
-		border-radius: 9px;
-		float: right;
-		margin-right: -4px;
-		transition: background 0.2s;
-	}
-	.scrubber .disabled .handle {
-		background: @flarum-bg-secondary-color;
-	}
-	.scrubber .info {
-		height: (2em * @line-height-base);
-		margin-top: (-1em * @line-height-base);
-		position: absolute;
-		top: 50%;
-		width: 100%;
-		right: 15px;
-	}
-	.scrubber .info strong {
-		display: block;
-	}
-	.scrubber .info .description {
-		color: @flarum-bg-muted-color;
-	}
-	.scrollbar-highlights {
-		position: absolute;
-		left: 0;
-		right: 0;
-		top: 0;
-		bottom: 0;
-		list-style-type: none;
-	}
-	.scrollbar-highlights li {
-		position: absolute;
-		right: -6px;
-		background: #fc0;
-		height: 8px;
-		width: 13px;
-		border-radius: 4px;
-		box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
-		opacity: 0.99;
-	}
-}
-
-
diff --git a/ember/app/styles/flarum/discussions.less b/ember/app/styles/flarum/discussions.less
deleted file mode 100644
index 9b3b6f2..0000000
--- a/ember/app/styles/flarum/discussions.less
+++ /dev/null
@@ -1,454 +0,0 @@
-.discussions-header, .discussions {
-	max-width: 1200px;
-}
-
-.discussions {
-	list-style: none;
-	margin: 0;
-	padding: 0;
-}
-.discussions > li {
-	.clearfix();
-	border-bottom: 1px solid @flarum-body-secondary-color;
-	position: relative;
-	line-height: 20px;
-	padding-left: 45px;
-	padding-right: 30px;
-}
-.discussions > li.highlight, .discussions > li.active {
-	background: lighten(@flarum-body-secondary-color, 8%);
-}
-.discussions, .discussions a {
-	color: @flarum-body-muted-color;
-}
-.discussions .action {
-	float: left;
-	width: 45px;
-	padding-right: 12px;
-	margin-left: -45px;
-	text-align: right;
-	// visibility: hidden;
-	// opacity: 0.5;
-	min-height: 1px;
-	padding-top: 12px;
-	padding-bottom: 12px;
-	font-size: 12px;
-	color: @flarum-body-muted-color;
-}
-.discussions .action .unread {
-	color: #fff;
-	background: @flarum-body-primary-color;
-	padding: 0 4px;
-	border-radius: 4px;
-	font-weight: 600;
-}
-.discussions li:hover .action {
-	visibility: visible;
-}
-.discussions .action:hover {
-	text-decoration: none;
-	// opacity: 0.75;
-}
-.discussions .action:active {
-	opacity: 0.5;
-}
-.discussions .info {
-	float: left;
-	width: 74%;
-	margin-right: 2%;
-	padding-top: 12px;
-	padding-bottom: 12px;
-	display: block;
-}
-.discussions .info .title {
-	font-weight: 300;
-	font-size: 16px;
-	color: @flarum-body-link-color;
-	margin-right: 5px;
-}
-.discussions .icon {
-	margin-right: 5px;
-	font-size: 14px;
-	color: @flarum-body-link-color;
-	margin-left: 0;
-}
-.discussions .info:hover {
-	text-decoration: none;
-}
-.discussions .info:hover .title {
-	text-decoration: underline;
-}
-// .discussions .info > span {
-// 	font-size: 12px;
-// 	margin-left: 5px;
-// }
-.discussions .info .draft {
-	color:#4ea11b;
-}
-.discussions .info .excerpt {
-	display: block;
-	white-space: normal;
-	color: @flarum-body-muted-color;
-	line-height: 1.5em;
-	margin: 10px 0 5px;
-}
-.discussions .category {
-	float: right;
-	margin: -1px 0 -1px 10px;
-}
-.discussions .terminal-post {
-	float: left;
-	width: 15%;
-	white-space: nowrap;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	padding-top: 12px;
-	padding-bottom: 12px;
-	font-size: 90%;
-}
-.discussions .terminal-post .avatar {
-	margin: -1px 5px -1px 0;
-}
-.discussions .replies {
-	float: left;
-	width: 8%;
-	font-size: 20px;
-	font-weight: 300;
-	text-align: center;
-	padding-top: 12px;
-	padding-bottom: 12px;
-}
-.discussions .unread .title {
-	font-weight: 600;
-}
-.discussions .locked .title, .discussions .icon-locked {
-	color: #777;
-}
-.discussions .locked .unread {
-	background-color: #777;
-}
-.discussions .sticky .title, .discussions .icon-sticky {
-	color: #D03202;
-}
-.discussions .sticky .unread {
-	background-color: #D03202;
-}
-.discussions .following .title {
-	color: #F5A623 !important;
-}
-.discussions .icon-following {
-	color: #F5A623;
-}
-.discussions .following .unread {
-	background-color: #F5A623 !important;
-}
-.discussions .controls {
-	position: absolute;
-	right: 0;
-	top: 11px;
-	display: none;
-}
-.discussions li:hover .controls {
-	display: block;
-}
-.discussions .relevant-posts {
-	clear: both;
-	// border: solid lighten(@flarum-body-text-color, 62%);
-	// border-width: 1px;
-	border-radius: 3px;
-	margin-bottom: 30px;
-	// padding: 0 10px;
-}
-.discussions .relevant-posts .post {
-	padding: 5px 0 5px 35px;
-	display: block;
-	// border-color: lighten(@flarum-body-text-color, 62%);
-	border: 0;
-	// margin-top: -1px;
-	color: @flarum-body-muted-color;
-}
-.discussions .relevant-posts .post:hover {
-	text-decoration: none;
-	// background: lighten(@flarum-body-secondary-color, 8%);
-	color: @flarum-body-text-color;
-	// padding-left: 45px;
-	// padding-right: 10px;
-	// margin-left: -10px;
-	// margin-right: -10px;
-}
-.discussions .relevant-posts .avatar {
-	margin-left: -35px;
-	opacity: 0.25;
-	float: left;
-}
-.discussions .relevant-posts .post:hover .avatar {
-	opacity: 1;
-}
-
-.load-more {
-	text-align: center;
-	margin-top: 20px;
-}
-.load-more .loading {
-	padding: 10px 0;
-}
-
-.discussions-pane {
-	left: 300px - 375px;
-	width: 100%;
-}
-.discussions-pane.paned {
-	position: fixed;
-	z-index: 10;
-	overflow: auto;
-	top: 0;
-	bottom: 0;
-	width: 375px;
-	padding: 2.5vh 0;
-	background: #fff;
-	border-right: 5px solid @flarum-background-color;
-	transition: left 0.2s;
-
-	&.showing {
-		left: 300px;
-	}
-
-	& .page-header .pull-right {
-		right: 20px;
-	}
-
-	& .discussions > li {
-		padding-right: 15px;
-		padding-left: 20px;
-
-		& .action {
-			padding-top: 15px;
-			padding-bottom: 15px;
-		}
-		& .info {
-			padding: 15px 0;
-			width: 85%;
-			min-height: 70px;
-			& .category {
-				padding: 1px 4px;
-				font-size: 11px;
-				margin-top: 0;
-			}
-			& .name {
-				display: block;
-			}
-			& .title {
-				font-size: 14px;
-			}
-			& .excerpt {
-				display: none;
-			}
-		}
-		& .controls {
-			display: none;
-		}
-		& .terminal-post {
-			width: 13%;
-			text-align: right;
-			float: right;
-			padding: 15px 0 5px;
-			& .avatar {
-				display: none;
-			}
-			& a {
-				margin: 0;
-			}
-		}
-		// & .replies {
-		// 	width: auto;
-		// 	float: right;
-		// 	clear: right;
-		// 	padding: 0 5px;
-		// 	border-radius: 4px;
-		// 	background: @flarum-body-secondary-color;
-		// 	color: #fff;
-		// 	font-size: 12px;
-		// 	font-weight: 600;
-		// 	margin-bottom: 15px;
-		// }
-		& .replies, & .action .unread {
-			width: auto;
-			float: none;
-			position: absolute;
-			top: 37px;
-			right: 15px;
-			padding: 0 5px;
-			border-radius: 4px;
-			color: #fff;
-			font-size: 12px;
-			font-weight: 600;
-			pointer-events: none;
-		}
-		& .replies {
-			background: @flarum-body-secondary-color;
-		}
-		&.unread .replies {
-			display: none;
-		}
-	}
-}
-
-.pinned {
-	& .discussions-pane {
-		left: 300px;
-		transition: left 0.2s, width 0.2s;
-	}
-	& .discussion-pane {
-		margin-left: 375px;
-	}
-}
-
-.discussions-header .select {
-	vertical-align: -1px;
-	margin-left: 10px;
-}
-
-
-
-@media (max-width: @screen-sm-max) {
-	.discussions-pane.paned {
-		display: none;
-	}
-	.discussions-header {
-		display: none;
-	}
-	.discussions {
-		& > li {
-			margin-left: 20px;
-			padding: 0;
-			line-height: inherit;
-		}
-		& .discussion {
-			position: relative;
-			background: #fff;
-		}
-		& .controls {
-			position: absolute;
-			left: 0;
-			right: 0;
-			bottom: 0;
-			top: 0;
-			display: block;
-
-			& .dropdown-toggle {
-				display: none;
-			}
-			& .dropdown-menu {
-				float: none;
-				position: static;
-				display: block;
-				text-align: center;
-				margin: 0;
-				padding: 0;
-				border: 0;
-				box-shadow: none;
-				width: auto;
-				min-width: 0;
-				background: none;
-				height: 100%;
-
-				& li {
-					float: left;
-					margin: 0;
-					height: 100%;
-
-					& a {
-						height: 100%;
-						padding: 25px 10px;
-						background: @flarum-body-primary-color;
-						color: #fff;
-						&.delete {
-							background: #e74135;
-						}
-					}
-					& .fa {
-						font-size: 22px;
-					}
-					& span {
-						display: none;
-					}
-				}
-			}
-		}
-		& .info {
-			display: block;
-			padding: 15px 75px 15px 20px;
-			width: auto;
-			float: none;
-			min-height: 70px;
-			margin: 0 0 0 -20px;
-			&.pressed {
-				background: @flarum-body-secondary-color;
-			}
-			& .category {
-				padding: 1px 4px;
-				font-size: 11px;
-				margin-top: 0;
-			}
-			& .name {
-				display: block;
-			}
-			& .excerpt {
-				display: none;
-			}
-		}
-		& .info:hover .title {
-			text-decoration: none;
-		}
-		& .info:after {
-			content: '\f054';
-			font-family: FontAwesome;
-			position: absolute;
-			right: 10px;
-			top: 15px;
-			color: @flarum-body-secondary-color;
-		}
-		& .info .title {
-			font-weight: 400;
-			font-size: 13px;
-		}
-		& .unread .title {
-			font-weight: 600;
-		}
-		& .terminal-post {
-			width: auto;
-			float: none;
-			position: absolute;
-			top: 15px;
-			right: 25px;
-			padding: 0;
-			pointer-events: none;
-			& .avatar {
-				display: none;
-			}
-			& a {
-				margin: 0;
-			}
-		}
-		
-		& .replies, & .action .unread {
-			width: auto;
-			float: none;
-			position: absolute;
-			top: 37px;
-			right: 25px;
-			padding: 0 5px;
-			border-radius: 4px;
-			color: #fff;
-			font-size: 12px;
-			font-weight: 600;
-			pointer-events: none;
-		}
-		& .replies {
-			background: @flarum-body-secondary-color;
-		}
-		& .unread .replies {
-			display: none;
-		}
-	}
-}
diff --git a/ember/app/styles/flarum/global.less b/ember/app/styles/flarum/global.less
deleted file mode 100644
index fccf58d..0000000
--- a/ember/app/styles/flarum/global.less
+++ /dev/null
@@ -1,858 +0,0 @@
-@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300,600);
-
-@import url(http://fonts.googleapis.com/css?family=Fugaz+One);
-
-// .pace .pace-progress {
-//   background: @flarum-bg-muted-color;
-//   position: fixed;
-//   z-index: 2000;
-//   top: 0;
-//   left: 0;
-//   height: 2px;
-
-//   -webkit-transition: width 1s;
-//   -moz-transition: width 1s;
-//   -o-transition: width 1s;
-//   transition: width 1s;
-// }
-
-// .pace-inactive {
-//   display: none;
-// }
-
-body {
-	background-color: @flarum-background-color;
-	background-image: @flarum-background-image;
-	color: @flarum-bg-text-color;
-	// -webkit-font-smoothing: antialiased;
-}
-body, input, button, select, textarea {
-	font-size: 13px;
-}
-body, h1, h2, h3, h4, h5, h6 {
-	font-family: 'Open Sans', sans-serif;
-}
-a {
-	color: @flarum-bg-link-color;
-}
-a:hover {
-	color: @flarum-bg-link-color;
-}
-#wrapper {
-	padding-top: 2.5vh;
-	padding-bottom: 2.5vh;
-}
-@media (min-width: @screen-md-min) {
-	#sidebar {
-		position: fixed;
-		top: 0;
-		bottom: 0;
-		height: 100%;
-		width: 295px;
-		padding: 2.5vh 25px 2.5vh 0;
-		display: table;
-		z-index: 20;
-	}
-	#sidebar-body {
-		display: table-row;
-		height: 100%;
-	}
-	#sidebar-content {
-		overflow: auto;
-		height: 100%;
-		box-sizing: content-box;
-		padding: 10px 25px;
-		margin: 0 -25px;
-	}
-	#sidebar-content .toolbar, #sidebar-content .body {
-		list-style-type: none;
-		margin: 0 0 10px;
-		padding: 0;
-	}
-	#sidebar-footer {
-		display: table-row;
-	}
-	#sidebar footer {
-		border-top: 1px solid @flarum-bg-secondary-color;
-		padding-top: 15px;
-	}
-	#sidebar-footer .statistics {
-		border-bottom: 1px solid @flarum-bg-secondary-color;
-		padding: 10px 0;
-	}
-	#sidebar-footer .statistics i {
-		margin-right: 10px;
-	}
-	#sidebar-footer .statistics a {
-		margin-right: 10px;
-	}
-	#sidebar-footer .meta {
-		padding-top: 10px;
-		.clearfix();
-	}
-	#sidebar-footer .meta > a, #sidebar-footer .meta > .dropdown > a {
-		color: @flarum-bg-muted-color;
-	}
-	#sidebar-footer .language {
-		float: left;
-	}
-	#sidebar-footer .powered-by {
-		float: right;
-	}
-	#sidebar-header {
-		display: table-row;
-	}
-	#sidebar header {
-	}
-	#sidebar header h1 {
-		font-weight: 600;
-		font-size: 18px;
-		margin: 0 0 15px;
-		line-height: 1.5em;
-		text-align: center;
-
-		// font-family: Fugaz One;
-		// font-weight: normal;
-		// font-size: 22px;
-	}
-	#sidebar header h1 .logo {
-		display: block;
-		margin: 0 auto;
-		padding-bottom: 10px;
-	}
-}
-#sidebar .search {
-	white-space: nowrap;
-	margin-bottom: 15px;
-}
-#sidebar .search input, #sidebar .search .search-nav {
-	border-radius: 20px;
-	background: transparent;
-	border: 1px solid @flarum-bg-secondary-color;
-	box-shadow: none;
-	padding: @padding-base-vertical @padding-base-horizontal;
-}
-#sidebar .search input {
-	padding-right: 30px;
-}
-#sidebar .search input::-webkit-input-placeholder {
-	color: @flarum-bg-muted-color;
-}
-#sidebar .search input::-moz-placeholder {
-	color: @flarum-bg-muted-color;
-}
-.search-input {
-	position: relative;
-}
-.search-input input[type=text] {
-	padding-right: 30px;
-	-webkit-appearance: none;
-}
-.search-input .clear {
-	position: absolute;
-	right: 13px;
-	top: @padding-base-vertical;
-	line-height: 18px;
-	color: @flarum-bg-muted-color;
-	font-size: 18px;
-	transition: transform 0.1s;
-	transform: scale(0.01);
-}
-.search-input.clearable .clear {
-	transform: scale(1);
-}
-#sidebar .search input:focus, #sidebar .search .search-nav, #sidebar .search-input.active input {
-	background: @flarum-body-background-color;
-	color: @flarum-body-text-color;
-	border-color: transparent;
-}
-#sidebar .search-nav {
-	text-align: center;
-}
-#sidebar .search-nav, #sidebar .search-nav a {
-	color: @flarum-body-muted-color;
-}
-#sidebar .search-nav a {
-	padding: 5px 10px;
-	margin: 0 -5px;
-}
-#sidebar .search-nav a:hover {
-	color: @flarum-body-primary-color;
-	text-decoration: none;
-}
-#sidebar .search-nav a.disabled {
-	color: @flarum-body-secondary-color;
-	cursor: default;
-}
-.nav-list {
-	list-style: none;
-	padding: 0;
-	margin: 0;
-	margin-top: 10px;
-}
-.nav-list li a {
-	display: block;
-	padding: (@padding-base-vertical + 1) @padding-base-horizontal;
-	color: @flarum-bg-link-color;
-	border-top: 1px solid @flarum-bg-secondary-color;
-}
-.nav-list li a i {
-	float: left;
-	margin-right: 10px;
-	margin-top: 3px;
-	text-decoration: none;
-	font-size: 14px;
-}
-
-.nav-list li.active + li a, .nav-list:not(.categories) li:first-of-type > a {
-	border-top: 0;
-}
-.nav-list li.active a {
-	color: contrast(@flarum-bg-primary-color, #000, #fff) !important;
-	background: @flarum-bg-primary-color;
-	border-radius: @border-radius-base;
-	font-weight: 600;
-	border-top: 0;
-}
-.nav-list li.active a:hover {
-	text-decoration: none;
-}
-.nav-list li a.count {
-	float: right;
-	border: 0;
-	background: none;
-}
-
-.nav-list .header {
-	display: block;
-	color: @flarum-bg-muted-color;
-	text-transform: uppercase;
-	font-weight: 600;
-	font-size: 12px;
-	border-top: 0;
-	padding: 8px 0;
-	margin-top: 15px;
-}
-.nav-list-small li a {
-	padding: 7px 12px;
-}
-
-.nav-list.categories {
-	margin-left: 35px;
-}
-.nav-list ul {
-	list-style: none;
-	margin: 0;
-	padding: 0;
-}
-.nav-list ul ul {
-	padding-left: 20px;
-	font-size: 12px;
-	display: none;
-}
-.nav-list.categories .color {
-	width: 16px;
-	height: 16px;
-	display: inline-block;
-	box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
-	border-radius: 4px;
-	margin-top: 2px;
-}
-
-.category {
-	padding: 2px 6px;
-	border-radius: 4px;
-	font-size: 12px;
-	display: inline-block;
-	line-height: @line-height-base;
-	color: @flarum-body-muted-color;
-	border: 1px solid @flarum-body-secondary-color;
-}
-// .category-announcements {
-// 	background-color: #2D4053;
-// 	color: #fff !important;
-// }
-// .category-general {
-// 	background-color: #D9D9D9;
-// 	color: #555 !important;
-// }
-// .category-support {
-// 	background-color: #5A69DB;
-// 	color: #fff !important;
-// }
-// .category-feedback {
-// 	background-color: #529E3C;
-// 	color: #fff !important;
-// }
-// .category-core {
-// 	background-color: #F7D64E;
-// 	color: #7E6500 !important;
-// }
-// .category-plugins {
-// 	background-color: #EC9A3D;
-// 	color: #fff !important;
-// }
-// .category-themes {
-// 	background-color: #DB5A5A;
-// 	color: #fff !important;
-// }
-
-#body {
-	background: @flarum-body-background-color;
-	margin-left: 295px;
-	padding: 2.5vh 30px;
-	border-radius: @border-radius-base;
-	// min-height: 94vh;
-	color: @flarum-body-text-color;
-}
-.page-header {
-	border-bottom: 1px solid @flarum-body-secondary-color;
-	position: relative;
-	padding-bottom: 40px;
-	margin: 0;
-}
-.page-header h2 {
-	text-align: center;
-	font-size: 22px;
-	font-weight: 300;
-	color: @flarum-body-primary-color;
-	margin: 0;
-}
-.page-header h2 i {
-	margin-right: 5px;
-}
-.page-header:before {
-	content: " ";
-	position: absolute;
-	display: block;
-	width: 0;
-	height: 0;
-	border: 15px solid transparent;
-	border-bottom-color: @flarum-body-secondary-color;
-	border-top-width: 0;
-	left: 50%;
-	bottom: -1px;
-	margin-left: -15px;
-}
-.page-header:after {
-	content: " ";
-	position: absolute;
-	display: block;
-	width: 0;
-	height: 0;
-	border: 14px solid transparent;
-	border-bottom-color: @flarum-body-background-color;
-	border-top-width: 0;
-	left: 50%;
-	bottom: -2px;
-	margin-left: -14px;
-}
-.page-header .pull-right {
-	position: absolute;
-	right: 0;
-}
-.page-header .pull-left {
-	position: absolute;
-	left: 0;
-}
-
-.btn {
-	padding: @padding-base-vertical @padding-base-horizontal;
-	font-weight: 600;
-}
-.btn-following {
-	.button-variant(#d80; #ffd; #ffd);
-}
-.btn i {
-	font-size: 14px;
-}
-.btn .icon-caret-down {
-	margin: 0 2px;
-}
-.btn:active,
-.btn.active,
-.btn-group.open .dropdown-toggle {
-	.box-shadow(inset 0 1px 4px rgba(0,0,0,.05));
-}
-
-.btn-default {
-	&,
-	&:hover,
-	&:focus,
-	&:active,
-	&.active,
-	.open .dropdown-toggle& {
-		background: transparent;
-		border-color: @flarum-body-secondary-color;
-		color: @flarum-body-muted-color;
-		font-weight: normal;
-	}
-}
-
-#sidebar .btn-default {
-	.button-variant(@flarum-bg-primary-color; @flarum-bg-secondary-color; @flarum-bg-secondary-color);
-	border: 0;
-	font-weight: 600;
-}
-
-.btn-sm {
-	padding: @padding-small-vertical @padding-small-horizontal;
-	border-radius: @border-radius-base;
-	font-size: 13px;
-}
-.btn-xs {
-	padding: @padding-xs-vertical @padding-xs-horizontal;
-	border-radius: @border-radius-base;
-}
-
-.select {
-	// margin-right: @padding-small-horizontal;
-}
-.select select {
-	-webkit-appearance: none;
-	background: transparent;
-	border-color: @flarum-body-secondary-color;
-	color: @flarum-body-muted-color;
-	padding: @padding-small-vertical @padding-small-horizontal;
-	padding-right: @padding-small-horizontal + 16;
-	outline: none;
-	border-radius: @border-radius-base;
-	cursor: pointer;
-	margin-right: -3px;
-	line-height: 1.5em;
-}
-.select i {
-	margin-left: -@padding-small-horizontal - 12;
-	pointer-events: none;
-	font-size: 12px;
-	color: @flarum-body-muted-color;
-}
-
-
-.controls.open {
-	display: block !important;
-}
-
-
-.tooltip-inner {
-	padding: 5px 8px;
-}
-
-#message-container {
-	position: fixed;
-	left: 0;
-	right: 0;
-	top: 2vh;
-	text-align: center;
-	z-index: 100;
-	pointer-events: none;
-}
-.message {
-	background: fade(#feb, 95%);
-	box-shadow: 0 0 0 1px fade(#c2991a, 40%), 0 3px 3px rgba(0, 0, 0, 0.1);
-	padding: @padding-base-vertical @padding-base-horizontal;
-	display: inline-block;
-	border-radius: @border-radius-base;
-	pointer-events: auto;
-	text-align: left;
-	&, & a {
-		color: #a61;
-	}
-}
-.message a {
-	font-weight: bold;
-	margin: 0 -10px 0 0;
-	padding: 10px;
-}
-.message-warning {
-	background: fade(#c21a1a, 95%);
-	box-shadow: 0 0 0 1px fade(#911, 80%), 0 3px 3px rgba(0, 0, 0, 0.1);
-	&, & a {
-		color: #fff;
-	}
-}
-.message-text {
-	display: inline-block;
-	max-width: 400px;
-	vertical-align: middle;
-}
-.message-actions {
-	display: inline-block;
-	vertical-align: middle;
-}
-
-.avatar, .avatar img {
-	display: inline-block;
-	width: 48px;
-	height: 48px;
-	border-radius: 24px;
-	color: #fff;
-	font-size: 26px;
-	font-weight: 300;
-	text-align: center;
-	line-height: 48px;
-	vertical-align: top;
-}
-.avatar-thumb, .avatar-thumb img {
-	width: 24px;
-	height: 24px;
-	font-size: 13px;
-	line-height: 24px;
-	vertical-align: middle;
-}
-
-#session {
-	// position: fixed;
-	// top: 10px;
-	// right: 10px;
-	// background: darken(@flarum-body-background-color, 3%);
-	// box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
-	// border-radius: 24px;
-	// padding: 5px;
-	z-index: 100;
-	text-align: center;
-}
-#session > ul {
-	list-style-type: none;
-	padding: 0;
-	margin: 0;
-	.clearfix();
-}
-#session > ul > li {
-	display: inline-block;
-	// border-right: 1px solid @flarum-bg-secondary-color;
-	vertical-align: middle;
-}
-#session > ul > li:last-of-type {
-	border-right: 0;
-}
-#session > ul > li > a, #session > ul > li > .dropdown > a {
-	display: block;
-	height: 24px;
-	line-height: 24px;
-	padding: 0 10px;
-}
-#session .avatar-thumb {
-	margin-right: 10px;
-	margin-left: -10px;
-	vertical-align: 0;
-}
-#session .notifications-dropdown .dropdown-toggle i {
-	font-size: 14px;
-	// color: @flarum-bg-muted-color;
-}
-#session .badge {
-	background: #e00;
-	color: #fff;
-	font-size: 10px;
-	font-weight: bold;
-	border-radius: 3px;
-	padding: 2px 3px;
-	position: absolute;
-	top: -1px;
-	right: 8px;
-	line-height: 10px;
-}
-
-
-
-.btn-icon {
-	color: @flarum-body-muted-color;
-	font-size: 14px;
-	padding-left: 7px;
-	padding-right: 7px;
-	background: transparent;
-	border-color: transparent;
-}
-.btn-icon:hover {
-	// color: @flarum-body-muted-color;
-	text-decoration: none;
-}
-
-
-.dropdown-menu .fa {
-	font-size: 14px;
-}
-
-
-.loading {
-	color: @flarum-body-muted-color;
-	padding: 50px 0;
-}
-
-/* Full width styles */
-@media (min-width: @screen-md-min) {
-	.container {
-		max-width: none;
-		width: auto;
-		padding: 0;
-	}
-	#sidebar {
-		width: 325px;
-		padding-left: 25px;
-		background: @flarum-background-color;
-		background-image: @flarum-background-image;
-	}
-	#body {
-		margin-left: 325px;
-		border-radius: 0;
-		// max-width: 900px;
-	}
-	#wrapper {
-		padding: 0;
-	}
-}
-
-body {
-	background: @flarum-body-background-color;
-}
-
-
-
-/* New post */
-.composer {
-	position: fixed;
-	left: 330px;
-	right: 30px;
-	bottom: -100%;
-	// right: 0;
-	z-index: 9;
-	max-width: 800px;
-	// transition: bottom 0.5s;
-}
-#body {
-	// transition: padding-bottom 0.5s;
-}
-.composer-handle {
-	// background: @flarum-background-color;
-	cursor: row-resize;
-	// height: 5px;
-}
-.composer-body {
-	padding: 15px 15px 0;
-	border: 1px solid @flarum-body-primary-color;
-	border-radius: 4px;
-	background: @flarum-body-background-color;
-	// background: @flarum-body-background-color;
-	box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
-}
-.composer-controls {
-	position: absolute;
-	right: 15px;
-	top: 15px;
-}
-.composer-controls a {
-	color: @flarum-body-muted-color;
-	font-size: 14px;
-	margin-left: 5px;
-}
-.composer-controls a:hover {
-	color: @flarum-body-primary-color;
-	font-size: 14px;
-	margin-left: 5px;
-}
-.composer h3 {
-	font-size: 13px;
-	color: @flarum-body-muted-color;
-	font-weight: 400;
-	margin: 0 0 15px;
-	padding-bottom: 15px;
-	border-bottom: 1px solid @flarum-body-secondary-color;
-}
-
-.composer-editor {
-
-}
-.composer-editor textarea {
-	.box-shadow(none);
-	min-height: 200px;
-	background: @flarum-body-background-color;
-	border: 0;
-	resize: none;
-	padding: 0;
-	// padding-bottom: 0;
-	&:focus {
-		.box-shadow(none);
-	}
-}
-.composer-editor-controls {
-	padding: 15px 0;
-	.clearfix();
-}
-.composer-editor-controls .btn {
-}
-.composer-editor-controls .pull-left > .btn:first-child {
-	margin-right: 5px;
-}
-.composer-editor-controls .pull-right > .btn {
-	margin-left: 5px;
-}
-
-
-.composer.collapsed .composer-handle {
-	height: 5px;
-}
-.composer.collapsed .composer-body {
-	padding-bottom: 1px;
-}
-.composer.collapsed .composer-editor {
-	display: none;
-}
-
-
-.highlight-keyword {
-	background: #fff28e;
-	padding: 1px 4px;
-	border-radius: 3px;
-	color: @flarum-body-text-color;
-}
-
-
-
-
-/* Small devices (tablets, 768px and up) */
-@media (min-width: @screen-sm-min) {
-	#sidebar-header .mobile-header {
-		display: none;
-	}
-}
-
-/* Medium devices (desktops, 992px and up) */
-@media (min-width: @screen-md-min) {
-
-}
-
-/* Large devices (large desktops, 1200px and up) */
-@media (min-width: @screen-lg-min) {
-
-}
-
-
-@media (max-width: @screen-sm-max) {
-	body {
-		padding-top: 45px;
-	}
-	#sidebar-header header {
-		display: none;
-	}
-	#sidebar-header .mobile-header {
-		display: block;
-		position: fixed;
-		top: 0;
-		left: 0;
-		right: 0;
-		background: @flarum-background-color;
-		padding: 12px 10px;
-		color: @flarum-bg-primary-color;
-		text-align: center;
-		z-index: 20;
-		height: 45px;
-	}
-	#sidebar-header .mobile-header h1 {
-		font-size: 16px;
-		font-weight: 600;
-		display: inline-block;
-		width: 80%;
-		margin: 0;
-		white-space: nowrap;
-		text-overflow: ellipsis;
-		overflow: hidden;
-	}
-	#sidebar-header .mobile-header .back {
-		position: absolute;
-		left: 15px;
-		top: 10px;
-		font-size: 18px;
-	}
-	#sidebar-header .search {
-		padding: 10px 15px;
-		margin: 0;
-		background: lighten(@flarum-body-secondary-color, 5%);
-		& .search-input input {
-			border: 0;
-			background: #fff;
-		}
-	}
-
-	#sidebar-footer {
-		display: none;
-	}
-
-	#sidebar-content {
-		overflow: hidden;
-		height: auto;
-		-webkit-overflow-scrolling: touch;
-		padding: 0;
-		margin: 0;
-		display: block;
-		position: fixed;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		top: auto;
-		background: #fff;
-		box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15);
-		height: 45px;
-		width: auto;
-		z-index: 20;
-	}
-	#sidebar-content .toolbar {
-		margin: 0;
-		padding: 0;
-		display: table;
-		list-style-type: none;
-		width: 100%;
-
-		& li {
-			display: table-cell;
-			text-align: center;
-		}
-		& li:first-of-type {
-			text-align: left;
-		}
-		& li:last-of-type {
-			text-align: right;
-		}
-
-		& .btn {
-			padding: 13px;
-			transition: opacity 0.3s;
-			&:active {
-				opacity: 0.25;
-			}
-		}
-		& .btn, & .btn-default, & .btn-group {
-			display: inline-block;
-			background: none !important;
-			border: 0 !important;
-			box-shadow: none !important;
-			width: auto;
-
-			& span {
-				display: none;
-			}
-		}
-	}
-	#sidebar-content .body {
-		display: none;
-	}
-
-	.discussion-scrubber {
-		& .scrubber-first, & .scrollbar-before, & .handle, & .description, & .scrollbar-after, & .scrubber-last {
-			display: none;
-		}
-		& .info {
-			padding: 15px;
-		}
-	}
-
-
-
-	#body {
-		margin-left: 0;
-		padding: 0;
-	}
-	#wrapper {
-		padding: 0;
-	}
-}
diff --git a/ember/app/templates/.gitkeep b/ember/app/templates/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/ember/app/templates/application.hbs b/ember/app/templates/application.hbs
deleted file mode 100644
index 053e7f0..0000000
--- a/ember/app/templates/application.hbs
+++ /dev/null
@@ -1,56 +0,0 @@
-{{outlet "modal"}}
-
-{{#if notificationMessage}}
-	
- {{notification-message message=notificationMessage closeAction="hideMessage"}} -
-{{/if}} - - diff --git a/ember/app/templates/components/.gitkeep b/ember/app/templates/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/templates/components/item-collection.hbs b/ember/app/templates/components/item-collection.hbs deleted file mode 100644 index 0eddfdb..0000000 --- a/ember/app/templates/components/item-collection.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#each item in items}} - {{view item}} -{{/each}} diff --git a/ember/app/templates/components/menu-list.hbs b/ember/app/templates/components/menu-list.hbs deleted file mode 100644 index 40dd5f6..0000000 --- a/ember/app/templates/components/menu-list.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#each item in items}} -
  • {{view item}}
  • -{{/each}} diff --git a/ember/app/templates/components/menu-split.hbs b/ember/app/templates/components/menu-split.hbs deleted file mode 100644 index 13978bd..0000000 --- a/ember/app/templates/components/menu-split.hbs +++ /dev/null @@ -1,13 +0,0 @@ -{{#if items}} - -{{/if}} diff --git a/ember/app/templates/components/notification-message.hbs b/ember/app/templates/components/notification-message.hbs deleted file mode 100644 index 43cee91..0000000 --- a/ember/app/templates/components/notification-message.hbs +++ /dev/null @@ -1,9 +0,0 @@ -
    - {{message.text}} - - Undo - {{#if message.dismissable}} - - {{/if}} - -
    diff --git a/ember/app/templates/components/post-type-comment.hbs b/ember/app/templates/components/post-type-comment.hbs deleted file mode 100644 index 8af1bac..0000000 --- a/ember/app/templates/components/post-type-comment.hbs +++ /dev/null @@ -1,26 +0,0 @@ -{{#if post.deleteTime}} - -{{/if}} - -
    -

    - {{#link-to "user" post.user}}{{user-avatar post.user}} {{post.user.username}}{{/link-to}} -

    -
    - -
    - {{{post.contentHtml}}} -
    - - diff --git a/ember/app/templates/components/post-type-title.hbs b/ember/app/templates/components/post-type-title.hbs deleted file mode 100644 index f87bdd2..0000000 --- a/ember/app/templates/components/post-type-title.hbs +++ /dev/null @@ -1,4 +0,0 @@ -
    - - {{#link-to "user" post.user}}{{post.user.username}}{{/link-to}} named the discussion: {{post.content}}. -
    diff --git a/ember/app/templates/components/search-input.hbs b/ember/app/templates/components/search-input.hbs deleted file mode 100644 index ce9b004..0000000 --- a/ember/app/templates/components/search-input.hbs +++ /dev/null @@ -1,2 +0,0 @@ -{{input type="text" placeholder="Search forum..." class="form-control" value=value action="search"}} -{{fa-icon "times-circle"}} diff --git a/ember/app/templates/components/text-editor.hbs b/ember/app/templates/components/text-editor.hbs deleted file mode 100644 index 6c6a884..0000000 --- a/ember/app/templates/components/text-editor.hbs +++ /dev/null @@ -1,9 +0,0 @@ - - -
    -
    - - - -
    -
    diff --git a/ember/app/templates/composer.hbs b/ember/app/templates/composer.hbs deleted file mode 100644 index ec63b09..0000000 --- a/ember/app/templates/composer.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
    - -
    - -
    - - - -
    - -

    {{{title}}}

    - -
    - {{text-editor placeholder=""}} -
    - -
    diff --git a/ember/app/templates/discussion-header.hbs b/ember/app/templates/discussion-header.hbs deleted file mode 100644 index 249d315..0000000 --- a/ember/app/templates/discussion-header.hbs +++ /dev/null @@ -1,9 +0,0 @@ -{{#if category}} -

    - {{category}} -

    -{{/if}} - -

    - {{title}} -

    diff --git a/ember/app/templates/discussion-post.hbs b/ember/app/templates/discussion-post.hbs deleted file mode 100644 index 443143e..0000000 --- a/ember/app/templates/discussion-post.hbs +++ /dev/null @@ -1,13 +0,0 @@ -{{#if view.controls}} -
    - - {{menu-list items=view.controls class="dropdown-menu pull-right"}} -
    -{{/if}} - -{{#link-to "discussion" view.post.discussion (query-params start=view.post.number) class="time"}} - {{abbreviate-time view.post.time}} - {{!-- #{{view.post.number}} (ID: {{view.post.id}}) --}} -{{/link-to}} - -{{dynamic-component type=view.contentComponent post=view.post}} diff --git a/ember/app/templates/discussion-scrollbar.hbs b/ember/app/templates/discussion-scrollbar.hbs deleted file mode 100644 index 8585e3a..0000000 --- a/ember/app/templates/discussion-scrollbar.hbs +++ /dev/null @@ -1,20 +0,0 @@ -Original Post -
    -
    -
    -
    -
    - 0 of {{postStream.count}} posts - -
    -
    -
    - {{#if relevantPostRanges}} -
      - {{#each range in relevantPostRanges}} -
    • - {{/each}} -
    - {{/if}} -
    -Now diff --git a/ember/app/templates/discussion.hbs b/ember/app/templates/discussion.hbs deleted file mode 100644 index 67084ba..0000000 --- a/ember/app/templates/discussion.hbs +++ /dev/null @@ -1,19 +0,0 @@ - - -
    -{{#each item in postStream}} - - {{#view "discussion-item" item=item}} - {{#if item.post}}{{view "discussion-post" post=item.post}}{{/if}} - {{/view}} - -{{/each}} -
    - -{{#if postStream.lastLoaded}} -
    - {{menu-list items=view.footerControls class="list-inline"}} -
    -{{/if}} diff --git a/ember/app/templates/discussions-header.hbs b/ember/app/templates/discussions-header.hbs deleted file mode 100644 index 588e2f3..0000000 --- a/ember/app/templates/discussions-header.hbs +++ /dev/null @@ -1,7 +0,0 @@ -

    - {{#if searchQuery}} - {{fa-icon "search"}} {{searchQuery}} - {{else}} - All Discussions - {{/if}} -

    diff --git a/ember/app/templates/discussions-nav.hbs b/ember/app/templates/discussions-nav.hbs deleted file mode 100644 index a95a957..0000000 --- a/ember/app/templates/discussions-nav.hbs +++ /dev/null @@ -1,4 +0,0 @@ - -{{#link-to "discussions"}}{{index}} of {{count}} {{view.type}}{{/link-to}} -{{#link-to "discussion" previous classNameBindings="previous::disabled"}}{{fa-icon "chevron-up"}}{{/link-to}} -{{#link-to "discussion" next classNameBindings="next::disabled"}}{{fa-icon "chevron-down"}}{{/link-to}} diff --git a/ember/app/templates/discussions-result.hbs b/ember/app/templates/discussions-result.hbs deleted file mode 100644 index 2acaef4..0000000 --- a/ember/app/templates/discussions-result.hbs +++ /dev/null @@ -1,60 +0,0 @@ -{{!--
    - - {{menu-list items=view.controls class="dropdown-menu pull-right"}} -
    --}} - -
    - - {{!-- {{#if view.action}} - - {{partial view.action}} - - {{/if}} --}} - - - {{#if discussion.unread}} - {{discussion.unread}} - {{/if}} - - - {{#link-to "discussion" discussion.content (query-params searchQuery=searchQuery) class="info"}} - {{#if discussion.category}} - {{discussion.category}} - {{/if}} - - {{!-- {{#if discussion.following}}{{fa-icon "star" class="icon icon-following"}}{{/if}} --}} - {{#if discussion.sticky}}{{fa-icon "thumb-tack" class="icon icon-sticky"}}{{/if}} - {{#if discussion.locked}}{{fa-icon "lock" class="icon icon-locked"}}{{/if}} - {{highlight-words discussion.title searchQuery}} - - {{#if discussion.sticky}} - {{discussion.excerpt}} - {{/if}} - {{/link-to}} - - - {{#if displayStartPosts}} - {{#link-to "user" discussion.startUser}}{{user-avatar discussion.startUser class="avatar-thumb"}}{{/link-to}} - {{#link-to "discussion" discussion.content}}{{abbreviate-time discussion.startTime}}{{/link-to}} - {{else}} - {{#link-to "user" discussion.lastUser}}{{user-avatar discussion.lastUser class="avatar-thumb"}}{{/link-to}} - {{#link-to "discussion" discussion.content (query-params start="last")}}{{abbreviate-time discussion.lastTime}}{{/link-to}} - {{/if}} - - - {{abbreviate-number discussion.repliesCount}} - - {{#if view.relevantPosts}} -
    - {{#each post in view.relevantPosts}} - {{#link-to "discussion" discussion.content (query-params start=post.number) class="post item"}} - {{user-avatar post.user class="avatar-thumb"}} - {{highlight-words post.relevantContent searchQuery}} - {{/link-to}} - {{/each}} -
    - {{/if}} - - {{render-hook "discussions-result"}} - -
    diff --git a/ember/app/templates/discussions.hbs b/ember/app/templates/discussions.hbs deleted file mode 100644 index 2f6891c..0000000 --- a/ember/app/templates/discussions.hbs +++ /dev/null @@ -1,46 +0,0 @@ -
    - - - {{#if resultsLoading}} - {{loading-indicator size="small"}} - {{else}} -
      - {{#each discussion in content}} - {{view "discussions-result" discussion=discussion}} - {{/each}} -
    - - {{#if moreResults}} -
    - {{#if loadingMore}} - {{loading-indicator size="small"}} - {{else}} - Load More - {{/if}} -
    - {{/if}} - {{/if}} -
    - -
    - {{liquid-outlet}} -
    diff --git a/ember/app/templates/error.hbs b/ember/app/templates/error.hbs deleted file mode 100644 index de7c858..0000000 --- a/ember/app/templates/error.hbs +++ /dev/null @@ -1,5 +0,0 @@ -

    Oops! Something went wrong.

    - -

    {{message}}

    - -
    {{stack}}
    diff --git a/ember/app/templates/loading.hbs b/ember/app/templates/loading.hbs deleted file mode 100644 index 646f8ba..0000000 --- a/ember/app/templates/loading.hbs +++ /dev/null @@ -1 +0,0 @@ -{{loading-indicator size="large"}} diff --git a/ember/app/templates/login.hbs b/ember/app/templates/login.hbs deleted file mode 100644 index f81da32..0000000 --- a/ember/app/templates/login.hbs +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/ember/app/templates/session.hbs b/ember/app/templates/session.hbs deleted file mode 100644 index f8b1f95..0000000 --- a/ember/app/templates/session.hbs +++ /dev/null @@ -1,36 +0,0 @@ -
    - {{#if session.user}} - - {{else}} -
    -
    - Log In -
    -
    - Sign Up -
    -
    - {{/if}} -
    diff --git a/ember/app/transitions.js b/ember/app/transitions.js deleted file mode 100644 index 52c15b3..0000000 --- a/ember/app/transitions.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function() { - this.transition( - this.fromRoute('discussions-sidebar'), - this.toRoute('discussion-sidebar'), - this.use('slideLeft') - ); - this.transition( - this.fromRoute('discussions'), - this.toRoute('discussion'), - this.use('slideLeft') - ); -} diff --git a/ember/app/transitions/slide-left.js b/ember/app/transitions/slide-left.js deleted file mode 100644 index 4fca2cf..0000000 --- a/ember/app/transitions/slide-left.js +++ /dev/null @@ -1,2 +0,0 @@ -import { curryTransition } from "vendor/liquid-fire"; -export default curryTransition('slide', 'x', -1, { duration: 300 }); diff --git a/ember/app/transitions/slide-right.js b/ember/app/transitions/slide-right.js deleted file mode 100644 index 5be9c7b..0000000 --- a/ember/app/transitions/slide-right.js +++ /dev/null @@ -1,2 +0,0 @@ -import { curryTransition } from "vendor/liquid-fire"; -export default curryTransition('slide', 'x', 1, { duration: 300 }); diff --git a/ember/app/transitions/slide.js b/ember/app/transitions/slide.js deleted file mode 100644 index 9026f0b..0000000 --- a/ember/app/transitions/slide.js +++ /dev/null @@ -1,48 +0,0 @@ -import { stop, animate, Promise, isAnimating, finish } from "vendor/liquid-fire"; - -export default function slide(oldView, insertNewView, dimension, direction, opts) { - var oldParams = {}, - newParams = {}, - firstStep, - property, - measure; - - if (dimension.toLowerCase() === 'x') { - property = 'translateX'; - measure = 'width'; - } else { - property = 'translateY'; - measure = 'height'; - } - - if (isAnimating(oldView, 'moving-in')) { - firstStep = finish(oldView, 'moving-in'); - } else { - stop(oldView); - firstStep = Promise.cast(); - } - - - return firstStep.then(insertNewView).then(function(newView){ - // if (newView && newView.$() && oldView && oldView.$()) { - // var sizes = [parseInt(newView.$().css(measure), 10), - // parseInt(oldView.$().css(measure), 10)]; - // var bigger = Math.max.apply(null, sizes); - var bigger = 20; - oldParams[property] = (bigger * direction) + 'px'; - newParams[property] = ["0px", (-1 * bigger * direction) + 'px']; - // } - // else { - // oldParams[property] = (100 * direction) + '%'; - // newParams[property] = ["0%", (-100 * direction) + '%']; - // } - - oldParams['opacity'] = [0, 1]; - newParams['opacity'] = [1, 0]; - - return Promise.all([ - animate(oldView, oldParams, opts), - animate(newView, newParams, opts, 'moving-in') - ]); - }); -} diff --git a/ember/app/utils/.gitkeep b/ember/app/utils/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/utils/menu.js b/ember/app/utils/menu.js deleted file mode 100644 index ec1b655..0000000 --- a/ember/app/utils/menu.js +++ /dev/null @@ -1,33 +0,0 @@ -import Ember from 'ember'; - -import NamedContainerView from './named-container-view'; -import MenuItemSeparator from '../components/menu-item-separator'; - -export default NamedContainerView.extend({ - - tagName: 'ul', - - active: null, - - i: 1, - addSeparator: function(index) { - var item = MenuItemSeparator; - this.addItem('separator'+(this.i++), item, index); - }, - - activeChanged: function() { - var active = this.get('active'); - if (typeof active != 'array') { - active = [active]; - } - - var namedViews = this.get('namedViews'); - var view; - for (var name in namedViews) { - if (namedViews.hasOwnProperty(name) && (view = namedViews.get(name))) { - view.set('active', active.indexOf(name) !== -1); - } - } - }.observes('active') - -}); diff --git a/ember/app/utils/named-container-view.js b/ember/app/utils/named-container-view.js deleted file mode 100644 index 539d4d5..0000000 --- a/ember/app/utils/named-container-view.js +++ /dev/null @@ -1,65 +0,0 @@ -import Ember from 'ember'; - -export default Ember.ArrayProxy.extend({ - - content: null, - - namedViews: null, - - init: function() { - this.set('content', Ember.A()); - this.set('namedViews', Ember.Object.create()); - this._super(); - }, - - // Add an item to the container. - addItem: function(name, viewClass, index) { - // view = this.createChildView(view); - - if (typeof index == 'undefined') { - index = this.get('length'); - } - this.replace(index, 0, [viewClass]); - this.get('namedViews').set(name, viewClass); - }, - - // Remove an item from the container. - removeItem: function(name) { - this.removeObject(this.get('namedViews').get(name)); - this.get('namedViews').set(name, null); - }, - - // Replace an item in the container with another one. - replaceItem: function(name, viewClass) { - // view = this.createChildView(view); - - var oldView = this.get('namedViews').get(name); - var index = this.indexOf(oldView); - this.replace(index, 1, [viewClass]) - this.get('namedViews').set(name, viewClass); - }, - - // Move an item in the container to a new position. - moveItem: function(name, index) { - var view = this.get('namedViews').get(name); - this.removeItem(name); - this.addItem(name, view, index); - }, - - firstItem: function() { - return this.objectAt(0); - }.property(), - - secondItem: function() { - return this.objectAt(1); - }.property(), - - remainingItems: function() { - return this.slice(2); - }.property(), - - getItem: function(name) { - return this.get('namedViews').get(name); - } - -}); diff --git a/ember/app/utils/plugin.js b/ember/app/utils/plugin.js deleted file mode 100644 index 74cf197..0000000 --- a/ember/app/utils/plugin.js +++ /dev/null @@ -1,5 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Object.extend({ - -}); diff --git a/ember/app/utils/scrollbar.js b/ember/app/utils/scrollbar.js deleted file mode 100644 index 9bbbba8..0000000 --- a/ember/app/utils/scrollbar.js +++ /dev/null @@ -1,62 +0,0 @@ -// TODO probably change this into an Ember object/merge it into discussion-scrollbar - -var Scrollbar = function(element) { - this.$ = $(element); - this.count = 1; - this.index = 0; - this.visible = 1; - this.disabled = false; -}; - -Scrollbar.prototype = { - - setIndex: function(index) { - this.index = index; - }, - - setVisible: function(visible) { - this.visible = visible; - }, - - setCount: function(count) { - this.count = count; - }, - - setDisabled: function(disabled) { - this.disabled = disabled; - }, - - percentPerPost: function() { - // To stop the slider of the scrollbar from getting too small when there - // are many posts, we define a minimum percentage height for the slider - // calculated from a 50 pixel limit. Subsequently, we can calculate the - // minimum percentage per visible post. If this is greater than the - // actual percentage per post, then we need to adjust the 'before' - // percentage to account for it. - var minPercentVisible = 50 / this.$.outerHeight() * 100; - var percentPerVisiblePost = Math.max(100 / this.count, minPercentVisible / this.visible); - var percentPerPost = this.count == this.visible ? 0 : (100 - percentPerVisiblePost * this.visible) / (this.count - this.visible); - - return { - index: percentPerPost, - visible: percentPerVisiblePost - }; - }, - - update: function(animate) { - var percentPerPost = this.percentPerPost(); - - var before = percentPerPost.index * this.index, - slider = Math.min(100 - before, percentPerPost.visible * this.visible), - func = animate ? 'animate' : 'css'; - - this.$.find('.scrollbar-before').stop(true)[func]({height: before+'%'}).css('overflow', 'visible'); - this.$.find('.scrollbar-slider').stop(true)[func]({height: slider+'%'}).css('overflow', 'visible'); - this.$.find('.scrollbar-after').stop(true)[func]({height: (100 - before - slider)+'%'}).css('overflow', 'visible'); - - this.$.toggleClass('disabled', this.disabled || slider >= 100); - } - -}; - -export default Scrollbar; diff --git a/ember/app/views/.gitkeep b/ember/app/views/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ember/app/views/application.js b/ember/app/views/application.js deleted file mode 100644 index 1a9d1a9..0000000 --- a/ember/app/views/application.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - - title: function() { - return this.get('controller.forumTitle'); - }.property('controller.forumTitle') - -}); diff --git a/ember/app/views/composer.js b/ember/app/views/composer.js deleted file mode 100644 index ee49d24..0000000 --- a/ember/app/views/composer.js +++ /dev/null @@ -1,37 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - - classNames: ['composer'], - classNameBindings: ['controller.showing:showing'], - - showingChanged: function() { - if (this.$()) { - var view = this; - this.$().animate({bottom: this.get('controller.showing') ? 20 : -this.$().height()}, 'fast', function() { - if (view.get('controller.showing')) { - $(this).find('textarea').focus(); - } - }); - $('#body').animate({marginBottom: this.get('controller.showing') ? this.$().height() + 20 : 0}, 'fast'); - } - }.observes('controller.showing'), - - panePinnedChanged: function() { - if (this.$()) { - var discussions = this.get('controller.controllers.discussions'); - var $this = this.$(); - Ember.run.scheduleOnce('afterRender', function() { - var discussion = $('.discussion-pane'); - var width = discussion.length ? discussion.offset().left : $('#body').offset().left; - $this.css('left', width); - }); - } - }.observes('controller.controllers.discussions.paned', 'controller.controllers.discussions.panePinned'), - - didInsertElement: function() { - this.showingChanged(); - this.panePinnedChanged(); - } - -}); diff --git a/ember/app/views/discussion-item.js b/ember/app/views/discussion-item.js deleted file mode 100644 index 060a75d..0000000 --- a/ember/app/views/discussion-item.js +++ /dev/null @@ -1,132 +0,0 @@ -import Ember from 'ember'; - -// A discussion 'item' represents one item in the post stream. In other words, a -// single item may represent a single post, or it may represent a gap of many -// posts which have not been loaded. - -export default Ember.View.extend({ - classNames: ['item'], - classNameBindings: ['item.gap:gap', 'loading', 'direction'], - attributeBindings: [ - 'start:data-start', - 'end:data-end', - 'time:data-time', - 'number:data-number' - ], - - start: function() { - return this.get('item.indexStart'); - }.property('item.indexStart'), - - end: function() { - return this.get('item.indexEnd'); - }.property('item.indexEnd'), - - count: function() { - return this.get('end') - this.get('start') + 1; - }.property('start', 'end'), - - time: function() { - return this.get('item.post.time'); - }.property('item.post.time'), - - number: function() { - return this.get('item.post.number'); - }.property('item.post.number'), - - loading: function() { - return this.get('item.loading'); - }.property('item.loading'), - - direction: function() { - return this.get('item.direction'); - }.property(), - - loadingChanged: function() { - this.rerender(); - }.observes('loading'), - - render: function(buffer) { - if (! this.get('item.gap')) { - return this._super(buffer); - } - - buffer.push(''); - if (this.get('loading')) { - buffer.push(' '); - } else { - buffer.push(this.get('count')+' more post'+(this.get('count') != 1 ? 's' : '')); - } - buffer.push(''); - }, - - didInsertElement: function() { - if (! this.get('item.gap')) { - return; - } - - if (this.get('loading')) { - var view = this; - Ember.run.scheduleOnce('afterRender', function() { - view.$().spin('small'); - }); - } else { - var self = this; - this.$().hover(function(e) { - var up = e.clientY > $(this).offset().top - $(document).scrollTop() + $(this).outerHeight(true) / 2; - self.set('direction', up ? 'up' : 'down'); - }); - } - }, - - load: function(relativeIndex) { - // If this item is not a gap, or if we're already loading its posts, - // then we don't need to do anything. - if (! this.get('item.gap') || this.get('loading')) { - return false; - } - - // If new posts are being loaded in an upwards direction, then when they - // are rendered, the rest of the posts will be pushed down the page. - // However, we want to maintain the current scroll position relative to - // the content after the gap. To do this, we need to find item directly - // after the gap and use it as an anchor. - if (this.get('direction') === 'up') { - var anchor = this.$().nextAll('.item:first'); - - // Immediately after the posts have been loaded (but before they - // have been rendered,) we want to grab the distance from the top of - // the viewport to the top of the anchor element. - this.get('controller.postStream').one('postsLoaded', function() { - if (anchor.length) { - var scrollOffset = anchor.offset().top - $(document).scrollTop(); - } - - // After they have been rendered, we scroll back to a position - // so that the distance from the top of the viewport to the top - // of the anchor element is the same as before. If there is no - // anchor (i.e. this gap is terminal,) then we'll scroll to the - // bottom of the document. - Ember.run.scheduleOnce('afterRender', function() { - $('body').scrollTop(anchor.length - ? anchor.offset().top - scrollOffset - : $('body').height()); - }); - }); - } - - // Tell the controller that we want to load the range of posts that this - // gap represents. We also specify which direction we want to load the - // posts from. - this.get('controller').send( - 'loadRange', - this.get('start') + (relativeIndex || 0), - this.get('end'), - this.get('direction') === 'up' - ); - }, - - click: function() { - this.load(); - } -}); diff --git a/ember/app/views/discussion-post.js b/ember/app/views/discussion-post.js deleted file mode 100644 index 1eeaa96..0000000 --- a/ember/app/views/discussion-post.js +++ /dev/null @@ -1,55 +0,0 @@ -import Ember from 'ember'; - -import Menu from '../utils/menu'; -import MenuItem from '../components/menu-item'; - -export default Ember.View.extend({ - tagName: 'article', - templateName: 'discussion-post', - - controls: null, - - contentComponent: function() { - return 'post-type-'+this.get('post.type'); - }.property('post.type'), - - classNames: ['post'], - classNameBindings: ['post.deleted', 'post.edited'], - - construct: function() { - this.set('controls', Menu.create()); - - var post = this.get('post'); - - if (post.get('deleted')) { - this.addControl('restore', 'Restore', 'reply', 'canEdit'); - this.addControl('delete', 'Delete', 'times', 'canDelete'); - } else { - if (post.get('type') == 'comment') { - this.addControl('edit', 'Edit', 'pencil', 'canEdit'); - this.addControl('hide', 'Delete', 'times', 'canEdit'); - } else { - this.addControl('delete', 'Delete', 'times', 'canDelete'); - } - } - }.on('init'), - - didInsertElement: function() { - this.$().hide().fadeIn('slow'); - }, - - addControl: function(tag, title, icon, permissionAttribute) { - if (permissionAttribute && ! this.get('post').get(permissionAttribute)) { - return; - } - - var self = this; - var action = function(post) { - self.get('controller').send(actionName, post); - }; - - var item = MenuItem.extend({title: title, icon: icon, action: action}); - this.get('controls').addItem(tag, item); - } - -}); diff --git a/ember/app/views/discussion-scrollbar.js b/ember/app/views/discussion-scrollbar.js deleted file mode 100644 index f558918..0000000 --- a/ember/app/views/discussion-scrollbar.js +++ /dev/null @@ -1,280 +0,0 @@ -import Ember from 'ember'; - -import MenuItem from '../components/menu-item'; -import Scrollbar from '../utils/scrollbar'; -import PostStreamMixin from '../mixins/post-stream'; - -export default Ember.View.extend(PostStreamMixin, { - - /** - @property templateName - @type String - */ - templateName: 'discussion-scrollbar', - classNames: ['scrubber', 'discussion-scrubber'], - - // An object which represents/ecapsulates the scrollbar. - scrollbar: null, - - // Right after the controller finished loading a discussion, we want to - // trigger a scroll event on the window so the interface is kept up-to-date. - loadedChanged: function() { - this.scrollbar.setDisabled(! this.get('controller.loaded')); - }.observes('controller.loaded'), - - countChanged: function() { - this.scrollbar.setCount(this.get('controller.postStream.count')); - }.observes('controller.postStream.count'), - - windowWasResized: function(event) { - var view = event.data.view; - view.scrollbar.$.height($('#sidebar-content').height() + $('#sidebar-content').offset().top - view.scrollbar.$.offset().top - 80); - view.scrollbar.update(); - }, - - windowWasScrolled: function(event) { - var view = event.data.view, - $window = $(window); - - if (! view.get('controller.loaded') || $window.data('disableScrollHandler')) { - return; - } - - var scrollTop = $window.scrollTop(), - windowHeight = $window.height(); - - // Before looping through all of the posts, we reset the scrollbar - // properties to a 'default' state. These values reflect what would be - // seen if the browser were scrolled right up to the top of the page, - // and the viewport had a height of 0. - var index = $('.posts .item:first').data('end'); - var visiblePosts = 0; - var period = ''; - - var first = $('.posts .item[data-start=0]'); - var offsetTop = first.length ? first.offset().top : 0; - - // Now loop through each of the items in the discussion. An 'item' is - // either a single post or a 'gap' of one or more posts that haven't - // been loaded yet. - // @todo cache item top positions to speed this up? - $('.posts .item').each(function(k) { - var $this = $(this), - top = $this.offset().top - offsetTop, - height = $this.outerHeight(); - - // 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 - // loop. - if (top + height < scrollTop) { - return; - } - if (top > scrollTop + windowHeight) { - return false; - } - - // If the bottom half of this item is visible at the top of the - // viewport, then add the visible proportion to the visiblePosts - // counter, and set the scrollbar index to whatever the visible - // proportion represents. For example, if a gap represents indexes - // 0-9, and the bottom 50% of the gap is visible in the viewport, - // then the scrollbar index will be 5. - if (top <= scrollTop && top + height > scrollTop) { - visiblePosts = (top + height - scrollTop) / height; - index = parseFloat($this.data('end')) + 1 - visiblePosts; - } - - // If the top half of this item is visible at the bottom of the - // viewport, then add the visible proportion to the visiblePosts - // counter. - else if (top + height >= scrollTop + windowHeight) { - visiblePosts += (scrollTop + windowHeight - top) / height; - } - - // If the whole item is visible in the viewport, then increment the - // visiblePosts counter. - else { - visiblePosts++; - } - - // If this item has a time associated with it, then set the - // scrollbar's current period to a formatted version of this time. - if ($this.data('time')) { - period = $this.data('time'); - } - }); - - // Now that we've looped through all of the items and have worked out - // the scrollbar's current index and the number of posts visible in the - // viewport, we can update the scrollbar. - view.scrollbar.setIndex(index); - view.scrollbar.setVisible(visiblePosts); - view.scrollbar.update(); - - view.scrollbar.$.find('.index').text(Math.ceil(index + visiblePosts)); - view.scrollbar.$.find('.description').text(moment(period).format('MMMM YYYY')); - }, - - mouseWasMoved: function(event) { - var view = event.data.view; - - if ( ! event.data.handle) { - return; - } - - var offsetPixels = event.clientY - event.data.mouseStart; - var offsetPercent = offsetPixels / view.scrollbar.$.outerHeight() * 100; - - var offsetIndex = offsetPercent / view.scrollbar.percentPerPost().index; - var newIndex = Math.max(0, Math.min(event.data.indexStart + offsetIndex, view.scrollbar.count - 1)); - - view.scrollToIndex(newIndex); - }, - - mouseWasReleased: function(event) { - var view = event.data.view; - - if (! event.data.handle) { - return; - } - - event.data.mouseStart = 0; - event.data.indexStart = 0; - event.data.handle = null; - - $(window).data('disableScrollHandler', false); - - view.get('controller').send('jumpToIndex', Math.floor(view.scrollbar.index)); - - $(window).scroll(); - $('body').css('cursor', ''); - }, - - didInsertElement: function() { - var view = this; - - // Set up scrollbar object - this.scrollbar = new Scrollbar($('.discussion-scrubber .scrollbar')); - this.scrollbar.setDisabled(true); - this.countChanged(); - this.loadedChanged(); - - // Whenever the window is resized, adjust the height of the scrollbar - // so that it fills the height of the sidebar. - $(window).on('resize', {view: this}, this.windowWasResized).resize(); - - // Define a handler to update the state of the scrollbar to reflect the - // current scroll position of the page. - $(window).on('scroll', {view: this}, this.windowWasScrolled); - - this.get('controller').on('loadingIndex', this, this.loadingIndex); - - // Now we want to make the scrollbar handle draggable. Let's start by - // preventing default browser events from messing things up. - this.scrollbar.$ - .css('user-select', 'none') - .bind('dragstart mousedown', function(e) { - e.preventDefault(); - }); - - // When the mouse is pressed on the scrollbar handle, we need to capture - // some information about the current position. - var scrollData = { - view: this, - mouseStart: 0, - indexStart: 0, - handle: null - }; - - this.scrollbar.$.find('.scrollbar-slider').css('cursor', 'move').mousedown(function(e) { - scrollData.mouseStart = e.clientY; - scrollData.indexStart = view.scrollbar.index; - scrollData.handle = $(this); - $(window).data('disableScrollHandler', true); - $('body').css('cursor', 'move'); - }); - - // When the mouse moves, - $(document) - .on('mousemove', scrollData, this.mouseWasMoved) - .on('mouseup', scrollData, this.mouseWasReleased); - - // When any part of the whole scrollbar is clicked, we want to jump to - // that position. - this.scrollbar.$.click(function(e) { - - // Calculate the index which we want to jump to. - // @todo document how this complexity works. - var offsetPixels = e.clientY - view.scrollbar.$.offset().top + $('body').scrollTop(); - var offsetPercent = offsetPixels / view.scrollbar.$.outerHeight() * 100; - - var handleHeight = parseFloat(view.scrollbar.$.find('.scrollbar-slider')[0].style.height); - - var offsetIndex = (offsetPercent - handleHeight / 2) / view.scrollbar.percentPerPost().index; - var newIndex = Math.max(0, Math.min(view.scrollbar.count - 1, offsetIndex)); - - view.get('controller').send('jumpToIndex', Math.floor(newIndex)); - }) - - // Exempt the scrollbar handle from this 'jump to' click event. - this.scrollbar.$.find('.scrollbar-slider').click(function(e) { - e.stopPropagation(); - }); - - }, - - actions: { - firstPost: function() { - this.get('controller').send('jumpToIndex', 0); - }, - - lastPost: function() { - this.get('controller').send('jumpToIndex', this.scrollbar.count - 1); - } - }, - - loadingIndex: function(index) { - this.scrollToIndex(index, true); - }, - - // Instantly scroll to a certain index in the discussion. The index doesn't - // have to be an integer; any fraction of a post will be scrolled to. - scrollToIndex: function(index, animate) { - index = Math.max(0, Math.min(index, this.scrollbar.count - 1)); - var indexFloor = Math.floor(index); - - // Find - var nearestItem = this.findNearestToIndex(indexFloor); - var first = $('.posts .item[data-start=0]'); - var offsetTop = first.length ? first.offset().top : 0; - - var pos = nearestItem.offset().top - offsetTop; - if (! nearestItem.is('.gap')) { - pos += nearestItem.outerHeight() * (index - indexFloor); - } else { - nearestItem.addClass('active'); - } - - $('.posts .item.gap').not(nearestItem).removeClass('active'); - - if (animate) { - // $('html, body').animate({scrollTop: pos}); - } else { - $('html, body').scrollTop(pos); - } - this.scrollbar.setIndex(index); - this.scrollbar.update(animate); - }, - - willDestroyElement: function() { - $(window) - .off('resize', this.windowWasResized) - .off('scroll', this.windowWasScrolled); - - $(document) - .off('mousemove', this.mouseWasMoved) - .off('mouseup', this.mouseWasReleased); - - this.get('controller').off('loadingIndex', this, this.loadingIndex); - } -}); diff --git a/ember/app/views/discussion-sidebar.js b/ember/app/views/discussion-sidebar.js deleted file mode 100644 index c798e85..0000000 --- a/ember/app/views/discussion-sidebar.js +++ /dev/null @@ -1,50 +0,0 @@ -import Ember from 'ember'; - -import NamedContainerView from '../utils/named-container-view'; -import Menu from '../utils/menu'; -import MenuSplit from '../components/menu-split'; -import MenuItem from '../components/menu-item'; -import DiscussionScrollbar from './discussion-scrollbar'; - -export default Ember.View.extend({ - - // NamedContainerView which will be rendered in the template. - content: null, - controls: null, - - template: Ember.Handlebars.compile('{{menu-list items=view.toolbar class="toolbar"}}{{menu-list items=view.content class="body"}}'), - - construct: function() { - this.set('toolbar', NamedContainerView.create()); - this.set('content', NamedContainerView.create()); - this.set('controls', Menu.create()); - }.on('init'), - - didInsertElement: function() { - var view = this; - var toolbar = this.get('toolbar'); - var content = this.get('content'); - - var ReplyItem = MenuItem.extend({ - title: 'Reply', - icon: 'reply', - classNameBindings: ['className', 'replying:disabled'], - replying: function() { - return this.get('parentController.controllers.composer.showing'); - }.property('parentController.controllers.composer.showing'), - action: function() { view.get('controller').send('reply'); }, - parentController: this.get('controller'), - }); - this.get('controls').addItem('reply', ReplyItem); - - toolbar.addItem('menu', MenuSplit.extend({ - items: this.get('controls'), - classNames: ['discussion-controls'] - })); - - toolbar.addItem('scrollbar', DiscussionScrollbar.extend({ - controller: this.get('controller') - })); - } - -}); diff --git a/ember/app/views/discussion.js b/ember/app/views/discussion.js deleted file mode 100644 index 89d2801..0000000 --- a/ember/app/views/discussion.js +++ /dev/null @@ -1,237 +0,0 @@ -import Ember from 'ember'; - -import Menu from '../utils/menu'; -import MenuItem from '../components/menu-item'; -import PostStreamMixin from '../mixins/post-stream'; - -export default Ember.View.extend(Ember.Evented, PostStreamMixin, { - - // Set up a new menu view that will contain controls to be shown in the - // footer. The template will only render these controls if the last post is - // showing. - construct: function() { - // this.set('footerControls', this.createChildView(Menu)); - // this.set('footerControls.controller', this.get('controller')); - // console.log(this.get('controller')); - }.on('init'), - - // Whenever the model's title changes, we want to update that document's - // title the reflect the new title. - updateTitle: function() { - this.set('controller.controllers.application.pageTitle', this.get('controller.model.title')); - }.observes('controller.model.title'), - - didInsertElement: function() { - - this.set('footerControls', Menu.create()); - - // We've just inserted the discussion view. Let's start off by - // populating the footer controls menu object. - this.trigger('populateControls', this.get('footerControls')); - - // Whenever the window's scroll position changes, we want to check to - // see if any terminal 'gaps' are in the viewport and trigger their - // loading mechanism if they are. We also want to update the - // controller's 'start' query param with the current position. - $(window).on('scroll', {view: this}, this.windowWasScrolled); - - // We need to listen for some events on the controller. Whenever the - // controller says that it's loading or has loaded posts near a certain - // post number, we want to scroll down to this post (or the gap which - // the post is in) and highlight it. - var controller = this.get('controller'); - controller.on('loadingNumber', this, this.loadingNumber); - controller.on('loadedNumber', this, this.loadedNumber); - controller.on('loadingIndex', this, this.loadingIndex); - controller.on('loadedIndex', this, this.loadedIndex); - }, - - willDestroyElement: function() { - $(window).off('scroll', this.windowWasScrolled); - - var controller = this.get('controller'); - controller.off('loadingNumber', this, this.loadingNumber); - controller.off('loadedNumber', this, this.loadedNumber); - controller.off('loadingIndex', this, this.loadingIndex); - controller.off('loadedIndex', this, this.loadedIndex); - }, - - // By default, we just populate the footer controls with a 'reply' button. - addDefaultControls: function(controls) { - var view = this; - var ReplyItem = MenuItem.extend({ - title: 'Reply', - icon: 'reply', - className: 'btn btn-primary', - classNameBindings: ['className', 'replying:disabled'], - replying: function() { - return this.get('parentController.controllers.composer.showing'); - }.property('parentController.controllers.composer.showing'), - action: function() { - var lastPost = $('.posts .item:last'); - $('html, body').animate({scrollTop: lastPost.offset().top + lastPost.outerHeight() - $(window).height() + $('.composer').height() + 19}, 'fast'); - view.get('controller').send('reply'); - }, - parentController: this.get('controller'), - }); - controls.addItem('reply', ReplyItem); - }.on('populateControls'), - - // This function handles the window's scroll event. We check to see if any - // terminal 'gaps' are in the viewport and trigger their loading mechanism - // if they are. We also update the controller's 'start' query param with the - // current position. - windowWasScrolled: function(event) { - var view = event.data.view; - - if (! view.get('controller.loaded') || $(window).data('disableScrollHandler')) { - return; - } - - var posts = view.$().find('.posts'), - $this = $(this), - scrollTop = $this.scrollTop(), - viewportHeight = $this.height(), - firstItem = posts.find('.item[data-start=0]'), - firstItemOffset = firstItem.length ? firstItem.offset().top : 0, - currentNumber; - - // Loop through each of the items in the discussion. An 'item' is - // either a single post or a 'gap' of one or more posts that haven't - // been loaded yet. - posts.find('.item').each(function() { - var $this = $(this), - top = $this.offset().top - firstItemOffset, - height = $this.outerHeight(); - - // If this item is above the top of the viewport, skip to the - // next one. If it's below the bottom of the viewport, break - // out of the loop. - if (top + height < scrollTop) { - return; - } - if (top > scrollTop + viewportHeight) { - return false; - } - - // Now we know that this item is in the viewport. If we haven't - // yet stored a post's number, then this item must be the FIRST - // item in the viewport. Therefore, we'll grab its post number - // so we can update the controller's state later. - ! currentNumber && (currentNumber = $this.data('number')); - - // If this item is a gap, then we may proceed to check if it's - // a *terminal* gap and trigger its loading mechanism. - var gapView; - if ($this.hasClass('gap') && (gapView = Ember.View.views[$this.attr('id')])) { - if ($this.is(':first-of-type')) { - gapView.set('direction', 'up').load(); - } - else if ($this.is(':last-of-type')) { - gapView.set('direction', 'down').load(); - } - } - }); - - // Finally, we want to update the controller's state with regards to the - // current viewing position of the discussion. However, we don't want to - // do this on every single scroll event as it will slow things down. So, - // let's do it at a minimum of 250ms by clearing and setting a timeout. - clearTimeout(this.updateStateTimeout); - this.updateStateTimeout = setTimeout(function() { - view.get('controller').set('start', currentNumber || 1); - }, 250); - }, - - loadingNumber: function(number) { - // The post with this number is being loaded. We want to scroll to where - // we think it will appear. We may be scrolling to the edge of the page, - // but we don't want to trigger any terminal post gaps to load by doing - // that. So, we'll disable the window's scroll handler for now. - $(window).data('disableScrollHandler', true); - - this.jumpToNumber(number); - }, - - loadedNumber: function(number) { - // The post with this number has been loaded. After we scroll to this - // post, we want to resume scroll events. - var view = this; - this.jumpToNumber(number, function() { - $(window).data('disableScrollHandler', false).scroll(); - }); - }, - - // Scroll down to a certain post (or the gap which the post is in) and - // highlight it. - jumpToNumber: function(number, finish) { - // Clear the highlight class from all posts, and attempt to find and - // highlight a post with the specified number. - var item = this.$() - .find('.posts .item').removeClass('highlight') - .filter('[data-number='+number+']'); - - if (number > 1) { - item.addClass('highlight'); - } - - // If we didn't have any luck, then a post with this number either - // doesn't exist, or it hasn't been loaded yet. We'll find the item - // that's closest to the post with this number and scroll to that - // instead. - if (! item.length) { - item = this.findNearestToNumber(number); - } - - // We have an item to scroll to now. Let's get its position and animate - // a scroll-down! - if (item.length) { - $('html, body').stop(true).animate({scrollTop: number > 1 ? item.offset().top : 0}); - } - if (finish) { - $('html, body').promise().done(finish); - } - }, - - loadingIndex: function(index) { - // The post at this index is being loaded. We want to scroll to where we - // think it will appear. We may be scrolling to the edge of the page, - // but we don't want to trigger any terminal post gaps to load by doing - // that. So, we'll disable the window's scroll handler for now. - $(window).data('disableScrollHandler', true); - - this.jumpToIndex(index); - }, - - loadedIndex: function(index) { - // The post at this index has been loaded. After we scroll to this post, - // we want to resume scroll events. - var view = this; - this.jumpToIndex(index, function() { - $(window).data('disableScrollHandler', false).scroll(); - }); - }, - - jumpToIndex: function(index, finish) { - var item = this.findNearestToIndex(index); - - // We have an item to scroll to now. Let's get its position and animate - // a scroll-down! - if (item.length) { - $('html, body').stop(true).animate({scrollTop: index > 0 ? item.offset().top : 0}); - } - if (finish) { - $('html, body').promise().done(finish); - } - }, - - // Right after the controller finished loading a discussion, we want to - // trigger a scroll event on the window so the interface is kept up-to-date. - loadedChanged: function() { - if (this.get('controller.loaded')) { - Ember.run.scheduleOnce('afterRender', function() { - $(window).scroll(); - }); - } - }.observes('controller.loaded') -}); diff --git a/ember/app/views/discussions-nav.js b/ember/app/views/discussions-nav.js deleted file mode 100644 index fdbe149..0000000 --- a/ember/app/views/discussions-nav.js +++ /dev/null @@ -1,22 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - - classNames: ['search-nav'], - templateName: 'discussions-nav', - - type: 'discussions', - - mouseEnter: function() { - clearTimeout(this.get('controller.paneTimeout')); - this.set('controller.paneShowing', true); - }, - - mouseLeave: function() { - var view = this; - this.set('controller.paneTimeout', setTimeout(function() { - view.set('controller.paneShowing', false); - }, 500)); - } - -}); diff --git a/ember/app/views/discussions-result.js b/ember/app/views/discussions-result.js deleted file mode 100644 index 60c3241..0000000 --- a/ember/app/views/discussions-result.js +++ /dev/null @@ -1,154 +0,0 @@ - -import Ember from 'ember'; -import Menu from '../utils/menu'; -import MenuItem from '../components/menu-item'; - -export default Ember.View.extend({ - - _init: function() { - this.set('controls', Menu.create()); - }.on('init'), - - tagName: 'li', - attributeBindings: ['discussionId:data-id'], - classNameBindings: [ - 'discussion.unread:unread', - 'discussion.sticky:sticky', - 'discussion.locked:locked', - 'discussion.following:following', - 'active' - ], - templateName: 'discussions-result', - - active: function() { - return this.get('childViews').anyBy('active'); - }.property('childViews.@each.active'), - - discussionId: function() { - return this.get('discussion.id'); - }.property('discussion.id'), - - relevantPosts: function() { - if (this.get('controller.show') != 'posts') return []; - - if (this.get('controller.searchQuery')) { - return this.get('discussion.relevantPosts'); - } else if (this.get('controller.sort') == 'newest' || this.get('controller.sort') == 'oldest') { - return [this.get('discussion.startPost')]; - } else { - return [this.get('discussion.lastPost')]; - } - }.property('discussion.relevantPosts', 'discussion.startPost', 'discussion.lastPost'), - - icon: function() { - if (this.get('discussion.unread')) return 'circle'; - }.property('discussion.unread'), - - iconAction: function() { - if (this.get('discussion.unread')) return function() { - - }; - }.property('discussion.unread'), - - categoryClass: function() { - return 'category-'+this.get('discussion.category').toLowerCase(); - }.property('discussion.category'), - - didInsertElement: function() { - this.$().hide().fadeIn('slow'); - - this.$().find('.terminal-post a').tooltip(); - - var view = this; - this.$().find('a.info, .terminal-post a').click(function() { - view.set('controller.paneShowing', false); - }); - - // https://github.com/nolimits4web/Framework7/blob/master/src/js/swipeout.js - this.$().find('.discussion').on('touchstart mousedown', function(e) { - var isMoved = false; - var isTouched = true; - var isScrolling = undefined; - var touchesStart = { - x: e.type === 'touchstart' ? e.originalEvent.targetTouches[0].pageX : e.pageX, - y: e.type === 'touchstart' ? e.originalEvent.targetTouches[0].pageY : e.pageY, - }; - var touchStartTime = (new Date()).getTime(); - - $(this).on('touchmove mousemove', function(e) { - if (! isTouched) return; - $(this).find('a.info').removeClass('pressed'); - var touchesNow = { - x: e.type === 'touchmove' ? e.originalEvent.targetTouches[0].pageX : e.pageX, - y: e.type === 'touchmove' ? e.originalEvent.targetTouches[0].pageY : e.pageY, - }; - if (typeof isScrolling === 'undefined') { - isScrolling = !!(isScrolling || Math.abs(touchesNow.y - touchesStart.y) > Math.abs(touchesNow.x - touchesStart.x)); - } - if (isScrolling) { - isTouched = false; - return; - } - - isMoved = true; - e.preventDefault(); - - var diffX = touchesNow.x - touchesStart.x; - var translate = diffX; - var actionsRightWidth = 150; - - if (translate < -actionsRightWidth) { - translate = -actionsRightWidth - Math.pow(-translate - actionsRightWidth, 0.8); - } - - $(this).css('left', translate); - }); - - $(this).on('touchend mouseup', function(e) { - $(this).off('touchmove mousemove touchend mouseup'); - $(this).find('a.info').removeClass('pressed'); - if (!isTouched || !isMoved) { - isTouched = false; - isMoved = false; - return; - } - isTouched = false; - // isMoved = false; - - if (isMoved) { - e.preventDefault(); - $(this).animate({left: -150}); - } - }); - $(this).find('a.info').addClass('pressed').on('click', function(e) { - if (isMoved) { - e.preventDefault(); - e.stopImmediatePropagation(); - } - $(this).off('click'); - }); - }); - - var discussion = this.get('discussion'); - - var controls = this.get('controls'); - - controls.addItem('sticky', MenuItem.extend({title: 'Sticky', icon: 'thumb-tack', action: 'sticky'})); - controls.addItem('lock', MenuItem.extend({title: 'Lock', icon: 'lock', action: 'lock'})); - - controls.addSeparator(); - - controls.addItem('delete', MenuItem.extend({title: 'Delete', icon: 'times', className: 'delete', action: function() { - // this.get('controller').send('delete', discussion); - var discussion = view.$().slideUp().find('.discussion'); - discussion.css('position', 'relative').animate({left: -discussion.width()}); - }})); - }, - - actions: { - icon: function() { - this.get('iconAction')(); - } - } - -}); diff --git a/ember/app/views/discussions-sidebar.js b/ember/app/views/discussions-sidebar.js deleted file mode 100644 index 438b6f2..0000000 --- a/ember/app/views/discussions-sidebar.js +++ /dev/null @@ -1,127 +0,0 @@ -import Ember from 'ember'; - -import NamedContainerView from '../utils/named-container-view'; -import Menu from '../utils/menu'; -import NavItem from '../components/nav-item'; -import ButtonItem from '../components/button-item'; -import MenuList from '../components/menu-list'; -import ItemCollection from '../components/item-collection'; - -export default Ember.View.extend({ - - // NamedContainerView which will be rendered in the template. - content: null, - - template: Ember.Handlebars.compile('{{menu-list items=view.toolbar class="toolbar"}}{{menu-list items=view.content class="body"}}'), - - construct: function() { - this.set('toolbar', NamedContainerView.create()); - this.set('content', NamedContainerView.create()); - }.on('init'), - - didInsertElement: function() { - var self = this; - var content = this.get('content'); - var toolbar = this.get('toolbar'); - - // Add the 'New Discussion' button. When clicked, this will trigger the - // application's composer or something - toolbar.addItem('newDiscussion', ButtonItem.extend({ - title: 'New Discussion', - icon: 'plus', - class: 'btn-default btn-block', - action: function() { - self.set('controller.controllers.composer.showing', true); - }, - disabled: function() { - return this.get('parentController.controllers.composer.showing'); - }.property('parentController.controllers.composer.showing'), - parentController: this.get('controller') - })); - - // Add the discussions navigation list. - var nav = Menu.create(); - - nav.addItem('all', NavItem.extend({ - title: 'All Discussions', - icon: 'comments-o', - linkTo: '"discussions" (query-params filter="")' - })); - - nav.addItem('private', NavItem.extend({ - title: 'Private', - icon: 'envelope-o', - linkTo: '"discussions" (query-params filter="private")' - })); - - nav.addItem('following', NavItem.extend({ - title: 'Following', - icon: 'star', - linkTo: '"discussions" (query-params filter="following")' - })); - - nav.addItem('categories', NavItem.extend({ - title: 'Categories', - icon: 'reorder', - linkTo: '"categories"' - })); - - content.addItem('nav', ItemCollection.extend({classNames: ['nav-list'], items: nav})); - - // var tree = { - // 'Flarum': ['Announcements', 'General', 'Support', 'Feedback'], - // 'Extend': ['Core', 'Plugins', 'Themes'] - // }; - - var tree = { - 'Ninetech': ['Announcements', 'Sales', 'General', 'Off-Topic'], - 'Development': ['Getting Started', 'Databases', 'Targets', 'Add-Ons'] - }; - - // var tree = { - // 'TV Addicts': ['General'], - // 'Shows': ['Breaking Bad', 'Game of Thrones', 'Doctor Who', 'Sherlock', 'Arrested Development', '72 more...'] - // }; - - // var tree = { - // 'Categories': ['GameToAid', 'General', 'Journals', 'Gaming', 'Technology', 'Music', 'Movies, TV & Books'] - // }; - - // var tree = { - // 'Society': ['News', 'Committee', 'General'], - // 'Year Levels': ['First Year', 'Second Year', 'Third Year', 'Fourth Year', 'Fifth Year', 'Sixth Year', 'Honours'] - // }; - - var items = Menu.create(); - - var CategoryNavItem = NavItem.extend({ - iconTemplate: function() { - return ''; - // return ''; - }.property('title'), - linkTo: '"discussions" (query-params filter="category")' - }); - - for (var section in tree) { - var categories = tree[section]; - var categoryItems = Menu.create(); - - categories.forEach(function(category) { - categoryItems.addItem(category.replace(/\./g, ''), CategoryNavItem.extend({ - title: category - })); - }); - - items.addItem(section, Ember.View.extend({ - tagName: 'li', - template: Ember.Handlebars.compile('{{view.title}}{{item-collection items=view.items}}'), - title: section, - items: categoryItems - })); - } - - content.addItem('categories', ItemCollection.extend({classNames: ['nav-list', 'nav-list-small', 'categories'], items: items})); - - } - -}); diff --git a/ember/app/views/discussions.js b/ember/app/views/discussions.js deleted file mode 100644 index abe05c1..0000000 --- a/ember/app/views/discussions.js +++ /dev/null @@ -1,57 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - - classNameBindings: ['pinned'], - - pinned: function() { - return this.get('controller.panePinned'); - }.property('controller.panePinned'), - - didInsertElement: function() { - - var view = this; - - this.$().find('.discussions-pane').on('mouseenter', function() { - if (! $(this).hasClass('paned')) return; - clearTimeout(view.get('controller.paneTimeout')); - view.set('controller.paneShowing', true); - }).on('mouseleave', function() { - view.set('controller.paneShowing', false); - }); - - if (this.get('controller.test') !== null) { - var row = this.$().find('li[data-id='+this.get('controller.controllers.application.resultStream.currentResult.id')+']'); - if (row.length) { - row.addClass('highlight'); - } - // TODO: work out if the highlighted row is in view of the saved scroll position. - // If it isn't, don't use the saved scroll position - generate a new one. - $(window).scrollTop(this.get('controller.test')); - this.set('controller.test', null); - } - - var self = this; - - $(window).on('scroll.loadMore', function() { - if (self.get('controller.loadingMore') || ! self.get('controller.moreResults')) { - return; - } - - var w = $(window), - d = $('.discussions'), - curPos = w.scrollTop() + w.height(), - endPos = d.offset().top + d.height() - 200; - - if (curPos > endPos) { - self.get('controller').send('loadMore'); - } - }); - }, - - willDestroyElement: function() { - this.set('controller.test', $(window).scrollTop()); - $(window).off('scroll.loadMore'); - } - -}); diff --git a/ember/app/views/discussions/index.js b/ember/app/views/discussions/index.js deleted file mode 100644 index 59291a9..0000000 --- a/ember/app/views/discussions/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - _updateTitle: function() { - var q = this.get('controller.searchQuery'); - this.get('controller.controllers.application').set('pageTitle', q ? '"'+q+'"' : ''); - }.observes('controller.searchQuery'), - - didInsertElement: function() { - this._updateTitle(); - } -}); diff --git a/ember/app/views/login.js b/ember/app/views/login.js deleted file mode 100644 index f0c3652..0000000 --- a/ember/app/views/login.js +++ /dev/null @@ -1,29 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - classNames: ['modal', 'fade'], - templateName: 'login', - - didInsertElement: function() { - var self = this; - this.$().modal('show').on('hidden.bs.modal', function() { - self.get('controller').send('closeModal'); - }); - - this.get('controller.session').on('sessionAuthenticationSucceeded', this, this.hide); - }, - - willDestroyElement: function() { - this.get('controller.session').off('sessionAuthenticationSucceeded', this, this.hide) - }, - - hide: function() { - this.$().modal('hide'); - }, - - actions: { - close: function() { - this.$().modal('hide'); - } - } -}); diff --git a/ember/app/views/session.js b/ember/app/views/session.js deleted file mode 100644 index e5377f4..0000000 --- a/ember/app/views/session.js +++ /dev/null @@ -1,5 +0,0 @@ -import Ember from 'ember'; - -export default Ember.View.extend({ - templateName: 'session' -}); diff --git a/ember/bower.json b/ember/bower.json deleted file mode 100644 index be21f6e..0000000 --- a/ember/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "flarum", - "dependencies": { - "handlebars": "2.0.0", - "jquery": "^1.11.1", - "ember": "1.9.0", - "ember-data": "1.0.0-beta.12", - "ember-resolver": "~0.1.10", - "loader.js": "stefanpenner/loader.js#1.0.1", - "ember-cli-shims": "stefanpenner/ember-cli-shims#0.0.3", - "ember-cli-test-loader": "rwjblue/ember-cli-test-loader#0.0.4", - "ember-load-initializers": "stefanpenner/ember-load-initializers#0.0.2", - "ember-qunit": "0.1.8", - "ember-qunit-notifications": "0.0.4", - "qunit": "~1.15.0", - "bootstrap": "~3.3.0", - "font-awesome": "~4", - "spin.js": "~2.0.1", - "pace": "~0.7.1", - "moment": "~2.8.4" - } -} diff --git a/ember/config/environment.js b/ember/config/environment.js deleted file mode 100644 index cf21aee..0000000 --- a/ember/config/environment.js +++ /dev/null @@ -1,47 +0,0 @@ -/* jshint node: true */ - -module.exports = function(environment) { - var ENV = { - modulePrefix: 'flarum', - environment: environment, - baseURL: '/', - locationType: 'hash', - EmberENV: { - FEATURES: { - // Here you can enable experimental features on an ember canary build - // e.g. 'with-controller': true - } - }, - - APP: { - // Here you can pass flags/options to your application instance - // when it is created - } - }; - - if (environment === 'development') { - // ENV.APP.LOG_RESOLVER = true; - ENV.APP.LOG_ACTIVE_GENERATION = true; - // ENV.APP.LOG_TRANSITIONS = true; - // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; - ENV.APP.LOG_VIEW_LOOKUPS = true; - } - - if (environment === 'test') { - // Testem prefers this... - ENV.baseURL = '/'; - ENV.locationType = 'none'; - - // keep test console output quieter - ENV.APP.LOG_ACTIVE_GENERATION = false; - ENV.APP.LOG_VIEW_LOOKUPS = false; - - ENV.APP.rootElement = '#ember-testing'; - } - - if (environment === 'production') { - - } - - return ENV; -}; diff --git a/ember/package.json b/ember/package.json deleted file mode 100644 index 691b20c..0000000 --- a/ember/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "flarum", - "version": "0.0.0", - "private": true, - "directories": { - "doc": "doc", - "test": "tests" - }, - "scripts": { - "start": "ember server", - "build": "ember build", - "test": "ember test" - }, - "repository": "", - "engines": { - "node": ">= 0.10.0" - }, - "author": "", - "license": "MIT", - "devDependencies": { - "broccoli-asset-rev": "^1.0.0", - "ember-cli": "0.1.4", - "ember-cli-content-security-policy": "0.3.0", - "ember-cli-dependency-checker": "0.0.6", - "ember-cli-esnext": "0.1.1", - "ember-cli-htmlbars": "^0.5.4", - "ember-cli-ic-ajax": "0.1.1", - "ember-cli-inject-live-reload": "^1.3.0", - "ember-cli-less": "^1.0.5", - "ember-cli-qunit": "0.1.2", - "ember-data": "1.0.0-beta.12", - "ember-dynamic-component": "0.0.4", - "ember-export-application-global": "^1.0.0", - "ember-json-api": "^0.2.3", - "express": "^4.8.5", - "glob": "^4.0.5", - "liquid-fire": "^0.9.2" - } -} diff --git a/ember/public/avatars/001.jpg b/ember/public/avatars/001.jpg deleted file mode 100644 index 316e9f8de0bfb591e631f0df3b1323eacea87ec4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5064 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 E000V}dH?_b diff --git a/ember/public/avatars/003.jpg b/ember/public/avatars/003.jpg deleted file mode 100644 index bcde0fc5157645aceb5e7495b99ed18d32850320..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5207 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 Z?qflx(YFE$A{D!iOsH+7&OBug{s%?v)5rh- diff --git a/ember/public/avatars/005.jpg b/ember/public/avatars/005.jpg deleted file mode 100644 index 9916c8f51e9ca257d382d20edb8752ac122aeccb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5524 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 diff --git a/ember/public/avatars/006.jpg b/ember/public/avatars/006.jpg deleted file mode 100644 index 7dbc5888772061ba969640936d676ff1d863face..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7417 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 t=lGqPEmc)J zLt_ICEnRi20RV^)x~`sHU@`#k@bvdJ)>MI8T3N$!#{e*Z55NEs06?Pry!A{pOaZKD zsjI^MuueDpw;as?*q#7jQUGBHhyShrzany!x350{fDEzTVvcA(6qcj0>>lLreWTyS zGL?hd4TEuR*cY21mg#QT`QOa{FQ0$2(hWO$csgQzZgTc^^l-f40W8M_2B5JF(ZzCX zpes5E%TripcMtGz#qtT3sXWj~KLEfXy3zZiQ7%{(#xjwwsj(`S?*ITEk@G*;;UDae zet^vr093uaANsmFyZFPoQ9N)F85wCf0v+Uz_V*VsM55e~zK(EJFAr~|=R*Ma_ndE{ z0E(O1!m&k`6p@jX6c81}mj6HU-x~kp_20wI+Wy;P-{@a!2BI1LTlaVD-#V`n0FXY! z)+X(5okJb~w8sDd-PgZ$>@NU-G#UWfKK)}pG&lRj#oymsR!Ar)C`iy1jS{>m=zrw@ z=(tuO3U%`H^&hQ(xs<9~? z72q3q!`L%%(?9_NfE1tt=z-e+8^8?+0HS~tAO|P`>VP(20GI++z&!v3xBwo2FAxNT z0+B#0kO-s!SwKGU94G^-fd-%jXb1iR-U36w2=E!02Ihe!U=8>M>;gx?IS2&81rdWN zLG&PI5C@14BnpxSDS*^Lx*%ha704ds4Dtj8fI>l!L5ZLYP(G*_R1Indb%Nf4-h;+L zUqH*C4bUIZ85ja40#kvRz?@(quryd1tOGU$-vv8^eZaxsXmAQR7hD3a1Gj!gV1T{8uSPTgVDgaU{Wvy%nEiN76MCx6~P)|y|8f@26l*xi%XBohbxb3fQ!WS z$Bo6!!>z-8gFAt{hI@iXh{ud4il>2RjpvCMg_n(2gV%#Mf%g;d9G?uI178;30N)Y+ zA$}@;8GaZ3C;T=1a{_V#E&_Q1Q-b>hkp#H}4FrP(3k3UwgoLbwGK5Biu7nYUxr9xG z?+BL(Pl+gqc!^YrY>5JhQiv*vdWpUe?GqCda}p~NTNC>erw~^Y_Y;37J|Uqb5hT$f zaU=;RDIjShnIPFF#V6$;RVKYh8cdo^`jYe$>2ET8GEOp8G9=j}vI4SBvRSf2a!PU$ zaszS?@+9&a^7rKH6u1H_L+>LnTo4JVBjjXO;$O$*Ho%^58dtuif|HlDVDc7pbp z?iQUA9hxqIu90qGbOVEvn%sc<~Pin zaB8?R+#Q|^AAs+&FtQ+60$7S!##qi-xme9uqgWeRzp)XpNwYbzJ!R`-+hu2F*JBT5 zuVw$rfzKh$;lh!_G0bty$;D~G`Gm8bbDfKhON%R*tCnklo0waX+lRY^dx{6fBhBN+ zQ^fO`7tAZc>%v>W`-uk!)$=MzVZ zKNFvmpp?*)cp}j!aVaS!=_}bFxgo_aWiM4IH7iXeZ77{2{Z0ldqaqV7^Owx2tfZ{J zY>VuUoPeC0T#elN9j-g*J7sq;@~rYm`C|Dc1-OE}!V85ZMHWS*Vu|945}T5vQiak_ zWgg}G%5}=yD#9whDy=HVsU1x7!NMT|p@hfOF=>`ZD*j!acdQ%&d1xXl90`pikqZOyCA zk1fda@~D!_x(MFd+ztT z?MUsAb}#Lr_7?Uv_7_M4WC`-nLCc}gVHc%}%0c~dRC0XkxPg{OXP|#N$vb5@tvky* zXF6}VD7s|3{B~7!&3E0qkGTK*{;`{ZTcz8zyM=qB2cCz$N2e!+r<>=1*KMx{UgO?; z-m%_GKC(VpKD)j;z7>8Tzq@`N{#4k@)Mx;AKuo}Lph93l;7O2q(8~wp4?G`?KIDBE z|L|w9MsRruP6#TbFO(%TGITjiIjr~*=#j&tzHqki=k5R%=8Bqt1 zEgpA8Get*4V`4O8YGX-aePd^z$US);hZE--Hx@4zpBI0X;F$0sQ8+O>@gfP8^dVU! zIXC$_#VKVhRWkKi8Z6B-Z6;kYy()t|BRB(-sh8RI6#g{f=|PrV*86Pn?B_WIIRQCK zxw^R>dF**QMUF)i&y=1uJZE~I`26gJ>x;Q!&EmEa&XVj> zSZQGCk23SJ;d1Hn>I#O6#EQ#G&&s7LCMxAZl=X$mJjs}5- z;zpXr#3rCAplP$&zImoar={9gyb@3-uq8Za6d8`K$mH>5Gt zKde0b=AHbzzuwEf@BASBp?yShq-|7UwDqIJ$JQ~)v9?cApE^FveC`^*Gu|_yIPrE; zZE|QzYieX#e|mhzY-VoOc6Mpbac=#K`Ji21CGLJ!Fey#egUaUp`ApVj4llf=$y6E~}8yXv*H*Gf8etG{o{~fzUzE!x* zz5QxOX=ilTa(8XdcklX7(mwru<$?G?-=X2*_al#^^W%gQ`je_t>C@pei?g5SK^M3e zxtBbbT~|6+3)ddk*VpraDgXiB$W05u3ie0vM(|)TC=4HufB+v4AD@7Tl!$E#u4BXa*1fg8rjx>|N+490p)*qU6BMlptUn zC=TRbV}r0+L2y{06e5($Mo1sg$n;_uRrB~RH7nbm%G>4ZuK*F`Uq0l3B5=lxhj;cg zDk+e*yP1X36s>tMm)Uw%$XaB!QA)L2Gr4X;H|apzbTm&<#MH_(gQ-#+t^k4XzVNux z>kWP!*4avC!enb=p2u~5V}_j|hedWJ^)wki6*x5y-D|0y`{oe4s9h6bx#hbQDE)o6 zeq}9B!ZN_~q#WHTpl`FnMgGGh|Gj{M=hF;L#L6#RK_()@%<)%I!JZ=KSEcI{&{+0} z=PHEpY%#a`e1=cW5rsl48g8(Mxf6Ej1%ssA(*wkjS;6?4zLLl{HW(y{CCI{T-fc+{ zz91_aRdIE1W+&>PE@OZ|NZElD71La|nf%4@J#&eNH~s<+$NE}^uCrF_rB4Pm#L~_! z!n-8tQUg^+KIRJKefX9I_&N!k+^Ab z7981mj*P6H&p-ubB1EcG|WTW-{-L2D&gyUDlZb9OH^9zm~WtWY(ElF+hc?j^*9v;q~qO2wEKEN!nyp6_F|*WD>N+M?<#P*K+g@UgH%X8nE(RFMkq! zMf1Qb>*r_QeZE>h#wp_=;^6}aGictsH@RQbUt{c7t)6}v(lf|4GtyBv64%+MYLRV@ z(s`KH@_R;pXt;sLmNxr`X`#F(Nl?M3T=T&Ot(v&;k50ax7N5bu?YUM%gFkv4t5X@I z!t%Jq14qGG+O6olS9{lhPOXufO=~IJb`t-GvZy-=(EMv)kuD+F^)Z8%u@2KglgBkc zH+$jy%&1%Uiv<}vt3GQG{Ei0ZFrCtykx<>)HdpU$9=3eR89+o&V^y%b<`_G|gcf^C zjW}4y;yNBItudz7Q>2rzou`$3c5x}(ZjHc)S5JFZ&XU}cIgsop`}k4c(VgCMj{Mgm zi5vNWP4Kz>3sJH^qUlZsY#;}(HY!u;W@fKY{tZd?t@+8@Q)J0ki|+n{Pbcf=h*>d@ zibS^6JjILR7gm%ND~X%h&F7DYRhCw{KZ)iLMQ^RXwxry65}?KMb#rC5vpa<$eDgUs zZ0QzBhk4cXtj!Bih<$LH9O!63i6j>OKM$iIe5|tqT||qeA!GEms}LCghRd% zb}6!M%KBc^1$r^6a;Z6WcF_Mg!PUhY!&ikt4&`Or6f)NEXW*Az1Ny7%&qepry*Z{G zp~~&q$AJIf&eM+s@v!>eL0gelQ_5l;o6p2$Ka;KWE(R!E3L7VwyF^FS+IVPw-f=?- zA>2Ew^{xyi(;EL8VbF}mh0lEPCQp;`qkFs2go@fH_Le5!zTSZON$M&S@p<4HSmoiB^D8&5lt^6>2(Zm7 z&L~*JB*=0a@axm8y*-q=j3w@7r&i-oV>KnLj-+iqq^sWx^N=(f7|G3lx1t$IpS|)^ zi>2+LwS6!FKLgR(3}&;(PIfBTV`~!3Y+6v6^Uy-ap@4Cb;B9}vki<2hm#^#(6mzcb zT&(oz)hTcJB*@ElnQM$LYYRQK{W^ScFxZn&`h)GSXc4ovbGuvetiPu#CxcjEuge&= z`n&vJh|&{9M`cfX9{);;8_b#wMeG06J7_!NJn_@aDL=ljfA^u1oxQc-(KXN`T+4(xI z9PSzM%G%nvJ3irMn<@1TU=$+kR6R*4a;ix2ck|7CTn1^r28MScPx)&Nb&cN5qUxVo zMjk7+5t$F~i+@Bo&}8{%zxmv2ONp%rw!EgDfj-2H@7TEB?!Z|lCrz~ zbo4vXgD7Hua9H@n8&|#Q>TD@?G-IrQ%t!wT=rs^WB}~Kb|8dbOCE~Qaj<@sCuT^jA@IYd5wW|Ny6Qufm!h-!9_D67Jk zyUusp+g8Qt9YdVL(e2B;6WrM%RUNHQ?A?Wt`thp0?&E1va^xEkoo2RTQ`8>H_CQ++ zv`(P&eBnyiiFZ>rmQyy`jqn>n>X&COPi1%At@zbtlLn#zHq(~6@mM8!yaQ=J6DUF8 zxxF6y4>Ie4Axl{2rSKd!s3Zq*V|ar%Iylk%urtztEk&`G}zCAPrA1nYz&lDJOYd%4c3dEz=8 zt16X#Av|&($GG%C)erJecw04twpp@XowqMN;<~3+ES{EWXoNIWOnpbW9b5zsM5miQ zYC_OXZtTQu*OgUd?6nR5Cf!d^Ot=$LU_wqi2cPucK@d{LX6$Thuf*%J~J2ZPUW;W_3>eObssd{%Z zzwk0JsjFu3jU-W&^zI~32YAJGq9yGLojZfb(07pOP82g7PDp(!?LY4rl-53z9fKNl zC<71aKK+snV+tH8_Wx|DwUedl9}T;aR=K40Q9ZEG3n(wG+`dT6uNvGq}-DOKZHBDziE z_Gw>ea!Nr!8^0aYj>1;+?K5ul0?35QEYFT^HsPTwYb84SSzt%1l7{8D4tWUAoPTfQ zKt7v9cz<7}wT?@~kCwm-fjCYk=?u0)v{cY)E1TXJ#eNP6d!gDuAnbvB)~~^s6Q2{U zlNTR5Fyh6ZPD@NxLdZpW!J3A6lY{xHzr;PEjVnYT?4Odp7>q#v}MLT zc+^lJ;)3yyDyzxDx7V9lK40uPsm3~l_w#OZtZb95+OGuuKe(@Fth2;;qZeKi6S2ZiroMj=fO^Y|Gdl>LGgm%#W1-m zMd{Pd-X3x9oF|#}8ahi?cd1o1D`Y!sR*OETd$`qpz?1tr(=m*EpuSi9_TsLQYMN80 z+NqvtJ}ZT5m)O^D-5#MF;IC4wuh;5s{|Gw=sO9E^WOMWjl!M-dw@wZSw8JgEdZ?;B zqq!mOVpM5U11&5h8CFj^emya*l4K{|LM2PXSNY$kM@2D2-p(CqdiXpWBt_fyO<}e_ zot`?+f3s_Y*QP@3&$~5rd{j-cAgH^0H;pyDbl(prC*2D7sZb|#sF*8(7QV%lwia(` zRu_*XSJ)AAK>y1+ElTw4Jbcr=T6Le5{ zo`Xn#8E8b-{WVkDyqiO%N$=k0-Ez5~5^Q20ktsrjRW)hvS`eWPn@`@AEo@WShfQ~0 zyp9o}^X8zI2%UAs)u>``=3qSkI~3{0SX`rH+1(T>ZgC?8Sg1M`f%EH(@@2IdO zr5!p{w6PlR|HjUSbZLb!SthH>e?8;-ibAzOE5ppRlx_-3XxEBe)OUeYSy$1zu>=M+ z(I)Hk5q{-$lJmJJsRc&;<;BOO^s9{HITORWbV8z5eN75oL6XPAk1BM{OKLesml!KS z)oaW;V0W6I#kVYvHj>spN!sqKSe2PEIL-`gc~zawyPH>5eV_Ewuk9AaiB_7ad|jq|*IL zPAj@V*@EJ8ONYgr9~5u(GfX-hQ9pHTNqsgRziJWCsK#Ul((nfIMbXrG4DrbYRRWLJ z!`2S1W0mHASD-2Qy}P;Z-g%CpNNn1-K~3&N9*lmT;;heJv&KGj7hZ2d>*pCpr1ap6 zbV{4clWR~{2I$0#@~vut5K7O!%THdRyhUXdb~#0(Ep6v>kqePXH^RtYaeGthmWb$7 zKDzhBXOU9OiO7ATf`Kmj+_r7SA^wVChn9()z^j6W42c~iYCZT?F!DV*_Q5+f4Q8F} zOD&J#o!H!$gA$^n8!Yfzs@sMhT}Nz>PHg1ZT5_kjb+)06RYlhTO`qT@4-}SWyn?y1 z32PcOlc}!^zN<@vU;-0T%RVudPoZIunW{l3;ZT5@=MLUr&XJR^7c}~Q9fl|IP-MoG zb=ahe67|Q4DbRj+S@r8B0Uw8&aI?!&?j~bp8ZoO5PuIO)t;4!^_qC03vHxyTyKyI! zHtZ!U7i&fPwOuAg_o;-fOUcKy2e+*FU2&$sizsPp3D65=`@zu0 zN2?k5oeSxq_c%DU$kb{A;PAv)wuPOZlRB5m;+3OEUI#T8p<=_tmfuU6zr9Wev$Lft z0u!VZ%-a{e_GfoOIauwRPIZjAd_SyhS%^+fOHOPixdLf{EV17gdqB>oI?7|Bx9?gR zY-lg;t^pp6?U){Z6I}<#LY`@=$k-(SVEBX)FG6>=9w&vjPF`bzHr^?)yxaMCh3&=b=xTZqRFcRHI zS;SIH(Ha$B`>Vbaw5Xafp{3J7CcgASzNFWHr4&_hiTU)8ZP~e%A_HTW`~7%h-FZU= zV7Z;o^ECWzH4N2g#Jur>u;fniz08Nx!kEm9r@TM1$JwG4$TrWu5hUWu8iXs!&Mmvj zI6JSx9F(PK&yuR7*Suy+c2|906{gWkp$H(Csy@`1 zYK~$O#*hFj%eYsqsoR_1nm=(iqBqUKLJS3#mPhS-sFU8%{U4<(80+fWIpy&XN(TZF zr#J1nM2y7c(dqJE5FJcUP}jhtj*;nGo|~jrXSMxHUM-XZPn}%Y3e}PY#Jk?I{?y2< z3ira`v}``lD^7lq2D5>&Q+nfFZe1*|2~U};)FAv(H9s)O5N3Ci+6Sd9%eSSTMDn?H zN<2(v?~EZr<58HD0s~SiCWbfxZ6|)!+3gJr@lhE;WP^Ty>9}o1tzYwI+tGy`blq5s z+vr6uu1g&q;f7@E@vn%45vT~)yRY$FdyBIqyUzkhoR!&>(Wlnc)%S+G+8gJR?lo1( zrag=yHfL(NQ)1O)MdFd8r6&ptGv=KL?(tc&~;pyW5H;?wy0fw=h)0sn8BxbwfWyc0n>WRS@ z-20p1<>hD4v+dFl%d6t}FC>of)^P!Vb$?jnLU`f*-;c#so-vQCzSxtDo-ok(!O@^Y z=WI90v4y-j5!EL}m|+5M5BkcHm(g{js3wHHr_<_jyITlG%FVdDwb#|t+DOwnp+55M K&X@4(FaHCTr0bmk diff --git a/ember/public/avatars/008.jpg b/ember/public/avatars/008.jpg deleted file mode 100644 index 2458a42b43a34442898fc6d02c056237254487f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4266 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 QM#PDbqxzYx>VG=t<8 diff --git a/ember/public/avatars/009.jpg b/ember/public/avatars/009.jpg deleted file mode 100644 index b103b7dd89cf4dc27616fec5f4378823a5ea3a48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3695 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 Vp;X8(E{7>~5N9*3HaUMf`5&(weHj1% diff --git a/ember/public/avatars/010.jpg b/ember/public/avatars/010.jpg deleted file mode 100644 index 142185afeeb87e572ff15af111fa341849a315a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2937 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 diff --git a/ember/public/avatars/011.jpg b/ember/public/avatars/011.jpg deleted file mode 100644 index d74b535bcfed302e74897aaf07591d3534b45bd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15768 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 diff --git a/ember/public/avatars/014.jpg b/ember/public/avatars/014.jpg deleted file mode 100644 index d7a5b0d02ff7815d8a20f834a81ed057340b1f13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3195 zcmb7_cTm&W7RP@fl!PV$=>eoE$VR2AKtz@W3nIN5TvJpF4Bs%suCP&%JZc%wdi*X92$32Dc0V5C{a^ zJXrvf0_Xu?mLL4s!6&e?v;F|W&d$aTg>Z60Ay6nM_bF~pE?zDuln2Jcdy0<_#>dIc zFTl?yaANcQ5CQ$j1hYa;68X5GTqmdhC(KR&1_45V5HLsvV1a?aFc9-SAP#^4@X2jK zz<*%lfUvWISx%gqe85Rr78VGYot*{zBLD=jfLYmK>>T{k5CLUfQwPt`(=x9GWy9X) z*9ocotY`L{xtDj>&`B=*L=yZ%`aik=D;qlq%)tUV@v6d3lv%)RteoI~2mGxJW0Tfp z=T|m$5ReJoIIZ%t=j*q1U2~g)Lf9cD8Q?yNWr2ZVfDTYI1bawYA^!5e(O<{fqBWz} z3>|SJ>E$a6u=k(l&wbkCJ*qCSaGRc`o)ulCd6brb&%RJ4DR(q2A>S>z2~%FBx0(Iw zt_mstw!m-jRYi3g@}1QY0WLf+A(w|MVqRw4zIs>8Me*8}>XJ>6!&%dyUl$qLuvs?aVXjTc>Dnb~`0Bf)#(6@8N0{$|qN_%8XQmS}N_Z@+w{@9$T9 zLxLXe*wr)i^rtEmF-kjAtjX7*Z?kv0YL-->th_-5(uR|wo`Fi~t~ohk;YT$&=Wcc} z^g?wkQ_CrSaIkX$k5ADAWo|jYDD%nWb+@1D#+X1nQs~B?ZjuY*&QhpD3(MN~T)7+G zQ23x0!%u)Ftd)=BEg+FZt6G9GE?>*eb{t4P;*Na`KKDLn5s!##H8*P~t*j^(I$u~- zoPG9_tMW8_Inb*aS%W1zFA(kWN5ol$DTUb1C2sY>+Kn*nn5JI(ugdN(O^TCx$5Oe_ z2|Q8fOkSYkz2?%6regX-K;@%Cvm)@Ou{IJ1cypX>V0Aix`p!((dOa|mSMl-EH6jC{CQJY~+Go}$8w-IS4;l-Q;wky@N5t>RHLf=t?OTbJzL7D$xZcRW zsgjI8;6>V3bP${L2vzf_ zl-7nzN>-2sF*I{>oMca{VehC;&t5ZKe=5ioyWLA#p+)RBPFP*(Q|iY1 z=usKA!G-MAsTQk*398eDLY&m^QF&X>3XkVI+WzEhX|((_;xBJ`{n9X5%ta_(wR2cU zrXD?WuVnOI%;VzHvo}Uj;@S&)A?ufO^UiNl4Si3Otw>G}H&#} zj8%Kw?p(JU{>pYy==*e?Egk%HR#;W~_?V^^SjquLkNSE8ajF+{9v%UEPjIxIsNK}u z-^Nr^UCWf7onP9KIwr@9@sqF(<|zcXjO zKGP2uHx)|HE3R-_YuKgKb!s*^w6R~OF`hrXYMLqCCg$Nmvld7Gl~bW&ylK(_VK4?D zY!PXM2VJzK()4=#(;*8x71T{vgQC32TzInU)Aae0)E1AY99m%iV`2X)j4;`Mtxyfe z|48dz+?_nB_4eOm57bVT@9reEXFi$^%p%O~-ENxg@wb`Do~b=3m+5bMmx)PldVbqa zK8~JfBxiKh`{7dr;G2Sr&(EAe4&%@gmJJ!5`EY0JlR}CIlKVB(3xHxX)Q&-i|`-$LIh;ZIW)C)b(OHD2O zu!F+Wd`f51)+}OU7I#v2HA`Izf5FD-aq<*%8nguy2zD+4NzqxQ_L5mNXJ zuQ}^-yqX3Rh^b5;=`;6A(B70>QlAWw7?=16AOB=BkF{tnIxN8CdCvwh0YR}ly05=_ zq=l{W407mPwwX$z^YbdqzMlV3DR(hrj4n>c`LvCgkuDydZ0Mg2eDmokz#kszZlvw&m;V#J%z$Eqm;B z*|_e-oS;E%^7JqJvj{fh3h`%S!&JkYu3z!GBvdiH`~JG=JMBYMz$KP$_iL9GF1#(! zKnVLc^IET_zGutqQ5Ftcaq;f7C!`u*Ta;=ge+h)XU8!zZ#%fN$Kf2t(@ky>9`FK5% zf7su?{yn%V@EzT=w5rsvFY$4zlm}=*kT4u$e&lp8Q*`EyGUY2Hs#9m`O|6tcJh7Cn z<5;^iJlOF3a>vp4DxBn6fJGK(g5DiD**4md8WSjujIpR5g8dhP-xSI(SQ2+v41@

    syo_w>vtEque*6q)d_?B zo|>z|?-Le1M*RXVX_hOLf!dOJXZ(n20v@d$T%zztk5oXbAD?Hv03Rw{c4<8}C?lpy z=$aWzVDuINCSd5j)j0k@p-_Kivu8X_r0W4&MA)SI+c+K%ATqLpr0~y({r8}~5I4GM zibh?!J$p^%)f3U&tjak6!z)Z~Y%;FmvKx58vrm diff --git a/ember/public/avatars/015.jpg b/ember/public/avatars/015.jpg deleted file mode 100644 index 5de26e5d9ac80d903fc58bb4429e5eaf8e5a3ebb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6919 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 Xj7hG$>LcA&m+WA_7V( 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 eWekVFaB4=0S5 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 W+|aL~?N8i^e>!k3_it8QO#BaMSzwO< diff --git a/ember/public/avatars/018.jpg b/ember/public/avatars/018.jpg deleted file mode 100644 index f9f78387fb82ba648df8553ae2d58f2e5cc5a762..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6111 zcmb7obx_n#{QjHn76m*)?hxTXX^|A9r8&Agj}A!*rSl{tm4-W7T4|)ELEu0@I4O@t z8h(6czVn;!zu#wPc6au*&$G{L%+9=S=WbU3Y83@#1po&J7XaNA;1&(M0`PGE!+$m2 z9qHiHJ$? zaBxZQ;u6#VE)E_p0q&h+0$ifIDLA-z_yjZnArURzBYt9;d+b`4Bmy2`^!LFjg4+4D zU9#a1URis3AtvtF3^)MZ|CatA`dx&L2Dsae|DykYWgJ`@+DH6&G9Gt|w+jF{9?qRj zJQ_e67^)nRNf9bDcSV65P{FxtKifZ@B(dbO zX2be&O_Eh)g*C`BekvL7{rS*4OeZe_=TB$nU>C;LPoR_=G5P-;2 ziK*6iuV-UMa`K0wzuG5PmKpvq99BLm z0+TNZ7gkCJkP)O(VX!yjn3opNcxA{Ni~WHKrFhbBu2duOrL8rR(QGsY4Zhhv1D~7G z<;Yh5^&Qzd&ke>tnf|crh6WSEV^n7%zgfW&NMZF@Sl`G8G;im?sM<+~ z&W8AP0V&9hZR2q&%C%MOu%A1Xi%I3f#Nh|ud9#>WkgH$2OD~dsa&%O=tJNIO=jyztUA028F2#BFNIt$%y_8P=vq4Kx}MmXjpbxSeO5Tj*g`SQal6p zas6R7n4d%c7&%+x&u1dq45}YGtzCh=;FpzIUm>K7~{)xbq?jhbe=RcoLM}8&HJOFe(6 zXy2vJCeT$}j9J)nCTTr?GI3modoa3YFKjuL8D{+f_%pj8^ic2Dld@@v>d3M(Foj^S z#yI&umSs<6s3{$`m`45WaJ5;k^()9bZXAS04byCB#x-96{CCv-Ci&90@UK3+B53r3 z1(jG-n)f>nxf(T4rJ4p)_>^)ejwnpKhj*RA4ynY&EjQh`VV~t*R}AKk|F9GzO2o+7 zyBvaW7Mh~B{VscNyGs=ksZ5%tx%SV_caI25Pnrg0UPX@_a% z$$*29`ci^quj?x|w+2f$Boeg~ zrR`qMW3+bFuHavcw{M>1TKc==&}Q;?OaDN2*<44CoAfYqmMe?@5nh)?3tO;H-R$Wv z!J?nPz`9dtm;2KQyTZ6b6&UrH<~EgtlYnee5dXgL$3?Fdk+s#d-@${*4hXfRpbA96 zcaKCbv3~NZF?tD1s88xMmzthXVk<9H-@8mQhKG`g-U+R~!Iaxg!pv6hG6gKkbcf}Z z52>0@FR6+BuAW>tFlin4r9UYsHRWya4SX+O*A509k7{)h{R2zMFtA;QQ^q-U9VlAf z0s@9c?coH^xd{XmWbgYI0_oA#gQsTw9hQk!KIX!yX?!d}PZo%LUCZRm-ya)t05Gz%31OMGzoRqKkY4iOp;xh;Jg4U#itW^rKr4*CIo;wuPH~d~~ zI?lx^G=4e@@45kNpFF5|uxtH?sX{j4;p*%`EOWKRZ_1^PC2rsJl|N+>RdKPlDekiA zY&K#-@SJd14TPrL4sl}Wg-T?6@++s0*4waXq?p0tJ_C*y_GGVQCOi78)B!<>!S5!i zS!7XS`;g-dM+tvE=3PNqH}~OHCJSr@xAoC?GXeLV$ZXn<#O&4`&(iw&(i;Z0{4kEP z%3KT?6MIv-mFJ6Szy<+~FR0Jc+qBU503r&lC;VvXor=FzS5}P}4z0w9um{{(noD`r z9^XNKJG4`#TB~mXYZTqq@N5#*wU)el;Q~w$j>g2Mnz-IaPvJ%;H$BQ@icW&EH{y8_ zfIr$;x3}D+r&t~SSmWGEpl$5}PY%#CAX=-WN`j5F0_^LJpQ01jnE$Dbb3VaTtP`tFg}nvdK|-HC7&OZm~DNb8WVHK4SjFS96z1^h@>$_&g5qK2ft?^TUavacKMvAB6io{-SSe zn={?_p%2C7V8bSe>RTKh!69PXVC0t2Uu@*PY;qD2IaxSPF@ip^Eo*78ZjyX3Kld&&VY+Bx0%yn7j{YY{-zdqS} zxzTm$@SR>(vve%zDeC!s->`+iDoZsg4uT$G*f-QWlTUg4?@3=6cgL@)*pF6PPa%Fu z|I}(?<DY5Nz6e^Z3gY zI|FSrJtjdjD4+Dx-4l8lQg^1?(j*O>rjOLhOAf7k&~kkUhrDp)Hj34D0w*Z``LMd4 z(w&H%=p0SC278F@*tNW_wMU-i52@9qtPS$tIMSFeJ_4<3*b)q2=hV^X&d=&kbX5+Ojn*D>ZjjSUWBSWz%>e7di`AzuJ)t1yP*(YA@O?z`+Gah2W9q?^LbP*~ddhI16Dd zg3Ds7#m6l;f@iBqt!SNhVSn&C(hq6Cff($~omZogrI5(*FN&^`<|3$5M2p>fI7y2z zh3*wgF}b?Z?Bg!g`uRYnvm>_xQ5`eJ0Iwut3VUoiNt!3EHAsH`;o-?W5z3c4+fT!} zpRy>qWiZ8xWBPXRdfbe{Uw7X-Dr&p&CVh1ar1Aq!7OUSh;k@mxCobg_FKqqiyFa_G zXN-jr zS$nmb(cyMHiPA~Q>$!C9QB^wlY^7X0cu;iRWp(r1s(C(O3D3PKD+4wBV{r7ne{D*R zz39MH#%f3*)49;EvvJ(9o+JzJ`#E_x!n-rgcYg*pPHN4`EUa~0=cjke`9-k7%(;J! zV4{mXp0rG{Z3%`sqrWk0MKje=3A^!X_0F$K->1c}HJSWG(UH2=zF2_~b%53Dvp4hV zj|^Ya=h)b#S`G)dqZ#DH2%#QJe>o)Lm$16$%0FnzUL|=4eBcRo_EJ(vQps9@R{ANE zBIdkEY9()D%#CFsA((sLRK)bX-}S$bw*YI0&NBs06DHEfkJ2SrX_epmN%f=!C)!`c zdVl1J7aRz@d1tZ1rIIV)dM0NqlH?BWO>pG`rSAG$R5P!Zt6o*~zKy&YEoHD!`WvWJ z+2pvRVG1=`hF0@*Cv^Kz&9tN*g|xoTLhD=PA@4;N82ebdx`t;GG6W4ntUGejdwcPXL(}P^SCL91 z4sSm+6kOXSp`XchwzimpA7Gv?!oqTlU5JCx~5V*3Nj zE7HPyG%Ifk9@d@xY2yZyZ~`jGQrt?SZ6soW+!H0c;=Vs4vpXWL>&t2i)8*OH{&xE79D~v3 z=44?xzL#5Bn#qna|D@4oOgevC^@T#uOgUV$W6;KSd(G`szX_1x1x)YOi_Lx6i zef}OfpkT_V&jnJ)ac_&;_S2za-n0c>#*#cKF*6vOl=wj?=O6jU`Z77;RxT{PB)_Mu zzMAiP&pI<#m8`l~geY;9f5=j%YA33}Yk`Ckc`AIl1*Y_Ae@02D6g}W>PeYs!yES_a z4ueekFLWABH9e&Suz3axCgi#Bq+)oIvdx#q{S;l4YwcS3ZZchJG^=IU=-i>9;zR{GpGtMQdEc?Tb9UNw6|h{<|5I22u&299yoDw|G0kpR0Z)ay(P}=8gRC zl~RviYix@8ro@{%9q0KGt>vfW-?N*ngoRYPK)$y?DOmX@4(I;N8`SS2nvo=)$?%u) zA`HPFBSM305>&FS;GdLrC^$?dAO0X^O77;{i$Z9H>&jg9XFx9hO>8cgF4ep`V}g=# zX!lq32-i6h5C0v{e&gEyN*f6CCl1MP)${ic> zuSFjZaLSGC2gZB!um{dZKbl+-y$E*|RD8X8jkvfoXV#oP2GddE~s zQPuB7*Q8oEYouP4x=*?ggRLl-BA2x)-N|7sPmBx2AP{BX{|YB@7w2dhJ@&RSdbTbO zR|Sj^PO)C6y>Vyk>f33(2SLDW7B-hUhQO-}E|9xe1`nsV5fcvH7@OTaQ|bDkBh z9|F0MZ!zL1%81lW(|=c%UCc}0ZGcemk?hTNOEV;D24^qY_f;<30?|EE8`mczveo<$ zstl=+tTeGLlhxdZ=)0>QrB94S*G+O6<-gYXaWC9}6K@O>H6 zedL;`ykoXYB_(slMlRvq7Y~0u;@b~J zZD|PVf~7aG9Bjb2{VAHa?X1D4Ny}sl>VmfOhu@>qoIWY%R&~`gD$6xYnbpUyh)Z-x z2c0riLy*$40(ES&6E`3hWTk}Fs(eS(Px~g#J&HIDdI~j=53SHoXaG0F52TDRs9n!a z%OL@*r|1+S9tf?ic2GZmM%sjli~cHGF}3vX+Qp~7_bXC)D8!*?-PSHvp7&vyHP;=|%e=)F;t_hL2iO_4Ht$}328SK9O$TVk(+ z&S*tX=GU86#ZS)D#vF*R_OS0tqkYQo!}Uqk{y|Og?*8o#u2Ytas5XJ0XQEm)bN|Hn z;)^WSME`n^N4+)XLasHs1{lRL2L`o?rHyE_UcXQr>N%du%X4@ft)?cR^bTXAKrY=p z3m4LwYjUwSCtBPvK{`DuiP?6tO=Mta^tW+Gc9e@bm^`~gd3@D&L7GPuTvd9P6Y|{x zR~GRr!7m3d5=eQYy%(XIyeTnN!pT!lcF7asZz2Y-BeRdQ`}$kmJPfcxb&H=%-D1j7 z7$}8A%#q^qQk*q#(~K<|qNF!kFi%)KCC#2u_teve65j&#pr-5IttCZ8MR=*o>tG(( zekmpqLR$Ebsf~^N$P*E?lg6wva&(w-2%2Rf~T>UJRoDRSItL_sd85nsX`5wr1|Efg*R5`&I0=}toY!lz_`lm3~ zJk><~yjZBd-jUfRd3xcADKn(ynNpOZ9w-?xc&*yJLEQ>^Ih7QBCA$=)zw9>(-V4l0 zJ8kPSI7#yo{{k4-^@YVinV!JpMrmkZzJOy(g$*`~&B4LJ>7~gWdc${r%FpQZS8s=| zVfC!zcQtzW3(!G3Fx?!zGN(_p^NfVka>2I!61Ck10?8d8d0V(h9@m>1 z4Uq=sLdO>!xwf~8@P$0fY^7^&fnY)D{hO*O6T5}y+h3jhsbO9c&bL6#IT(wOT6kW2 z_NgcFaqL8o*%NH=#Fb!)v;1A=(0qSM-Sv}B0e0}mz}cwa(A>WFYIR9<);%HJ6h(b9 zuinW=4rnlW;^&RjBzI3@B3W*`uj!$GlD3~jUrWYNaXE)8R4 z4)3@3G+rIxyzsxUvo{Md^BxsUv>i*y|9(NAxaDtswBnE;uibz2wz1rew&T8r>O-Ts z$MX%RO9vi#dBypqjcAU-NUjN)7;cUVgIGxX+(yh}Zu&vhLzI1KS#>Y#G*#KNgxZFq z^H&bjC+s%_)+q%$olCmwsIpt&eqk0@tLI~FqIJE^@(GV9U*0bYlBPH8-=3{Ne7?T6 z)75cfzT({T*m98{7k918|FT+-K}k*IuKw|JwXe}>I>d#d#*v_r#lw7=e#8U v>-ET5&ZYsxZ`q=ceuBl|u?Y3yy5 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 O;@pUE5jT70$Ugv3`vG+T diff --git a/ember/public/avatars/020.jpg b/ember/public/avatars/020.jpg deleted file mode 100644 index b94645756cf2737a0e879bc78ea213ff24704628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2064 zcmbtT3pA8z7=CBQFrxI&&_xV}>KH~*(ypT|#w|k(L+&wdGsdNHNtZ3PsZ^Aj*t98B zF4JUXmDrrN6BXNTvMwbmm1T-OMRKdz?`O+7d+O}&+4npD_de&nJm2>|=ld1yipMZ} zt%I`z5C{ZtLJJh##M91pcERr4bq>xPdz1hm9v1Oq5|ow##Kb0wxsJB_>wW$7RXSk? z(18j@U=_koh+pIG&VkR`a4R4tfMyoX_1UsdC)C2i6ZilG3QDi!$BPpYUX8HZro?z0 zA4XV{9~BaY@EL?n#pr?v-^JshQ}`KTSFJ2aa*!upX@*c8X1Qy8B`pC2WT zK{Xmq=ez5q0&^$h?2o1fAfapU|@*aqOW6@YUG;CL>;B6OZcfT9EJfJ{KcjtvQA}^X)uCaYDrPcS2<% zS%pABNhXF95>?65=i!8yfjL>3N@F0xjoh7C`Q=X(4?vxSh%gccU%}ng> z^%bP{bj#oLco%;!a`zCf%X@Y|LnxF*Tn#SxnpQe&e=DrZH^98}<^z*K#`@l`oT(mG z#U<~*-0ssb6xYTt-fkG%`qChE!v<@zc6^7W&6wAv$C)jUg5+h%4bnD8s=(uk-=W4K zSs%+ny0Cf`qf45jHB@9d7#nOe7tRFVxO(TMg=|ALb!6i+z|qd_l~WfFo!8$&1I|&?23^0^5IGXV%|j)wj&ZBHO`C z=Js}55!WUyvn8Xm?Awz2wez2MjxCl{x`)=MQ6)|iC+74hTqh&=GH%$!YTsQJh%KYV4lfXTVsLRrBEmAW!FDX@kzoc+kxeX^c zW5BA;N6hqkUMHMb?q5M0b!}_$y0X5j&Xf8iJ$pq!w0{TdyXs@bm0AJ6DVu z*AE;g?3T@uzg=*FRaKv^yML@QsKGVbvwuSH@Tl2|u6?b~eBSstt=aXq_3-m9Yb6)v z=v$u2=|zur7`w=l+gtgv{$xq;lD)Y@qsO%OrI%&iFmx~BOUpZ49sSzm0Jr{9DtzgA zsgX&`e4p{e6B4G&jq<3LwcA==X7Wb16(seB{v>B*_h=cPbugKGinFSb=J-laly5JI z+Hl{iuUTk$Fzgb;k`~5h#|>?A(J7(Hutkk~dU-FJVjOQKjAS1vjlM1|5EgrJQm^kQ zO_;Id2kz6IvG)RGo{{loRdr)Eu0Jxe8#TMtyysu^sp(p{V!5Aj^@V}0HI^l>zSzoG zkWxt>mgZII9QA!{cVJ;wNX4N%rZm%e|M-L+$9~JD^(?B{Wr64b)jH*rN8>QtO@0-l zB{9w7dIB4DA70_~urJ=-US9B`CgdB@fF0DA&fe|%Wor3_&WgMQr9eAGWK zOB&SjBKGt^(iV|xu;b5BnyPjh@}9vIz`A7*jxWD)aq#*&frK02?C z>*^qo`N&UI+Lv^uY-+xEg_pe2A>wIGt#z$w-+-UFG4qU5O!E1HN2@Lb4UCH%e^w7q zsn0oE)yXt8uJEou;JQO-y~?{NFSm?0Zg7EW;{AZl7u(qtB${ - - - - - Flarum Tests - - - - {{content-for 'head'}} - {{content-for 'test-head'}} - - - - - - - {{content-for 'head-footer'}} - {{content-for 'test-head-footer'}} - - - - {{content-for 'body'}} - {{content-for 'test-body'}} - - - - - - - {{content-for 'body-footer'}} - {{content-for 'test-body-footer'}} - - diff --git a/ember/tests/test-helper.js b/ember/tests/test-helper.js deleted file mode 100644 index b5f6449..0000000 --- a/ember/tests/test-helper.js +++ /dev/null @@ -1,12 +0,0 @@ -import resolver from './helpers/resolver'; -import { - setResolver -} from 'ember-qunit'; - -setResolver(resolver); - -document.write('

    '); - -QUnit.config.urlConfig.push({ id: 'nocontainer', label: 'Hide container'}); -var containerVisibility = QUnit.urlParams.nocontainer ? 'hidden' : 'visible'; -document.getElementById('ember-testing-container').style.visibility = containerVisibility; diff --git a/ember/tests/unit/.gitkeep b/ember/tests/unit/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Flarum/Api/Actions/Base.php b/src/Flarum/Api/Actions/Base.php deleted file mode 100644 index de2c856..0000000 --- a/src/Flarum/Api/Actions/Base.php +++ /dev/null @@ -1,215 +0,0 @@ -commandBus = $commandBus; - } - - abstract protected function run(); - - public function handle($request, $parameters) - { - $this->registerErrorHandlers(); - - $this->request = $request; - $this->parameters = $parameters; - - $this->document = new Document; - $this->document->addMeta('profile', '?'); - - return $this->run(); - } - - public function param($key, $default = null) - { - return array_get($this->parameters, $key, $default); - } - - public function input($key, $default = null) - { - return $this->request->input($key, $default); - } - - public function fillCommandWithInput($command, $inputKey = null) - { - $input = $inputKey ? $this->input($inputKey) : $this->request->input->all(); - - foreach ($input as $k => $v) { - $command->$k = $v; - } - } - - protected function inputRange($key, $default = null, $min = null, $max = null) - { - $value = (int) $this->input($key, $default); - - if (! is_null($min)) { - $value = max($value, $min); - } - if (! is_null($max)) { - $value = min($value, $max); - } - return $value; - } - - protected function included($available) - { - $requested = explode(',', $this->input('include')); - return array_intersect($available, $requested); - } - - protected function explodeIds($ids) - { - return array_unique(array_map('intval', array_filter(explode(',', $ids)))); - } - - protected function inputIn($key, $options) - { - $value = $this->input($key); - - if (array_key_exists($key, $options)) { - return $options[$key]; - } - if (! in_array($value, $options)) { - $value = reset($options); - } - - return $value; - } - - protected function sort($options) - { - $criteria = (string) $this->input('sort', ''); - $order = $criteria ? 'asc' : null; - - if ($criteria && $criteria[0] == '-') { - $order = 'desc'; - $criteria = substr($criteria, 1); - } - - if (! in_array($criteria, $options)) { - $criteria = reset($options); - } - - return [ - 'by' => $criteria, - 'order' => $order, - 'string' => ($order == 'desc' ? '-' : '').$criteria - ]; - } - - protected function start() - { - return $this->inputRange('start', 0, 0); - } - - protected function count($default, $max = 100) - { - return $this->inputRange('count', $default, 1, $max); - } - - protected function buildUrl($route, $params = [], $input = []) - { - $url = route('flarum.api.'.$route, $params); - $queryString = $input ? '?'.http_build_query($input) : ''; - - return $url.$queryString; - } - - protected function respondWithoutContent($statusCode = 204, $headers = []) - { - return Response::make('', $statusCode, $headers); - } - - protected function respondWithArray($array, $statusCode = 200, $headers = []) - { - // @todo remove this - $headers['Access-Control-Allow-Origin'] = 'http://0.0.0.0:4200'; - - return Response::json($array, $statusCode, $headers); - } - - protected function respondWithDocument($statusCode = 200, $headers = []) - { - // @todo remove this - $this->document->addMeta('pageload', microtime(true) - LARAVEL_START); - - Event::fire('flarum.api.willRespondWithDocument', [$this->document]); - - $headers['Content-Type'] = 'application/vnd.api+json'; - - return $this->respondWithArray($this->document->toArray(), $statusCode, $headers); - } - - // @todo fix this - protected function call($name, $params, $method, $input) - { - Input::replace($input); - - $url = URL::action('\\Flarum\\Api\\Controllers\\'.$name, $params, false); - $request = Request::create($url, $method); - $json = Route::dispatch($request)->getContent(); - - return json_decode($json, true); - } - - protected function registerErrorHandlers() - { - if (! Config::get('app.debug')) { - App::error(function ($exception, $code) { - return $this->respondWithError('ApplicationError', $code); - }); - } - - App::error(function (ModelNotFoundException $exception) { - return $this->respondWithError('ResourceNotFound', 404); - }); - - App::error(function (ValidationFailureException $exception) { - $errors = []; - foreach ($exception->getErrors()->getMessages() as $field => $messages) { - $errors[] = [ - 'code' => 'ValidationFailure', - 'detail' => implode("\n", $messages), - 'path' => $field - ]; - } - return $this->respondWithErrors($errors, 422); - }); - } - - protected function respondWithErrors($errors, $httpCode = 500) - { - return Response::json(['errors' => $errors], $httpCode); - } - - protected function respondWithError($error, $httpCode = 500, $detail = null) - { - $error = ['code' => $error]; - - if ($detail) { - $error['detail'] = $detail; - } - - return $this->respondWithErrors([$error], $httpCode); - } -} diff --git a/src/Flarum/Api/Actions/Discussions/Create.php b/src/Flarum/Api/Actions/Discussions/Create.php deleted file mode 100644 index 2456c10..0000000 --- a/src/Flarum/Api/Actions/Discussions/Create.php +++ /dev/null @@ -1,35 +0,0 @@ -input('discussions.title'); - $content = $this->input('discussions.content'); - $command = new StartDiscussionCommand($title, $content, User::current()); - - Event::fire('Flarum.Api.Actions.Discussions.Create.WillExecuteCommand', [$command, $this->document]); - - $discussion = $this->commandBus->execute($command); - - $serializer = new DiscussionSerializer(['posts']); - $this->document->setPrimaryElement($serializer->resource($discussion)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Discussions/Delete.php b/src/Flarum/Api/Actions/Discussions/Delete.php deleted file mode 100644 index 5119e9a..0000000 --- a/src/Flarum/Api/Actions/Discussions/Delete.php +++ /dev/null @@ -1,26 +0,0 @@ -param('id'); - $command = new DeleteDiscussionCommand($discussionId, User::current()); - - Event::fire('Flarum.Api.Actions.Discussions.Delete.WillExecuteCommand', [$command]); - - $this->commandBus->execute($command); - - return $this->respondWithoutContent(); - } -} diff --git a/src/Flarum/Api/Actions/Discussions/Index.php b/src/Flarum/Api/Actions/Discussions/Index.php deleted file mode 100644 index a953bce..0000000 --- a/src/Flarum/Api/Actions/Discussions/Index.php +++ /dev/null @@ -1,84 +0,0 @@ -finder = $finder; - } - - /** - * Show a list of discussions. - * - * @todo custom rate limit for this function? determined by if $key was valid? - * @return Response - */ - protected function run() - { - $query = $this->input('q'); - $key = $this->input('key'); - $start = $this->start(); - $include = $this->included(['startPost', 'lastPost', 'relevantPosts']); - $count = $this->count($include ? 20 : 50, 50); - $sort = $this->sort(['', 'lastPost', 'replies', 'created']); - - $relations = array_merge(['startUser', 'lastUser'], $include); - - // Set up the discussion finder with our search criteria, and get the - // requested range of results with the necessary relations loaded. - $this->finder->setUser(User::current()); - $this->finder->setQuery($query); - $this->finder->setSort($sort['by']); - $this->finder->setOrder($sort['order']); - $this->finder->setKey($key); - - $discussions = $this->finder->results($count, $start, array_merge($relations, ['state'])); - - if (($total = $this->finder->getCount()) !== null) { - $this->document->addMeta('total', $total); - } - if (($key = $this->finder->getKey()) !== null) { - $this->document->addMeta('key', $key); - } - - // If there are more results, then we need to construct a URL to the - // next results page and add that to the metadata. We do this by - // compacting all of the valid query parameters which have been - // specified. - if ($this->finder->areMoreResults()) { - $start += $count; - $include = implode(',', $include); - $sort = $sort['string']; - $input = array_filter(compact('query', 'key', 'sort', 'start', 'count', 'include')); - $moreUrl = $this->buildUrl('discussions.index', [], $input); - } else { - $moreUrl = ''; - } - $this->document->addMeta('moreUrl', $moreUrl); - - // Finally, we can set up the discussion serializer and use it to create - // a collection of discussion results. - $serializer = new DiscussionSerializer($relations); - $this->document->setPrimaryElement($serializer->collection($discussions)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Discussions/Show.php b/src/Flarum/Api/Actions/Discussions/Show.php deleted file mode 100644 index 3b6e123..0000000 --- a/src/Flarum/Api/Actions/Discussions/Show.php +++ /dev/null @@ -1,29 +0,0 @@ -included(['startPost', 'lastPost']); - - $discussion = Discussion::whereCanView()->findOrFail($this->param('id')); - - // Set up the discussion serializer, which we will use to create the - // document's primary resource. As well as including the requested - // relations, we will specify that we want the 'posts' relation to be - // linked so that a list of post IDs will show up in the response. - $serializer = new DiscussionSerializer($include, ['posts']); - $this->document->setPrimaryElement($serializer->resource($discussion)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Discussions/Update.php b/src/Flarum/Api/Actions/Discussions/Update.php deleted file mode 100644 index 5e6d235..0000000 --- a/src/Flarum/Api/Actions/Discussions/Update.php +++ /dev/null @@ -1,51 +0,0 @@ -param('id'); - $readNumber = $this->input('discussions.readNumber'); - - // First, we will run the EditDiscussionCommand. This will update the - // discussion's direct properties; by default, this is just the title. - // As usual, however, we will fire an event to allow plugins to update - // additional properties. - $command = new EditDiscussionCommand($discussionId, User::current()); - $this->fillCommandWithInput($command, 'discussions'); - - Event::fire('Flarum.Api.Actions.Discussions.Update.WillExecuteCommand', [$command]); - - $discussion = $this->commandBus->execute($command); - - // Next, if a read number was specified in the request, we will run the - // ReadDiscussionCommand. We won't bother firing an event for this one, - // because it's pretty specific. (This may need to change in the future.) - if ($readNumber) { - $command = new ReadDiscussionCommand($discussionId, User::current(), $readNumber); - $discussion = $this->commandBus->execute($command); - } - - // Presumably, the discussion was updated successfully. (One of the command - // handlers would have thrown an exception if not.) We set this - // discussion as our document's primary element. - $serializer = new DiscussionSerializer; - $this->document->setPrimaryElement($serializer->resource($discussion)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Groups/Index.php b/src/Flarum/Api/Actions/Groups/Index.php deleted file mode 100644 index 916686c..0000000 --- a/src/Flarum/Api/Actions/Groups/Index.php +++ /dev/null @@ -1,18 +0,0 @@ -document->setPrimaryElement($serializer->collection($groups)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Posts/Create.php b/src/Flarum/Api/Actions/Posts/Create.php deleted file mode 100644 index c6864b0..0000000 --- a/src/Flarum/Api/Actions/Posts/Create.php +++ /dev/null @@ -1,39 +0,0 @@ -input('posts.links.discussions'); - $content = $this->input('posts.content'); - $command = new PostReplyCommand($discussionId, $content, User::current()); - - Event::fire('Flarum.Api.Actions.Posts.Create.WillExecuteCommand', [$command]); - - $post = $this->commandBus->execute($command); - - // Presumably, the post was created successfully. (The command handler - // would have thrown an exception if not.) We set this post as our - // document's primary element. - $serializer = new PostSerializer; - $this->document->setPrimaryElement($serializer->resource($post)); - - return $this->respondWithDocument(201); - } -} diff --git a/src/Flarum/Api/Actions/Posts/Delete.php b/src/Flarum/Api/Actions/Posts/Delete.php deleted file mode 100644 index ee8cf2e..0000000 --- a/src/Flarum/Api/Actions/Posts/Delete.php +++ /dev/null @@ -1,26 +0,0 @@ -param('id'); - $command = new DeletePostCommand($postId, User::current()); - - Event::fire('Flarum.Api.Actions.Posts.Delete.WillExecuteCommand', [$command]); - - $this->commandBus->execute($command); - - return $this->respondWithoutContent(); - } -} diff --git a/src/Flarum/Api/Actions/Posts/Index.php b/src/Flarum/Api/Actions/Posts/Index.php deleted file mode 100644 index 6b53b0f..0000000 --- a/src/Flarum/Api/Actions/Posts/Index.php +++ /dev/null @@ -1,62 +0,0 @@ -input('discussions'); - - $count = $this->count(20, 50); - - if ($near = $this->input('near')) { - // fetch the nearest post - $post = Post::orderByRaw('ABS(CAST(number AS SIGNED) - ?)', [$near])->whereNotNull('number')->where('discussion_id', $discussionId)->take(1)->first(); - - $start = max( - 0, - Post::whereCanView() - ->where('discussion_id', $discussionId) - ->where('time', '<=', $post->time) - ->count() - round($count / 2) - ); - } else { - $start = $this->start(); - } - - $include = $this->included([]); - $sort = $this->sort(['time']); - - $relations = array_merge(['user', 'user.groups', 'editUser', 'deleteUser'], $include); - - // @todo move to post repository - $posts = Post::with($relations) - ->whereCanView() - ->where('discussion_id', $discussionId) - ->skip($start) - ->take($count) - ->orderBy($sort['by'], $sort['order'] ?: 'asc') - ->get(); - - if (! count($posts)) { - throw new ModelNotFoundException; - } - - // Finally, we can set up the post serializer and use it to create - // a post resource or collection, depending on how many posts were - // requested. - $serializer = new PostSerializer($relations); - $this->document->setPrimaryElement($serializer->collection($posts)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Posts/Show.php b/src/Flarum/Api/Actions/Posts/Show.php deleted file mode 100644 index 3bd2df1..0000000 --- a/src/Flarum/Api/Actions/Posts/Show.php +++ /dev/null @@ -1,39 +0,0 @@ -explodeIds($this->param('id')); - $posts = Post::whereCanView()->whereIn('id', $ids)->get(); - - if (! count($posts)) { - throw new ModelNotFoundException; - } - - $include = $this->included(['discussion', 'replyTo']); - $relations = array_merge(['user', 'editUser', 'deleteUser'], $include); - $posts->load($relations); - - // Finally, we can set up the post serializer and use it to create - // a post resource or collection, depending on how many posts were - // requested. - $serializer = new PostSerializer($relations); - $this->document->setPrimaryElement( - count($ids) == 1 ? $serializer->resource($posts->first()) : $serializer->collection($posts) - ); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Posts/Update.php b/src/Flarum/Api/Actions/Posts/Update.php deleted file mode 100644 index cdf0a33..0000000 --- a/src/Flarum/Api/Actions/Posts/Update.php +++ /dev/null @@ -1,39 +0,0 @@ -param('id'); - - // EditPost is a single command because we don't want to allow partial - // updates (i.e. if we were to run one command and then another, if the - // second one failed, the first one would still have succeeded.) - $command = new EditPostCommand($postId, User::current()); - $this->fillCommandWithInput($command, 'posts'); - - Event::fire('Flarum.Api.Actions.Posts.Update.WillExecuteCommand', [$command]); - - $post = $this->commandBus->execute($command); - - // Presumably, the post was updated successfully. (The command handler - // would have thrown an exception if not.) We set this post as our - // document's primary element. - $serializer = new PostSerializer; - $this->document->setPrimaryElement($serializer->resource($post)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Users/Create.php b/src/Flarum/Api/Actions/Users/Create.php deleted file mode 100644 index 7890bd8..0000000 --- a/src/Flarum/Api/Actions/Users/Create.php +++ /dev/null @@ -1,39 +0,0 @@ -input('users.username'); - $email = $this->input('users.email'); - $password = $this->input('users.password'); - $command = new RegisterUserCommand($username, $email, $password, User::current()); - - Event::fire('Flarum.Api.Actions.Users.Create.WillExecuteCommand', [$command]); - - $user = $this->commandBus->execute($command); - - // Presumably, the user was created successfully. (The command handler - // would have thrown an exception if not.) We set this post as our - // document's primary element. - $serializer = new UserSerializer; - $this->document->setPrimaryElement($serializer->resource($user)); - - return $this->respondWithDocument(201); - } -} diff --git a/src/Flarum/Api/Actions/Users/Delete.php b/src/Flarum/Api/Actions/Users/Delete.php deleted file mode 100644 index 2f58caa..0000000 --- a/src/Flarum/Api/Actions/Users/Delete.php +++ /dev/null @@ -1,26 +0,0 @@ -param('id'); - $command = new DeleteUserCommand($userId, User::current()); - - Event::fire('Flarum.Api.Actions.Users.Delete.WillExecuteCommand', [$command]); - - $this->commandBus->execute($command); - - return $this->respondWithoutContent(); - } -} diff --git a/src/Flarum/Api/Actions/Users/Index.php b/src/Flarum/Api/Actions/Users/Index.php deleted file mode 100644 index 57fd4ee..0000000 --- a/src/Flarum/Api/Actions/Users/Index.php +++ /dev/null @@ -1,83 +0,0 @@ -finder = $finder; - } - - /** - * Show a list of users. - * - * @todo custom rate limit for this function? determined by if $key was valid? - * @return Response - */ - protected function run() - { - $query = $this->input('q'); - $key = $this->input('key'); - $sort = $this->sort(['', 'username', 'posts', 'discussions', 'lastActive', 'created']); - $start = $this->start(); - $count = $this->count(50, 100); - $include = $this->included(['groups']); - $relations = array_merge(['groups'], $include); - - // Set up the user finder with our search criteria, and get the - // requested range of results with the necessary relations loaded. - $this->finder->setUser(User::current()); - $this->finder->setQuery($query); - $this->finder->setSort($sort['by']); - $this->finder->setOrder($sort['order']); - $this->finder->setKey($key); - - $users = $this->finder->results($count, $start); - $users->load($relations); - - if (($total = $this->finder->getCount()) !== null) { - $this->document->addMeta('total', $total); - } - if (($key = $this->finder->getKey()) !== null) { - $this->document->addMeta('key', $key); - } - - // If there are more results, then we need to construct a URL to the - // next results page and add that to the metadata. We do this by - // compacting all of the valid query parameters which have been - // specified. - if ($this->finder->areMoreResults()) { - $start += $count; - $include = implode(',', $include); - $sort = $sort['string']; - $input = array_filter(compact('query', 'key', 'sort', 'start', 'count', 'include')); - $moreUrl = $this->buildUrl('users.index', [], $input); - } else { - $moreUrl = ''; - } - $this->document->addMeta('moreUrl', $moreUrl); - - // Finally, we can set up the user serializer and use it to create - // a collection of user results. - $serializer = new UserSerializer($relations); - $this->document->setPrimaryElement($serializer->collection($users)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Users/Show.php b/src/Flarum/Api/Actions/Users/Show.php deleted file mode 100644 index 11da5c5..0000000 --- a/src/Flarum/Api/Actions/Users/Show.php +++ /dev/null @@ -1,26 +0,0 @@ -findOrFail($this->param('id')); - - // Set up the user serializer, which we will use to create the - // document's primary resource. We will specify that we want the - // 'groups' relation to be included by default. - $serializer = new UserSerializer(['groups']); - $this->document->setPrimaryElement($serializer->resource($user)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/Actions/Users/Update.php b/src/Flarum/Api/Actions/Users/Update.php deleted file mode 100644 index ed15e0a..0000000 --- a/src/Flarum/Api/Actions/Users/Update.php +++ /dev/null @@ -1,40 +0,0 @@ -param('id'); - - // EditUser is a single command because we don't want to allow partial - // updates (i.e. if we were to run one command and then another, if the - // second one failed, the first one would still have succeeded.) - $command = new EditUserCommand($userId, User::current()); - $this->fillCommandWithInput($command, 'users'); - - Event::fire('Flarum.Api.Actions.Users.Update.WillExecuteCommand', [$command]); - - $user = $this->commandBus->execute($command); - - // Presumably, the user was updated successfully. (The command handler - // would have thrown an exception if not.) We set this user as our - // document's primary element. - $serializer = new UserSerializer; - $this->document->setPrimaryElement($serializer->resource($user)); - - return $this->respondWithDocument(); - } -} diff --git a/src/Flarum/Api/ApiServiceProvider.php b/src/Flarum/Api/ApiServiceProvider.php deleted file mode 100644 index 35a102a..0000000 --- a/src/Flarum/Api/ApiServiceProvider.php +++ /dev/null @@ -1,46 +0,0 @@ -package('flarum/api', 'flarum.api'); - - include __DIR__.'/../../routes.api.php'; - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { - return array(); - } -} diff --git a/src/Flarum/Api/Serializers/ActivitySerializer.php b/src/Flarum/Api/Serializers/ActivitySerializer.php deleted file mode 100644 index 65c1369..0000000 --- a/src/Flarum/Api/Serializers/ActivitySerializer.php +++ /dev/null @@ -1,19 +0,0 @@ - (int) $activity->id - ]; - - Event::fire('flarum.api.serialize.activity', [&$serialized]); - - return $serialized; - } - -} diff --git a/src/Flarum/Api/Serializers/BaseSerializer.php b/src/Flarum/Api/Serializers/BaseSerializer.php deleted file mode 100644 index c8718fe..0000000 --- a/src/Flarum/Api/Serializers/BaseSerializer.php +++ /dev/null @@ -1,102 +0,0 @@ - (int) $discussion->id, - 'title' => $discussion->title, - ]; - - return $this->attributesEvent($discussion, $attributes); - } - - /** - * Get the URL templates where this resource and its related resources can - * be accessed. - * - * @return array - */ - protected function href() - { - $href = [ - 'discussions' => $this->action('DiscussionsController@show', ['id' => '{discussions.id}']), - 'posts' => $this->action('PostsController@indexForDiscussion', ['id' => '{discussions.id}']) - ]; - - return $this->hrefEvent($href); - } -} diff --git a/src/Flarum/Api/Serializers/DiscussionSerializer.php b/src/Flarum/Api/Serializers/DiscussionSerializer.php deleted file mode 100644 index 038e26d..0000000 --- a/src/Flarum/Api/Serializers/DiscussionSerializer.php +++ /dev/null @@ -1,137 +0,0 @@ - (int) $discussion->posts_count, - 'startTime' => $discussion->start_time->toRFC3339String(), - 'lastTime' => $discussion->last_time ? $discussion->last_time->toRFC3339String() : null, - 'lastPostNumber' => $discussion->last_post_number, - 'canEdit' => $discussion->permission('edit'), - 'canDelete' => $discussion->permission('delete'), - - // temp - 'sticky' => $discussion->sticky, - 'category' => $discussion->category - ]; - - if ($state = $discussion->state) { - $attributes += [ - 'readTime' => $state->read_time ? $state->read_time->toRFC3339String() : null, - 'readNumber' => (int) $state->read_number - ]; - } - - return $this->attributesEvent($discussion, $attributes); - } - - /** - * Get a collection containing a discussion's viewable post IDs. - * - * @param Discussion $discussion - * @return Tobscure\JsonApi\Collection - */ - public function linkPosts(Discussion $discussion) - { - return (new PostBasicSerializer)->collection($discussion->posts()->whereCanView()->ids()); - } - - /** - * Get a collection containing a discussion's viewable posts. - * - * @param Discussion $discussion - * @param array $relations - * @return Tobscure\JsonApi\Collection - */ - public function includePosts(Discussion $discussion, $relations) - { - return (new PostSerializer($relations))->collection($discussion->posts()->whereCanView()->get()); - } - - /** - * Get a collection containing a discussion's relevant posts. Assumes that - * the discussion model's relevantPosts attributes has been filled (this - * happens in the DiscussionFinder.) - * - * @param Discussion $discussion - * @param array $relations - * @return Tobscure\JsonApi\Collection - */ - public function includeRelevantPosts(Discussion $discussion, $relations) - { - return (new PostBasicSerializer($relations))->collection($discussion->relevantPosts); - } - - /** - * Get a resource containing a discussion's start user. - * - * @param Discussion $discussion - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeStartUser(Discussion $discussion, $relations) - { - return (new UserBasicSerializer($relations))->resource($discussion->startUser); - } - - /** - * Get a resource containing a discussion's starting post. - * - * @param Discussion $discussion - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeStartPost(Discussion $discussion, $relations) - { - return (new PostBasicSerializer($relations))->resource($discussion->startPost); - } - - /** - * Get a resource containing a discussion's last user. - * - * @param Discussion $discussion - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeLastUser(Discussion $discussion, $relations) - { - return (new UserBasicSerializer($relations))->resource($discussion->lastUser); - } - - /** - * Get a resource containing a discussion's last post. - * - * @param Discussion $discussion - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeLastPost(Discussion $discussion, $relations) - { - return (new PostBasicSerializer($relations))->resource($discussion->lastPost); - } -} diff --git a/src/Flarum/Api/Serializers/GroupSerializer.php b/src/Flarum/Api/Serializers/GroupSerializer.php deleted file mode 100644 index d90ebc2..0000000 --- a/src/Flarum/Api/Serializers/GroupSerializer.php +++ /dev/null @@ -1,48 +0,0 @@ - (int) $group->id, - 'name' => $group->name - ]; - - return $this->attributesEvent($group, $attributes); - } - - /** - * Get the URL templates where this resource and its related resources can - * be accessed. - * - * @return array - */ - public function href() - { - return [ - 'groups' => $this->action('GroupsController@show', ['id' => '{groups.id}']), - 'users' => $this->action('UsersController@indexForGroup', ['id' => '{groups.id}']) - ]; - } -} diff --git a/src/Flarum/Api/Serializers/PostBasicSerializer.php b/src/Flarum/Api/Serializers/PostBasicSerializer.php deleted file mode 100644 index a76dc7c..0000000 --- a/src/Flarum/Api/Serializers/PostBasicSerializer.php +++ /dev/null @@ -1,85 +0,0 @@ - (int) $post->id, - 'number' => (int) $post->number, - 'time' => $post->time->toRFC3339String(), - 'type' => $post->type, - 'content' => str_limit($post->content, 200) - ]; - - return $this->attributesEvent($post, $attributes); - } - - /** - * Get the URL templates where this resource and its related resources can - * be accessed. - * - * @return array - */ - public function href() - { - return [ - 'posts' => $this->action('PostsController@show', ['id' => '{posts.id}']) - ]; - } - - /** - * Get a resource containing a post's user. - * - * @param Post $post - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeUser(Post $post, $relations) - { - return (new UserBasicSerializer($relations))->resource($post->user); - } - - /** - * Get a resource containing a post's discussion ID. - * - * @param Post $post - * @return Tobscure\JsonApi\Resource - */ - public function linkDiscussion(Post $post) - { - return (new DiscussionBasicSerializer)->resource($post->discussion_id); - } -} diff --git a/src/Flarum/Api/Serializers/PostSerializer.php b/src/Flarum/Api/Serializers/PostSerializer.php deleted file mode 100644 index fa23f90..0000000 --- a/src/Flarum/Api/Serializers/PostSerializer.php +++ /dev/null @@ -1,109 +0,0 @@ -type != 'comment') { - $attributes['content'] = $post->content; - } else { - // @todo move to a formatter class - $attributes['contentHtml'] = $post->content_html ?: '

    '.nl2br(htmlspecialchars(trim($post->content))).'

    '; - } - - if ($post->edit_time) { - $attributes['editTime'] = (string) $post->edit_time; - } - - if ($post->delete_time) { - $attributes['deleteTime'] = (string) $post->delete_time; - } - - $user = User::current(); - - $attributes += [ - 'canEdit' => $post->can($user, 'edit'), - 'canDelete' => $post->can($user, 'delete') - ]; - - return $this->attributesEvent($post, $attributes); - } - - /** - * Get a resource containing a post's user. - * - * @param Post $post - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeUser(Post $post, $relations = []) - { - return (new UserSerializer($relations))->resource($post->user); - } - - /** - * Get a resource containing a post's discussion. - * - * @param Post $post - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeDiscussion(Post $post, $relations = []) - { - return (new DiscussionBasicSerializer($relations))->resource($post->discussion); - } - - /** - * Get a resource containing a post's edit user. - * - * @param Post $post - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeEditUser(Post $post, $relations = []) - { - return (new UserBasicSerializer($relations))->resource($post->editUser); - } - - /** - * Get a resource containing a post's delete user. - * - * @param Post $post - * @param array $relations - * @return Tobscure\JsonApi\Resource - */ - public function includeDeleteUser(Post $post, $relations = []) - { - return (new UserBasicSerializer($relations))->resource($post->deleteUser); - } -} diff --git a/src/Flarum/Api/Serializers/UserAdminSerializer.php b/src/Flarum/Api/Serializers/UserAdminSerializer.php deleted file mode 100644 index 27817a0..0000000 --- a/src/Flarum/Api/Serializers/UserAdminSerializer.php +++ /dev/null @@ -1,21 +0,0 @@ - $user->email, - ]; - - Event::fire('flarum.api.serialize.user.admin', [&$serialized]); - - return $serialized; - } - -} diff --git a/src/Flarum/Api/Serializers/UserBasicSerializer.php b/src/Flarum/Api/Serializers/UserBasicSerializer.php deleted file mode 100644 index f9cf4c9..0000000 --- a/src/Flarum/Api/Serializers/UserBasicSerializer.php +++ /dev/null @@ -1,50 +0,0 @@ - (int) $user->id, - 'username' => $user->username, - 'avatarUrl' => $user->avatar_url - ]; - - return $this->attributesEvent($user, $attributes); - } - - /** - * Get the URL templates where this resource and its related resources can - * be accessed. - * - * @return array - */ - protected function href() - { - $href = [ - 'users' => $this->action('UsersController@show', ['id' => '{users.id}']) - ]; - - return $this->hrefEvent($href); - } -} diff --git a/src/Flarum/Api/Serializers/UserCurrentSerializer.php b/src/Flarum/Api/Serializers/UserCurrentSerializer.php deleted file mode 100644 index a2e0900..0000000 --- a/src/Flarum/Api/Serializers/UserCurrentSerializer.php +++ /dev/null @@ -1,29 +0,0 @@ -id) - { - $serialized += [ - 'time_zone' => $user->time_zone, - 'time_zone_offset' => with(new DateTimeZone($user->time_zone))->getOffset(new DateTime('now')) - // other user preferences. probably mostly from external sources (e.g. flarum/web) - ]; - } - - Event::fire('flarum.api.serialize.user.current', [&$serialized]); - - return $serialized; - } - -} diff --git a/src/Flarum/Api/Serializers/UserSerializer.php b/src/Flarum/Api/Serializers/UserSerializer.php deleted file mode 100644 index cacf518..0000000 --- a/src/Flarum/Api/Serializers/UserSerializer.php +++ /dev/null @@ -1,52 +0,0 @@ - $user->join_time ? $user->join_time->toRFC3339String() : '', - 'lastSeenTime' => $user->last_seen_time ? $user->last_seen_time->toRFC3339String() : '', - 'discussionsCount' => (int) $user->discussions_count, - 'postsCount' => (int) $user->posts_count, - 'canEdit' => $user->permission('edit'), - 'canDelete' => $user->permission('delete'), - ]; - - return $this->attributesEvent($user, $attributes); - } - - /** - * Get a collection containing a user's groups. - * - * @param User $user - * @param array $relations - * @return Tobscure\JsonApi\Collection - */ - protected function includeGroups(User $user, $relations) - { - return (new GroupSerializer($relations))->collection($user->groups); - } -} diff --git a/src/Flarum/Core/Activity/Activity.php b/src/Flarum/Core/Activity/Activity.php deleted file mode 100644 index b194b2a..0000000 --- a/src/Flarum/Core/Activity/Activity.php +++ /dev/null @@ -1,36 +0,0 @@ -belongsTo('Flarum\Core\Users\User', 'from_user_id'); - } - - public function permission($permission) - { - return User::current()->can($permission, 'activity', $this); - } - - public function editable() - { - return $this->permission('edit'); - } - - public function deletable() - { - return $this->permission('delete'); - } - -} diff --git a/src/Flarum/Core/CoreServiceProvider.php b/src/Flarum/Core/CoreServiceProvider.php deleted file mode 100644 index fb8a0e6..0000000 --- a/src/Flarum/Core/CoreServiceProvider.php +++ /dev/null @@ -1,96 +0,0 @@ -package('flarum/core', 'flarum'); - - $this->app->make('validator')->extend('username', 'Flarum\Core\Users\UsernameValidator@validate'); - - Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater'); - Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater'); - Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\PostFormatter'); - Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\TitleChangePostCreator'); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - // Start up the Laracasts Commander package. This is used as the basis - // for the Commands & Domain Events architecture used to structure - // Flarum's domain. - $this->app->register('Laracasts\Commander\CommanderServiceProvider'); - - // Register a singleton entity that represents this forum. This entity - // will be used to check for global forum permissions (like viewing the - // forum, registering, and starting discussions.) - $this->app->singleton('flarum.forum', 'Flarum\Core\Forum'); - - // Register the extensions manager object. This manages a list of - // available extensions, and provides functionality to enable/disable - // them. - $this->app->singleton('flarum.extensions', 'Flarum\Core\Support\Extensions\Manager'); - - // Register the permissions manager object. This reads the permissions - // from the permissions repository and can determine whether or not a - // user has explicitly been granted a certain permission. - $this->app->singleton('flarum.permissions', 'Flarum\Core\Permissions\Manager'); - - - - $this->app->bind('flarum.discussionFinder', 'Flarum\Core\Discussions\DiscussionFinder'); - - - // $this->app->singleton( - // 'Flarum\Core\Repositories\Contracts\DiscussionRepository', - // function($app) - // { - // $discussion = new \Flarum\Core\Repositories\EloquentDiscussionRepository; - // return new DiscussionCacheDecorator($discussion); - // } - // ); - // $this->app->singleton( - // 'Flarum\Core\Repositories\Contracts\UserRepository', - // 'Flarum\Core\Repositories\EloquentUserRepository' - // ); - // $this->app->singleton( - // 'Flarum\Core\Repositories\Contracts\PostRepository', - // 'Flarum\Core\Repositories\EloquentPostRepository' - // ); - // $this->app->singleton( - // 'Flarum\Core\Repositories\Contracts\GroupRepository', - // 'Flarum\Core\Repositories\EloquentGroupRepository' - // ); - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { - return array(); - } -} diff --git a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommand.php b/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommand.php deleted file mode 100644 index a788a8b..0000000 --- a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommand.php +++ /dev/null @@ -1,14 +0,0 @@ -discussionId = $discussionId; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php b/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php deleted file mode 100644 index 6008c70..0000000 --- a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionCommandHandler.php +++ /dev/null @@ -1,33 +0,0 @@ -discussions = $discussions; - } - - public function handle($command) - { - $user = $command->user; - $discussion = $this->discussions->findOrFail($command->discussionId, $user); - - $discussion->assertCan($user, 'delete'); - - Event::fire('Flarum.Core.Discussions.Commands.DeleteDiscussion.DiscussionWillBeDeleted', [$discussion, $command]); - - $this->discussions->delete($discussion); - $this->dispatchEventsFor($discussion); - - return $discussion; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php deleted file mode 100644 index 3f0ccd0..0000000 --- a/src/Flarum/Core/Discussions/Commands/DeleteDiscussionValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -discussionId = $discussionId; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php b/src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php deleted file mode 100644 index cb92553..0000000 --- a/src/Flarum/Core/Discussions/Commands/EditDiscussionCommandHandler.php +++ /dev/null @@ -1,38 +0,0 @@ -discussions = $discussions; - } - - public function handle($command) - { - $user = $command->user; - $discussion = $this->discussions->findOrFail($command->discussionId, $user); - - $discussion->assertCan($user, 'edit'); - - if (isset($command->title)) { - $discussion->rename($command->title, $user); - } - - Event::fire('Flarum.Core.Discussions.Commands.EditDiscussion.DiscussionWillBeSaved', [$discussion, $command]); - - $this->discussions->save($discussion); - $this->dispatchEventsFor($discussion); - - return $discussion; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/EditDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/EditDiscussionValidator.php deleted file mode 100644 index 8fe7741..0000000 --- a/src/Flarum/Core/Discussions/Commands/EditDiscussionValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -discussionId = $discussionId; - $this->user = $user; - $this->readNumber = $readNumber; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/ReadDiscussionCommandHandler.php b/src/Flarum/Core/Discussions/Commands/ReadDiscussionCommandHandler.php deleted file mode 100644 index 363cfe2..0000000 --- a/src/Flarum/Core/Discussions/Commands/ReadDiscussionCommandHandler.php +++ /dev/null @@ -1,35 +0,0 @@ -discussions = $discussions; - } - - public function handle($command) - { - $user = $command->user; - $discussion = $this->discussions->findOrFail($command->discussionId, $user); - - $discussion->state = $this->discussions->getState($discussion, $user); - $discussion->state->read($command->readNumber); - - Event::fire('Flarum.Core.Discussions.Commands.ReadDiscussion.StateWillBeSaved', [$discussion, $command]); - - $this->discussions->saveState($discussion->state); - $this->dispatchEventsFor($discussion->state); - - return $discussion; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php deleted file mode 100644 index 46258ce..0000000 --- a/src/Flarum/Core/Discussions/Commands/ReadDiscussionValidator.php +++ /dev/null @@ -1,19 +0,0 @@ -user->exists) { - throw new PermissionDeniedException; - } - - parent::validate($command); - } -} diff --git a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php b/src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php deleted file mode 100644 index 5fbfa69..0000000 --- a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommand.php +++ /dev/null @@ -1,17 +0,0 @@ -title = $title; - $this->content = $content; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php b/src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php deleted file mode 100644 index 4cd0176..0000000 --- a/src/Flarum/Core/Discussions/Commands/StartDiscussionCommandHandler.php +++ /dev/null @@ -1,59 +0,0 @@ -forum = $forum; - $this->discussionRepo = $discussionRepo; - $this->commandBus = $commandBus; - } - - public function handle($command) - { - $this->forum->assertCan($command->user, 'startDiscussion'); - - // Create a new Discussion entity, persist it, and dispatch domain - // events. Before persistance, though, fire an event to give plugins - // an opportunity to alter the discussion entity based on data in the - // command they may have passed through in the controller. - $discussion = Discussion::start( - $command->title, - $command->user - ); - - Event::fire('Flarum.Core.Discussions.Commands.StartDiscussion.DiscussionWillBeSaved', [$discussion, $command]); - - $this->discussionRepo->save($discussion); - - // Now that the discussion has been created, we can add the first post. - // For now we will do this by running the PostReply command, but as this - // will trigger a domain event that is slightly semantically incorrect - // in this situation (ReplyWasPosted), we may need to reconsider someday. - $this->commandBus->execute( - new PostReplyCommand($discussion->id, $command->content, $command->user) - ); - - $this->dispatchEventsFor($discussion); - - return $discussion; - } -} diff --git a/src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php b/src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php deleted file mode 100644 index a00c68a..0000000 --- a/src/Flarum/Core/Discussions/Commands/StartDiscussionValidator.php +++ /dev/null @@ -1,7 +0,0 @@ - 'required', - 'start_time' => 'required|date', - 'posts_count' => 'integer', - 'start_user_id' => 'integer', - 'start_post_id' => 'integer', - 'last_time' => 'date', - 'last_user_id' => 'integer', - 'last_post_id' => 'integer', - 'last_post_number' => 'integer' - ]; - - public static function boot() - { - parent::boot(); - - static::grant(function ($grant, $user, $permission) { - return app('flarum.permissions')->granted($user, $permission, 'discussion'); - }); - - // Grant view access to a discussion if the user can view the forum. - static::grant('view', function ($grant, $user) { - return app('flarum.forum')->can($user, 'view'); - }); - - // Allow a user to edit their own discussion. - static::grant('edit', function ($grant, $user) { - if (app('flarum.permissions')->granted($user, 'editOwn', 'discussion')) { - $grant->where('user_id', $user->id); - } - }); - - static::deleted(function ($discussion) { - $discussion->raise(new Events\DiscussionWasDeleted($discussion)); - - $discussion->posts()->delete(); - $discussion->readers()->detach(); - }); - } - - public static function start($title, $user) - { - $discussion = new static; - - $discussion->title = $title; - $discussion->start_time = time(); - $discussion->start_user_id = $user->id; - - $discussion->raise(new Events\DiscussionWasStarted($discussion)); - - return $discussion; - } - - public function setLastPost($post) - { - $this->last_time = $post->time; - $this->last_user_id = $post->user_id; - $this->last_post_id = $post->id; - $this->last_post_number = $post->number; - } - - public function refreshLastPost() - { - $lastPost = $this->dialog()->orderBy('time', 'desc')->first(); - $this->setLastPost($lastPost); - } - - public function refreshPostsCount() - { - $this->posts_count = $this->dialog()->count(); - } - - public function rename($title, $user) - { - if ($this->title === $title) { - return; - } - - $this->title = $title; - - $this->raise(new Events\DiscussionWasRenamed($this, $user)); - } - - public function getDates() - { - return ['start_time', 'last_time']; - } - - public function posts() - { - return $this->hasMany('Flarum\Core\Posts\Post')->orderBy('time', 'asc'); - } - - public function dialog() - { - return $this->posts()->where('type', 'comment')->whereNull('delete_time'); - } - - public function startPost() - { - return $this->belongsTo('Flarum\Core\Posts\Post', 'start_post_id'); - } - - public function startUser() - { - return $this->belongsTo('Flarum\Core\Users\User', 'start_user_id'); - } - - public function lastPost() - { - return $this->belongsTo('Flarum\Core\Posts\Post', 'last_post_id'); - } - - public function lastUser() - { - return $this->belongsTo('Flarum\Core\Users\User', 'last_user_id'); - } - - public function readers() - { - return $this->belongsToMany('Flarum\Core\Users\User', 'users_discussions'); - } - - public function state($userId = null) - { - if (is_null($userId)) { - $userId = User::current()->id; - } - return $this->hasOne('Flarum\Core\Discussions\DiscussionState')->where('user_id', $userId); - } - - public function stateFor($user) - { - $state = $this->state($user->id)->first(); - - if (! $state) { - $state = new DiscussionState; - $state->discussion_id = $this->id; - $state->user_id = $user->id; - } - - return $state; - } - - public function scopePermission($query, $permission, $user = null) - { - if (is_null($user)) { - $user = User::current(); - } - return $this->scopeWhereCan($query, $user, $permission); - } - - public function scopeWhereCanView($query, $user = null) - { - return $this->scopePermission($query, 'view', $user); - } - - public function permission($permission, $user = null) - { - if (is_null($user)) { - $user = User::current(); - } - return $this->can($user, $permission); - } - - public function assertCan($user, $permission) - { - if (! $this->can($user, $permission)) { - throw new PermissionDeniedException; - } - } -} diff --git a/src/Flarum/Core/Discussions/DiscussionFinder.php b/src/Flarum/Core/Discussions/DiscussionFinder.php deleted file mode 100644 index 0efabbf..0000000 --- a/src/Flarum/Core/Discussions/DiscussionFinder.php +++ /dev/null @@ -1,258 +0,0 @@ - ['last_time', 'desc'], - 'replies' => ['posts_count', 'desc'], - 'created' => ['start_time', 'desc'] - ]; - - protected $order; - - protected $key; - - protected $count; - - protected $areMoreResults; - - protected $fulltext; - - public function __construct($user = null, $tokens = null, $sort = null, $order = null, $key = null) - { - $this->user = $user; - $this->tokens = $tokens; - $this->sort = $sort; - $this->order = $order; - $this->key = $key; - } - - public function getUser() - { - return $this->user; - } - - public function setUser($user) - { - $this->user = $user; - } - - public function getTokens() - { - return $this->tokens; - } - - public function setTokens($tokens) - { - $this->tokens = $tokens; - } - - public function setQuery($query) - { - $tokenizer = new Tokenizer($query); - $this->setTokens($tokenizer->tokenize()); - } - - public function getSort() - { - return $this->sort; - } - - public function setSort($sort) - { - $this->sort = $sort; - } - - public function getOrder() - { - return $this->order; - } - - public function setOrder($order) - { - $this->order = $order; - } - - public function getKey() - { - return $this->key; - } - - public function setKey($key) - { - $this->key = $key; - } - - protected function getCacheKey() - { - return 'discussions.'.$this->key; - } - - public function getCount() - { - return $this->count; - } - - public function areMoreResults() - { - return $this->areMoreResults; - } - - public function fulltext() - { - return $this->fulltext; - } - - public function results($count = null, $start = 0, $load = []) - { - $relevantPosts = false; - - if (in_array('relevantPosts', $load)) { - $load = array_diff($load, ['relevantPosts', 'relevantPosts.user']); - $relevantPosts = true; - } - - $ids = null; - $query = Discussion::whereCan($this->user, 'view'); - $query->with($load); - - if ($this->key and Cache::has($key = $this->getCacheKey())) { - $ids = Cache::get($key); - } elseif (count($this->tokens)) { - // foreach ($tokens as $type => $value) - // { - // switch ($type) - // { - // case 'flag:draft': - // case 'flag:muted': - // case 'flag:subscribed': - // case 'flag:private': - // // pre-process - // $ids = $this->discussions->getDraftIdsForUser(Auth::user()); - // $ids = $this->discussions->getMutedIdsForUser(Auth::user()); - // $ids = $this->discussions->getSubscribedIdsForUser(Auth::user()); - // $ids = $this->discussions->getPrivateIdsForUser(Auth::user()); - // // $user->permissions['discussion']['view'] = [1,2,3] - // break; - // } - // } - - // $search = $this->search->create(); - // $search->limitToIds($ids); - // $search->setQuery($query); - // $search->setSort($sort); - // $search->setSortOrder($sortOrder); - // $results = $search->results(); - - // process flag:unread here? - - // parse the tokens. - // run ID filters. - - // TESTING lol - $this->fulltext = reset($this->tokens); - $posts = Post::whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$this->fulltext]) - ->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$this->fulltext]); - - $posts = $posts->select('id', 'discussion_id'); - - $posts = $posts->get(); - - $ids = []; - foreach ($posts as $post) { - if (empty($ids[$post->discussion_id])) { - $ids[$post->discussion_id] = []; - } - $ids[$post->discussion_id][] = $post->id; - } - - if ($this->fulltext and ! $this->sort) { - $this->sort = 'relevance'; - } - - if (! is_null($ids)) { - $this->key = str_random(); - } - - // run other tokens - // $discussions->where(''); - } - - if (! is_null($ids)) { - Cache::put($this->getCacheKey(), $ids, 10); // recache - $this->count = count($ids); - - if (! $ids) { - return []; - } - $query->whereIn('id', array_keys($ids)); - - // If we're sorting by relevance, assume that the IDs we've been provided - // are already sorted by relevance. Therefore, we'll get discussions in - // the order that they are in. - if ($this->sort == 'relevance') { - foreach ($ids as $id) { - $query->orderBy(DB::raw('id != '.(int) $id)); - } - } - } - - if (empty($this->sort)) { - reset($this->sortMap); - $this->sort = key($this->sortMap); - } - if (! empty($this->sortMap[$this->sort])) { - list($column, $order) = $this->sortMap[$this->sort]; - $query->orderBy($column, $this->order ?: $order); - } - - if ($start > 0) { - $query->skip($start); - } - if ($count > 0) { - $query->take($count + 1); - $results = $query->get(); - $this->areMoreResults = $results->count() > $count; - if ($this->areMoreResults) { - $results->pop(); - } - } else { - $results = $query->get(); - } - - if (!empty($relevantPosts)) { - $postIds = []; - foreach ($ids as $id => &$posts) { - $postIds = array_merge($postIds, array_slice($posts, 0, 2)); - } - $posts = Post::with('user')->whereCan($this->user, 'view')->whereIn('id', $postIds)->get(); - - foreach ($results as $discussion) { - $discussion->relevantPosts = $posts->filter(function ($post) use ($discussion) { - return $post->discussion_id == $discussion->id; - }) - ->slice(0, 2) - ->each(function ($post) { - $pos = strpos(strtolower($post->content), strtolower($this->fulltext)); - // TODO: make clipping more intelligent (full words only) - $start = max(0, $pos - 50); - $post->content = ($start > 0 ? '...' : '').str_limit(substr($post->content, $start), 300); - }); - } - } - - return $results; - } -} diff --git a/src/Flarum/Core/Discussions/DiscussionRepository.php b/src/Flarum/Core/Discussions/DiscussionRepository.php deleted file mode 100644 index b73f512..0000000 --- a/src/Flarum/Core/Discussions/DiscussionRepository.php +++ /dev/null @@ -1,43 +0,0 @@ -whereCanView($user); - } - - return $query->findOrFail($id); - } - - public function save(Discussion $discussion) - { - $discussion->assertValid(); - $discussion->save(); - } - - public function delete(Discussion $discussion) - { - $discussion->delete(); - } - - public function getState(Discussion $discussion, User $user) - { - return $discussion->stateFor($user); - } - - public function saveState(DiscussionState $state) - { - $state->save(); - } -} diff --git a/src/Flarum/Core/Discussions/DiscussionState.php b/src/Flarum/Core/Discussions/DiscussionState.php deleted file mode 100644 index 8bbae7e..0000000 --- a/src/Flarum/Core/Discussions/DiscussionState.php +++ /dev/null @@ -1,49 +0,0 @@ -belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id'); - } - - public function user() - { - return $this->belongsTo('Flarum\Core\Users\User', 'user_id'); - } - - public function read($number) - { - $this->read_number = $number; // only if it's greater than the old one - $this->read_time = time(); - - $this->raise(new Events\DiscussionWasRead($this)); - } - - /** - * Set the keys for a save update query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function setKeysForSaveQuery(\Illuminate\Database\Eloquent\Builder $query) - { - $query->where('discussion_id', $this->discussion_id) - ->where('user_id', $this->user_id); - - return $query; - } -} diff --git a/src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php b/src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php deleted file mode 100644 index 8efa623..0000000 --- a/src/Flarum/Core/Discussions/Events/DiscussionWasDeleted.php +++ /dev/null @@ -1,13 +0,0 @@ -discussion = $discussion; - } -} diff --git a/src/Flarum/Core/Discussions/Events/DiscussionWasRead.php b/src/Flarum/Core/Discussions/Events/DiscussionWasRead.php deleted file mode 100644 index 4fcefa9..0000000 --- a/src/Flarum/Core/Discussions/Events/DiscussionWasRead.php +++ /dev/null @@ -1,13 +0,0 @@ -state = $state; - } -} diff --git a/src/Flarum/Core/Discussions/Events/DiscussionWasRenamed.php b/src/Flarum/Core/Discussions/Events/DiscussionWasRenamed.php deleted file mode 100644 index 3409c48..0000000 --- a/src/Flarum/Core/Discussions/Events/DiscussionWasRenamed.php +++ /dev/null @@ -1,17 +0,0 @@ -discussion = $discussion; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Discussions/Events/DiscussionWasStarted.php b/src/Flarum/Core/Discussions/Events/DiscussionWasStarted.php deleted file mode 100644 index 5e341c7..0000000 --- a/src/Flarum/Core/Discussions/Events/DiscussionWasStarted.php +++ /dev/null @@ -1,13 +0,0 @@ -discussion = $discussion; - } -} diff --git a/src/Flarum/Core/Entity.php b/src/Flarum/Core/Entity.php deleted file mode 100644 index a766133..0000000 --- a/src/Flarum/Core/Entity.php +++ /dev/null @@ -1,81 +0,0 @@ -validator = $validator ?: \App::make('validator'); - } - - public function valid() - { - return $this->getValidator()->passes(); - } - - public function assertValid() - { - $validation = $this->getValidator(); - - if ($validation->fails()) { - $this->throwValidationException($validation->errors(), $validation->getData()); - } - } - - protected function getValidator() - { - $rules = $this->expandUniqueRules(static::$rules); - - return $this->validator->make($this->attributes, $rules, static::$messages); - } - - protected function expandUniqueRules($rules) - { - foreach ($rules as $column => &$ruleset) { - if (is_string($ruleset)) { - $ruleset = explode('|', $ruleset); - } - foreach ($ruleset as &$rule) { - if (strpos($rule, 'unique') === 0) { - $parts = explode(':', $rule); - $key = $this->getKey() ?: 'NULL'; - $rule = 'unique:'.$this->getTable().','.$column.','.$key.','.$this->getKeyName(); - if (! empty($parts[1])) { - $wheres = explode(',', $parts[1]); - foreach ($wheres as &$where) { - $where .= ','.$this->$where; - } - $rule .= ','.implode(',', $wheres); - } - } - } - } - - return $rules; - } - - protected function throwValidationException($errors, $input) - { - $exception = new ValidationFailureException; - $exception->setErrors($errors)->setInput($input); - throw $exception; - } -} diff --git a/src/Flarum/Core/Forum.php b/src/Flarum/Core/Forum.php deleted file mode 100644 index 2270e25..0000000 --- a/src/Flarum/Core/Forum.php +++ /dev/null @@ -1,26 +0,0 @@ -granted($user, $permission, 'forum'); - }); - } - - public function assertCan($user, $permission) - { - if (! $this->can($user, $permission)) { - throw new PermissionDeniedException; - } - } -} diff --git a/src/Flarum/Core/Groups/Group.php b/src/Flarum/Core/Groups/Group.php deleted file mode 100644 index 8f0f50a..0000000 --- a/src/Flarum/Core/Groups/Group.php +++ /dev/null @@ -1,18 +0,0 @@ -belongsToMany('Flarum\Core\Users\User', 'users_groups'); - } - -} diff --git a/src/Flarum/Core/Groups/GroupRepository.php b/src/Flarum/Core/Groups/GroupRepository.php deleted file mode 100644 index b5e4c29..0000000 --- a/src/Flarum/Core/Groups/GroupRepository.php +++ /dev/null @@ -1,15 +0,0 @@ -save(); - } - - public function delete(Group $group) - { - $group->delete(); - } -} diff --git a/src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php b/src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php deleted file mode 100644 index de178ed..0000000 --- a/src/Flarum/Core/Listeners/DiscussionMetadataUpdater.php +++ /dev/null @@ -1,63 +0,0 @@ -discussionRepo = $discussionRepo; - } - - public function whenReplyWasPosted(ReplyWasPosted $event) - { - $discussion = $this->discussionRepo->find($event->post->discussion_id); - - $discussion->replies_count++; - $discussion->setLastPost($event->post); - - $this->discussionRepo->save($discussion); - } - - public function whenPostWasDeleted(PostWasDeleted $event) - { - $this->removePost($event->post); - } - - public function whenPostWasHidden(PostWasHidden $event) - { - $this->removePost($event->post); - } - - public function whenPostWasRestored(PostWasRestored $event) - { - $discussion = $this->discussionRepo->find($event->post->discussion_id); - - $discussion->replies_count++; - $discussion->refreshLastPost(); - - $this->discussionRepo->save($discussion); - } - - protected function removePost(Post $post) - { - $discussion = $this->discussionRepo->find($post->discussion_id); - - $discussion->replies_count--; - - if ($discussion->last_post_id == $post->id) { - $discussion->refreshLastPost(); - } - - $this->discussionRepo->save($discussion); - } -} diff --git a/src/Flarum/Core/Listeners/PostFormatter.php b/src/Flarum/Core/Listeners/PostFormatter.php deleted file mode 100644 index c2efd35..0000000 --- a/src/Flarum/Core/Listeners/PostFormatter.php +++ /dev/null @@ -1,48 +0,0 @@ -postRepo = $postRepo; - } - - protected function formatPost($post) - { - $post = $this->postRepo->find($post->id); - - // By default, we want to convert paragraphs of text into

    tags. - // And maybe also wrap URLs in tags. - // However, we want to allow plugins to completely override this, and/or - // just do some superficial formatting afterwards. - - $html = htmlspecialchars($post->content); - - // Primary formatter - $html = '

    '.$html.'

    '; // Move this to Flarum\Core\Support\Formatters\BasicFormatter < FormatterInterface - - // Run additional formatters - - $post->content_html = $html; - $this->postRepo->save($post); - } - - public function whenReplyWasPosted(ReplyWasPosted $event) - { - $this->formatPost($event->post); - } - - public function whenPostWasRevised(PostWasRevised $event) - { - $this->formatPost($event->post); - } -} diff --git a/src/Flarum/Core/Listeners/TitleChangePostCreator.php b/src/Flarum/Core/Listeners/TitleChangePostCreator.php deleted file mode 100644 index 18a2c1b..0000000 --- a/src/Flarum/Core/Listeners/TitleChangePostCreator.php +++ /dev/null @@ -1,28 +0,0 @@ -postRepo = $postRepo; - } - - public function whenDiscussionWasRenamed(DiscussionWasRenamed $event) - { - $post = TitleChangePost::reply( - $event->discussion->id, - $event->discussion->title, - $event->user->id - ); - - $this->postRepo->save($post); - } -} diff --git a/src/Flarum/Core/Listeners/UserMetadataUpdater.php b/src/Flarum/Core/Listeners/UserMetadataUpdater.php deleted file mode 100644 index ee54c29..0000000 --- a/src/Flarum/Core/Listeners/UserMetadataUpdater.php +++ /dev/null @@ -1,70 +0,0 @@ -userRepo = $userRepo; - } - - protected function updateRepliesCount($userId, $amount) - { - $user = $this->userRepo->find($userId); - - $user->posts_count += $amount; - - $this->userRepo->save($user); - } - - protected function updateDiscussionsCount($userId, $amount) - { - $user = $this->userRepo->find($userId); - - $user->discussions_count += $amount; - - $this->userRepo->save($user); - } - - public function whenReplyWasPosted(ReplyWasPosted $event) - { - $this->updateRepliesCount($event->post->user_id, 1); - } - - public function whenPostWasDeleted(PostWasDeleted $event) - { - $this->updateRepliesCount($event->post->user_id, -1); - } - - public function whenPostWasHidden(PostWasHidden $event) - { - $this->updateRepliesCount($event->post->user_id, -1); - } - - public function whenPostWasRestored(PostWasRestored $event) - { - $this->updateRepliesCount($event->post->user_id, 1); - } - - public function whenDiscussionWasStarted(DiscussionWasStarted $event) - { - $this->updateDiscussionsCount($event->discussion->start_user_id, 1); - } - - public function whenDiscussionWasDeleted(DiscussionWasDeleted $event) - { - $this->updateDiscussionsCount($event->discussion->start_user_id, -1); - } -} diff --git a/src/Flarum/Core/Permissions/Manager.php b/src/Flarum/Core/Permissions/Manager.php deleted file mode 100644 index ce09184..0000000 --- a/src/Flarum/Core/Permissions/Manager.php +++ /dev/null @@ -1,42 +0,0 @@ -permissions = $permissions; - } - - public function getMap() - { - if (is_null($this->map)) { - $permissions = $this->permissions->get(); - foreach ($permissions as $permission) { - $this->map[$permission->entity.'.'.$permission->permission][] = $permission->grantee; - } - } - - return $this->map; - } - - public function granted($user, $permission, $entity) - { - $grantees = $user->getGrantees(); - - // If user has admin, then yes! - if (in_array('group.1', $grantees)) { - return true; - } - - $permission = $entity.'.'.$permission; - - $map = $this->getMap(); - $mappedGrantees = isset($map[$permission]) ? $map[$permission] : []; - - return (bool) array_intersect($grantees, $mappedGrantees); - } -} diff --git a/src/Flarum/Core/Permissions/Permission.php b/src/Flarum/Core/Permissions/Permission.php deleted file mode 100644 index 2c2e3b4..0000000 --- a/src/Flarum/Core/Permissions/Permission.php +++ /dev/null @@ -1,7 +0,0 @@ -assertValid(); - $permission->save(); - } - - public function delete(Permission $permission) - { - $permission->delete(); - } -} diff --git a/src/Flarum/Core/Posts/Commands/DeletePostCommand.php b/src/Flarum/Core/Posts/Commands/DeletePostCommand.php deleted file mode 100644 index 16f3d85..0000000 --- a/src/Flarum/Core/Posts/Commands/DeletePostCommand.php +++ /dev/null @@ -1,14 +0,0 @@ -postId = $postId; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php b/src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php deleted file mode 100644 index 0493f86..0000000 --- a/src/Flarum/Core/Posts/Commands/DeletePostCommandHandler.php +++ /dev/null @@ -1,33 +0,0 @@ -posts = $posts; - } - - public function handle($command) - { - $user = $command->user; - $post = $this->posts->findOrFail($command->postId, $user); - - $post->assertCan($user, 'delete'); - - Event::fire('Flarum.Core.Posts.Commands.DeletePost.PostWillBeDeleted', [$post, $command]); - - $this->posts->delete($post); - $this->dispatchEventsFor($post); - - return $post; - } -} diff --git a/src/Flarum/Core/Posts/Commands/DeletePostValidator.php b/src/Flarum/Core/Posts/Commands/DeletePostValidator.php deleted file mode 100644 index c3e9aa4..0000000 --- a/src/Flarum/Core/Posts/Commands/DeletePostValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -postId = $postId; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php b/src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php deleted file mode 100644 index 75817a4..0000000 --- a/src/Flarum/Core/Posts/Commands/EditPostCommandHandler.php +++ /dev/null @@ -1,44 +0,0 @@ -posts = $posts; - } - - public function handle($command) - { - $user = $command->user; - $post = $this->posts->findOrFail($command->postId, $user); - - $post->assertCan($user, 'edit'); - - if (isset($command->content)) { - $post->revise($command->content, $user); - } - - if ($command->hidden === true) { - $post->hide($user); - } elseif ($command->hidden === false) { - $post->restore($user); - } - - Event::fire('Flarum.Core.Posts.Commands.EditPost.PostWillBeSaved', [$post, $command]); - - $this->posts->save($post); - $this->dispatchEventsFor($post); - - return $post; - } -} diff --git a/src/Flarum/Core/Posts/Commands/EditPostValidator.php b/src/Flarum/Core/Posts/Commands/EditPostValidator.php deleted file mode 100644 index 63a5608..0000000 --- a/src/Flarum/Core/Posts/Commands/EditPostValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -discussionId = $discussionId; - $this->content = $content; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php b/src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php deleted file mode 100644 index bedcb5e..0000000 --- a/src/Flarum/Core/Posts/Commands/PostReplyCommandHandler.php +++ /dev/null @@ -1,53 +0,0 @@ -discussions = $discussions; - $this->posts = $posts; - } - - public function handle($command) - { - $user = $command->user; - - // Make sure the user has permission to reply to this discussion. First, - // make sure the discussion exists and that the user has permission to - // view it; if not, fail with a ModelNotFound exception so we don't give - // away the existence of the discussion. If the user is allowed to view - // it, check if they have permission to reply. - $discussion = $this->discussions->findOrFail($command->discussionId, $user); - - $discussion->assertCan($user, 'reply'); - - // Create a new Post entity, persist it, and dispatch domain events. - // Before persistance, though, fire an event to give plugins an - // opportunity to alter the post entity based on data in the command. - $post = CommentPost::reply( - $command->discussionId, - $command->content, - $user->id - ); - - Event::fire('Flarum.Core.Posts.Commands.PostReply.PostWillBeSaved', [$post, $command]); - - $this->posts->save($post); - $this->dispatchEventsFor($post); - - return $post; - } -} diff --git a/src/Flarum/Core/Posts/Commands/PostReplyValidator.php b/src/Flarum/Core/Posts/Commands/PostReplyValidator.php deleted file mode 100644 index e54377e..0000000 --- a/src/Flarum/Core/Posts/Commands/PostReplyValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -number = $post->discussion->number_index++; - $post->discussion->save(); - }); - } - - public static function reply($discussionId, $content, $userId) - { - $post = new static; - - $post->content = $content; - $post->time = time(); - $post->discussion_id = $discussionId; - $post->user_id = $userId; - $post->type = 'comment'; - - $post->raise(new Events\ReplyWasPosted($post)); - - return $post; - } - - public function revise($content, $user) - { - $this->content = $content; - - $this->edit_time = time(); - $this->edit_user_id = $user->id; - - $this->raise(new Events\PostWasRevised($this)); - } - - public function hide($user) - { - $this->delete_time = time(); - $this->delete_user_id = $user->id; - - $this->raise(new Events\PostWasHidden($this)); - } - - public function restore($user) - { - $this->delete_time = null; - $this->delete_user_id = null; - - $this->raise(new Events\PostWasRestored($this)); - } -} diff --git a/src/Flarum/Core/Posts/Events/PostWasDeleted.php b/src/Flarum/Core/Posts/Events/PostWasDeleted.php deleted file mode 100644 index b57e680..0000000 --- a/src/Flarum/Core/Posts/Events/PostWasDeleted.php +++ /dev/null @@ -1,13 +0,0 @@ -post = $post; - } -} diff --git a/src/Flarum/Core/Posts/Events/PostWasHidden.php b/src/Flarum/Core/Posts/Events/PostWasHidden.php deleted file mode 100644 index 2fc8daa..0000000 --- a/src/Flarum/Core/Posts/Events/PostWasHidden.php +++ /dev/null @@ -1,13 +0,0 @@ -post = $post; - } -} diff --git a/src/Flarum/Core/Posts/Events/PostWasRestored.php b/src/Flarum/Core/Posts/Events/PostWasRestored.php deleted file mode 100644 index 0b65f02..0000000 --- a/src/Flarum/Core/Posts/Events/PostWasRestored.php +++ /dev/null @@ -1,13 +0,0 @@ -post = $post; - } -} diff --git a/src/Flarum/Core/Posts/Events/PostWasRevised.php b/src/Flarum/Core/Posts/Events/PostWasRevised.php deleted file mode 100644 index 8c9b019..0000000 --- a/src/Flarum/Core/Posts/Events/PostWasRevised.php +++ /dev/null @@ -1,13 +0,0 @@ -post = $post; - } -} diff --git a/src/Flarum/Core/Posts/Events/ReplyWasPosted.php b/src/Flarum/Core/Posts/Events/ReplyWasPosted.php deleted file mode 100644 index 9b9a9f6..0000000 --- a/src/Flarum/Core/Posts/Events/ReplyWasPosted.php +++ /dev/null @@ -1,13 +0,0 @@ -post = $post; - } -} diff --git a/src/Flarum/Core/Posts/Post.php b/src/Flarum/Core/Posts/Post.php deleted file mode 100644 index 7c82c93..0000000 --- a/src/Flarum/Core/Posts/Post.php +++ /dev/null @@ -1,113 +0,0 @@ - 'required|integer', - 'time' => 'required|date', - 'content' => 'required', - 'number' => 'integer', - 'user_id' => 'integer', - 'edit_time' => 'date', - 'edit_user_id' => 'integer', - 'delete_time' => 'date', - 'delete_user_id' => 'integer', - ]; - - public static function boot() - { - parent::boot(); - - static::grant(function ($grant, $user, $permission) { - return app('flarum.permissions')->granted($user, $permission, 'post'); - }); - - // Grant view access to a post only if the user can also view the - // discussion which the post is in. Also, the if the post is hidden, - // the user must have edit permissions too. - static::grant('view', function ($grant, $user) { - $grant->whereCan('view', 'discussion'); - }); - - static::check('view', function ($check, $user) { - $check->whereNull('delete_user_id') - ->orWhereCan('edit'); - }); - - // Allow a user to edit their own post, unless it has been hidden by - // someone else. - static::grant('edit', function ($grant, $user) { - $grant->whereCan('editOwn') - ->where('user_id', $user->id); - }); - - static::check('editOwn', function ($check, $user) { - $check->whereNull('delete_user_id') - ->orWhere('delete_user_id', $user->id); - }); - - static::deleted(function ($post) { - $post->raise(new Events\PostWasDeleted($post)); - }); - } - - public function discussion() - { - return $this->belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id'); - } - - public function user() - { - return $this->belongsTo('Flarum\Core\Users\User', 'user_id'); - } - - public function editUser() - { - return $this->belongsTo('Flarum\Core\Users\User', 'edit_user_id'); - } - - public function deleteUser() - { - return $this->belongsTo('Flarum\Core\Users\User', 'delete_user_id'); - } - - public function getDates() - { - return ['time', 'edit_time', 'delete_time']; - } - - // Terminates the query and returns an array of matching IDs. - // Example usage: $discussion->posts()->ids(); - public function scopeIds($query) - { - return array_map('intval', $query->get(['id'])->fetch('id')->all()); - } - - public function scopeWhereCanView($query, $user = null) - { - if (is_null($user)) { - $user = User::current(); - } - return $this->scopeWhereCan($query, $user, 'view'); - } - - public function assertCan($user, $permission) - { - if (! $this->can($user, $permission)) { - throw new PermissionDeniedException; - } - } -} diff --git a/src/Flarum/Core/Posts/PostRepository.php b/src/Flarum/Core/Posts/PostRepository.php deleted file mode 100644 index 4fa4233..0000000 --- a/src/Flarum/Core/Posts/PostRepository.php +++ /dev/null @@ -1,31 +0,0 @@ -whereCanView($user); - } - - return $query->findOrFail($id); - } - - public function save(Post $post) - { - $post->assertValid(); - $post->save(); - } - - public function delete(Post $post) - { - $post->delete(); - } -} diff --git a/src/Flarum/Core/Posts/TitleChangePost.php b/src/Flarum/Core/Posts/TitleChangePost.php deleted file mode 100644 index a9eaa65..0000000 --- a/src/Flarum/Core/Posts/TitleChangePost.php +++ /dev/null @@ -1,25 +0,0 @@ -content = $content; - $post->time = time(); - $post->discussion_id = $discussionId; - $post->user_id = $userId; - $post->type = 'titleChange'; - - return $post; - } -} diff --git a/src/Flarum/Core/Search/FulltextSearchDriver.php b/src/Flarum/Core/Search/FulltextSearchDriver.php deleted file mode 100644 index b85055b..0000000 --- a/src/Flarum/Core/Search/FulltextSearchDriver.php +++ /dev/null @@ -1,48 +0,0 @@ -table = $table; - // inject db connection? - // pass primary key name? - } - - public function results(SearchCriteria $criteria) - { - $query = DB::table($this->table); - - $this->parseConditions($criteria->conditions, $query); - - return $query->get('id'); - } - - protected function parseConditions(ConditionCollection $conditions, Query $query) - { - foreach ($conditions as $condition) - { - if ($condition instanceof ConditionOr) - { - $query->orWhere(function($query) - { - $this->parseConditions($condition->conditions, $query); - }) - } - elseif ($condition instanceof ConditionComparison) - { - // etc - } - } - } -} diff --git a/src/Flarum/Core/Search/SearchDriverInterface.php b/src/Flarum/Core/Search/SearchDriverInterface.php deleted file mode 100644 index f257675..0000000 --- a/src/Flarum/Core/Search/SearchDriverInterface.php +++ /dev/null @@ -1,8 +0,0 @@ -client = $client; - $this->index = $index; - } - - public function results(SearchCriteria $criteria) - { - foreach ($query->conditions as $condition) - { - if ($condition instanceof ConditionOr) - { - // $search->setSelect("*, IF(code = 1 OR productid = 2, 1,0) AS filter"); - // $->setFilter('filter',array(1)); - } - } - - // etc - } -} diff --git a/src/Flarum/Core/Search/Tokenizer.php b/src/Flarum/Core/Search/Tokenizer.php deleted file mode 100644 index e495e1d..0000000 --- a/src/Flarum/Core/Search/Tokenizer.php +++ /dev/null @@ -1,17 +0,0 @@ -query = $query; - } - - public function tokenize() - { - return $this->query ? [$this->query] : []; - } - -} diff --git a/src/Flarum/Core/Search/Tokens/AuthorToken.php b/src/Flarum/Core/Search/Tokens/AuthorToken.php deleted file mode 100644 index 90494f2..0000000 --- a/src/Flarum/Core/Search/Tokens/AuthorToken.php +++ /dev/null @@ -1,25 +0,0 @@ -validator = $validator; - } - - public function validate($command) - { - if (! $command->user) { - throw new InvalidArgumentException('Empty argument [user] in command ['.get_class($command).']'); - } - - $validator = $this->validator->make(get_object_vars($command), $this->rules); - - $this->fireValidationEvent([$validator, $command]); - - if ($validator->fails()) { - $this->throwValidationException($validator->errors(), $validator->getData()); - } - } - - protected function fireValidationEvent(array $arguments) - { - Event::fire(str_replace('\\', '.', get_class($this)), $arguments); - } - - protected function throwValidationException($errors, $input) - { - $exception = new ValidationFailureException; - $exception->setErrors($errors)->setInput($input); - throw $exception; - } -} diff --git a/src/Flarum/Core/Support/Exceptions/PermissionDeniedException.php b/src/Flarum/Core/Support/Exceptions/PermissionDeniedException.php deleted file mode 100644 index 3b3278a..0000000 --- a/src/Flarum/Core/Support/Exceptions/PermissionDeniedException.php +++ /dev/null @@ -1,7 +0,0 @@ -errors = new MessageBag; - } - - public function setErrors(MessageBag $errors) - { - $this->errors = $errors; - - return $this; - } - - public function getErrors() - { - return $this->errors; - } - - public function setInput(array $input) - { - $this->input = $input; - - return $this; - } - - public function getInput() - { - return $this->input; - } -} diff --git a/src/Flarum/Core/Support/Extensions/Extension.php b/src/Flarum/Core/Support/Extensions/Extension.php deleted file mode 100644 index a306278..0000000 --- a/src/Flarum/Core/Support/Extensions/Extension.php +++ /dev/null @@ -1,6 +0,0 @@ -truncate(); - } - - $this->call('Flarum\Core\Support\Seeders\ConfigTableSeeder'); - $this->call('Flarum\Core\Support\Seeders\UserTableSeeder'); - $this->call('Flarum\Core\Support\Seeders\DiscussionTableSeeder'); - } - -} diff --git a/src/Flarum/Core/Support/Seeders/DiscussionTableSeeder.php b/src/Flarum/Core/Support/Seeders/DiscussionTableSeeder.php deleted file mode 100644 index e19ff95..0000000 --- a/src/Flarum/Core/Support/Seeders/DiscussionTableSeeder.php +++ /dev/null @@ -1,134 +0,0 @@ - str_replace("'", '', rtrim($faker->realText(rand(20, 80)), '.')), - 'start_time' => $faker->dateTimeThisYear, - 'start_user_id' => rand(1, $users) - ]); - $discussion->posts_count = $posts_count; - - $post = Post::create([ - 'discussion_id' => $discussion->id, - 'number' => 1, - 'time' => $discussion->start_time, - 'user_id' => $discussion->start_user_id, - 'type' => 'comment', - 'content' => $faker->realText(rand(100, 1000)) - ]); - - $discussion->start_post_id = $post->id; - - $discussion->last_time = $post->time; - $discussion->last_user_id = $post->user_id; - $discussion->last_post_id = $post->id; - $discussion->last_post_number = $post->number; - $discussion->number_index = $post->number; - - $lastPost = null; - $count = $posts_count; - $posts = []; - $startTime = $discussion->start_time; - $numberOffset = 0; - - for ($j = 0; $j < $count - 1; $j++) { - if (rand(1, 100) == 1) { - $discussion->posts_count--; - - $post = Post::create([ - 'discussion_id' => $discussion->id, - 'time' => $startTime = date_add($startTime, date_interval_create_from_date_string('1 second')), - 'user_id' => rand(1, $users), - 'type' => 'title', - 'content' => $discussion->title - ]); - } else { - $edited = rand(1, 20) == 1; - $deleted = rand(1, 100) == 1; - - if ($deleted) { - $discussion->posts_count--; - } - - $post = Post::create([ - 'discussion_id' => $discussion->id, - 'number' => $j + 2 + $numberOffset, - 'time' => $startTime = date_add($startTime, date_interval_create_from_date_string('1 second')), - 'user_id' => rand(1, $users), - 'type' => 'comment', - 'content' => $faker->realText(rand(50, 500)), - 'edit_time' => $edited ? $startTime = date_add($startTime, date_interval_create_from_date_string('1 second')) : null, - 'edit_user_id' => $edited ? rand(1, $users) : null, - 'delete_time' => $deleted ? $startTime = date_add($startTime, date_interval_create_from_date_string('1 second')) : null, - 'delete_user_id' => $deleted ? rand(1, $users) : null, - ]); - - $posts[] = $post; - } - - if (! $lastPost or $post->time >= $lastPost->time) { - $lastPost = $post; - } - - if (rand(1, 20) == 1) { - $numberOffset += rand(0, 3); - } - } - - // Update the discussion's last post details. - if ($lastPost) { - $discussion->last_time = $lastPost->time; - $discussion->last_user_id = $lastPost->user_id; - $discussion->last_post_id = $lastPost->id; - $discussion->last_post_number = $lastPost->number; - $discussion->number_index = $lastPost->number; - } - - $discussion->save(); - - // Give some users some random discussion state data. - for ($j = rand(0, 100); $j < 100; $j++) { - try { - DiscussionState::create([ - 'discussion_id' => $discussion->id, - 'user_id' => rand(1, $users), - 'read_number' => rand(0, $posts_count - 1), - 'read_time' => $faker->dateTimeBetween($discussion->start_time, 'now') - ]); - } catch (\Illuminate\Database\QueryException $e) { - - } - } - } - - // Update user post and discussion counts. - $prefix = DB::getTablePrefix(); - DB::table('users')->update([ - 'discussions_count' => DB::raw('(SELECT COUNT(id) FROM '.$prefix.'discussions WHERE start_user_id = '.$prefix.'users.id)'), - 'posts_count' => DB::raw('(SELECT COUNT(id) FROM '.$prefix.'posts WHERE user_id = '.$prefix.'users.id and type = "comment")'), - ]); - } -} diff --git a/src/Flarum/Core/Support/Seeders/UserTableSeeder.php b/src/Flarum/Core/Support/Seeders/UserTableSeeder.php deleted file mode 100644 index e604264..0000000 --- a/src/Flarum/Core/Support/Seeders/UserTableSeeder.php +++ /dev/null @@ -1,76 +0,0 @@ - $group]); - } - - for ($i = 0; $i < 100; $i++) { - $user = User::create([ - 'username' => $faker->userName, - 'email' => $faker->safeEmail, - 'password' => 'password', - 'join_time' => $faker->dateTimeThisYear, - 'time_zone' => $faker->timezone - ]); - - // Assign the users to the 'Member' group, and possibly some others. - $user->groups()->attach(3); - if (rand(1, 50) == 1) { - $user->groups()->attach(4); - } - if (rand(1, 20) == 1) { - $user->groups()->attach(5); - } - if (rand(1, 20) == 1) { - $user->groups()->attach(1); - } - } - - // Set up the default permissions. - $permissions = [ - - // Guests can view the forum - ['group.2' , 'forum' , 'view'], - - // Members can create and reply to discussions + edit their own stuff - ['group.3' , 'discussion' , 'create'], - ['group.3' , 'discussion' , 'editOwn'], - ['group.3' , 'discussion' , 'reply'], - ['group.3' , 'post' , 'editOwn'], - - // Moderators can edit + delete stuff and suspend users - ['group.4' , 'discussion' , 'delete'], - ['group.4' , 'discussion' , 'edit'], - ['group.4' , 'post' , 'delete'], - ['group.4' , 'post' , 'edit'], - ['group.4' , 'user' , 'suspend'], - - ]; - foreach ($permissions as &$permission) { - $permission = [ - 'grantee' => $permission[0], - 'entity' => $permission[1], - 'permission' => $permission[2] - ]; - } - DB::table('permissions')->insert($permissions); - } -} diff --git a/src/Flarum/Core/Users/Commands/DeleteUserCommand.php b/src/Flarum/Core/Users/Commands/DeleteUserCommand.php deleted file mode 100644 index 4b7add6..0000000 --- a/src/Flarum/Core/Users/Commands/DeleteUserCommand.php +++ /dev/null @@ -1,14 +0,0 @@ -userId = $userId; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Users/Commands/DeleteUserCommandHandler.php b/src/Flarum/Core/Users/Commands/DeleteUserCommandHandler.php deleted file mode 100644 index 8ac3a2c..0000000 --- a/src/Flarum/Core/Users/Commands/DeleteUserCommandHandler.php +++ /dev/null @@ -1,33 +0,0 @@ -userRepo = $userRepo; - } - - public function handle($command) - { - $user = $command->user; - $userToDelete = $this->userRepo->findOrFail($command->userId, $user); - - $userToDelete->assertCan($user, 'delete'); - - Event::fire('Flarum.Core.Users.Commands.DeleteUser.UserWillBeDeleted', [$userToDelete, $command]); - - $this->userRepo->delete($userToDelete); - $this->dispatchEventsFor($userToDelete); - - return $userToDelete; - } -} diff --git a/src/Flarum/Core/Users/Commands/DeleteUserValidator.php b/src/Flarum/Core/Users/Commands/DeleteUserValidator.php deleted file mode 100644 index 7719a54..0000000 --- a/src/Flarum/Core/Users/Commands/DeleteUserValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -userId = $userId; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Users/Commands/EditUserCommandHandler.php b/src/Flarum/Core/Users/Commands/EditUserCommandHandler.php deleted file mode 100644 index 17dc3d7..0000000 --- a/src/Flarum/Core/Users/Commands/EditUserCommandHandler.php +++ /dev/null @@ -1,46 +0,0 @@ -userRepo = $userRepo; - } - - public function handle($command) - { - $user = $command->user; - $userToEdit = $this->userRepo->findOrFail($command->userId, $user); - - $userToEdit->assertCan($user, 'edit'); - - if (isset($command->username)) { - $userToEdit->username = $command->username; - } - - if (isset($command->email)) { - $userToEdit->email = $command->email; - } - - if (isset($command->password)) { - $userToEdit->password = $command->password; - } - - Event::fire('Flarum.Core.Users.Commands.EditUser.UserWillBeSaved', [$userToEdit, $command]); - - $this->userRepo->save($userToEdit); - $this->dispatchEventsFor($userToEdit); - - return $userToEdit; - } -} diff --git a/src/Flarum/Core/Users/Commands/EditUserValidator.php b/src/Flarum/Core/Users/Commands/EditUserValidator.php deleted file mode 100644 index 358c98f..0000000 --- a/src/Flarum/Core/Users/Commands/EditUserValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -username = $username; - $this->email = $email; - $this->password = $password; - $this->user = $user; - } -} diff --git a/src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php b/src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php deleted file mode 100644 index 35016bc..0000000 --- a/src/Flarum/Core/Users/Commands/RegisterUserCommandHandler.php +++ /dev/null @@ -1,49 +0,0 @@ -forum = $forum; - $this->userRepo = $userRepo; - } - - public function handle($command) - { - // Assert the the current user has permission to create a user. In the - // case of a guest trying to register an account, this will depend on - // whether or not registration is open. If the user is an admin, though, - // it will be allowed. - $this->forum->assertCan($command->user, 'register'); - - // Create a new User entity, persist it, and dispatch domain events. - // Before persistance, though, fire an event to give plugins an - // opportunity to alter the post entity based on data in the command. - $user = User::register( - $command->username, - $command->email, - $command->password - ); - - Event::fire('Flarum.Core.Users.Commands.RegisterUser.UserWillBeSaved', [$user, $command]); - - $this->userRepo->save($user); - $this->userRepo->syncGroups($user, [3]); // default groups - $this->dispatchEventsFor($user); - - return $user; - } -} diff --git a/src/Flarum/Core/Users/Commands/RegisterUserValidator.php b/src/Flarum/Core/Users/Commands/RegisterUserValidator.php deleted file mode 100644 index add4a62..0000000 --- a/src/Flarum/Core/Users/Commands/RegisterUserValidator.php +++ /dev/null @@ -1,7 +0,0 @@ -user = $user; - } -} diff --git a/src/Flarum/Core/Users/Events/PasswordWasChanged.php b/src/Flarum/Core/Users/Events/PasswordWasChanged.php deleted file mode 100644 index 9844e60..0000000 --- a/src/Flarum/Core/Users/Events/PasswordWasChanged.php +++ /dev/null @@ -1,13 +0,0 @@ -user = $user; - } -} diff --git a/src/Flarum/Core/Users/Events/UserWasDeleted.php b/src/Flarum/Core/Users/Events/UserWasDeleted.php deleted file mode 100644 index 6683080..0000000 --- a/src/Flarum/Core/Users/Events/UserWasDeleted.php +++ /dev/null @@ -1,13 +0,0 @@ -user = $user; - } -} diff --git a/src/Flarum/Core/Users/Events/UserWasRegistered.php b/src/Flarum/Core/Users/Events/UserWasRegistered.php deleted file mode 100644 index 0ecd829..0000000 --- a/src/Flarum/Core/Users/Events/UserWasRegistered.php +++ /dev/null @@ -1,13 +0,0 @@ -user = $user; - } -} diff --git a/src/Flarum/Core/Users/Events/UserWasRenamed.php b/src/Flarum/Core/Users/Events/UserWasRenamed.php deleted file mode 100644 index 68a8568..0000000 --- a/src/Flarum/Core/Users/Events/UserWasRenamed.php +++ /dev/null @@ -1,13 +0,0 @@ -user = $user; - } -} diff --git a/src/Flarum/Core/Users/Guest.php b/src/Flarum/Core/Users/Guest.php deleted file mode 100644 index 4a43164..0000000 --- a/src/Flarum/Core/Users/Guest.php +++ /dev/null @@ -1,34 +0,0 @@ -setAttribute($this->getKeyName(), 0); - - return parent::__construct($attributes); - } - - public function getGroupsAttribute() - { - if ( ! isset($this->attributes['groups'])) - { - $this->attributes['groups'] = $this->relations['groups'] = Group::where('id', Group::GUEST_ID)->get(); - } - - return $this->attributes['groups']; - } - - public function guest() - { - return true; - } - -} diff --git a/src/Flarum/Core/Users/User.php b/src/Flarum/Core/Users/User.php deleted file mode 100644 index 9790dcd..0000000 --- a/src/Flarum/Core/Users/User.php +++ /dev/null @@ -1,240 +0,0 @@ - 'required|username|unique', - 'email' => 'required|email|unique', - 'password' => 'required', - 'join_time' => 'date', - 'last_seen_time' => 'date', - 'discussions_count' => 'integer', - 'posts_count' => 'integer', - ]; - - protected $table = 'users'; - - protected $hidden = ['password']; - - public static function boot() - { - parent::boot(); - - static::grant(function ($grant, $user, $permission) { - return app('flarum.permissions')->granted($user, $permission, 'forum'); - }); - - // Grant view access to a user if the user can view the forum. - static::grant('view', function ($grant, $user) { - return app('forum')->can($user, 'view'); - }); - - // Allow a user to edit their own account. - static::grant('edit', function ($grant, $user) { - $grant->where('id', $user->id); - }); - - static::deleted(function ($user) { - $user->raise(new Events\UserWasDeleted($user)); - }); - } - - public function setUsernameAttribute($username) - { - $this->attributes['username'] = $username; - $this->raise(new Events\UserWasRenamed($this)); - } - - public function setEmailAttribute($email) - { - $this->attributes['email'] = $email; - $this->raise(new Events\EmailWasChanged($this)); - } - - public function setPasswordAttribute($password) - { - $this->attributes['password'] = Hash::make($password); - $this->raise(new Events\PasswordWasChanged($this)); - } - - public static function register($username, $email, $password) - { - $user = new static; - - $user->username = $username; - $user->email = $email; - $user->password = $password; - $user->join_time = time(); - - $user->raise(new Events\UserWasRegistered($user)); - - return $user; - } - - public function getDates() - { - return ['join_time', 'last_seen_time']; - } - - public function getAvatarUrlAttribute() - { - return ''; - } - - public static function current() - { - static $current = null; - - if (Auth::guest()) { - if (! isset($current)) { - // $current = new Guest; - $current = User::find(1); - } - return $current; - } - - return Auth::user(); - } - - public function getGrantees() - { - $grantees = ['group.2']; // guests - if ($this->id) { - $grantees[] = 'user.'.$this->id; - } - foreach ($this->groups as $group) { - $grantees[] = 'group.'.$group->id; - } - - /* - TODO: maybe we should rethink how groups and permissions work a bit. - - Permissions table could be like: - GRANTEE ENTITY PERMISSION - all forum view - all discussion view - all post view - all user view - user discussion create - user discussion reply - group.1 forum administrate - group.1 post delete - etc - - sit on it. what about for suspended users? we could hook in and remove the 'user' grantee? - */ - - return $grantees; - } - - public function permission($permission, $user = null) - { - if (is_null($user)) { - $user = User::current(); - } - return $this->can($user, $permission); - } - - public function scopePermission($query, $permission, $user = null) - { - if (is_null($user)) { - $user = User::current(); - } - return $this->scopeWhereCan($query, $user, $permission); - } - - public function scopeWhereCanView($query, $user = null) - { - return $this->scopePermission($query, 'view', $user); - } - - public function assertCan($user, $permission) - { - if (! $this->can($user, $permission)) { - throw new PermissionDeniedException; - } - } - - // public function granted($permission, $scope) - // { - // return isset($this->permissions[$scope]) && in_array($permission, $this->permissions[$scope]); - // } - - // public function mustBeAbleTo($permission, $scope = 'forum', $entity = null) - // { - // if (! $this->can($permission, $scope, $entity)) { - // throw new PermissionDeniedException; - // } - // } - - public function admin() - { - return $this->can('administrate'); - } - - public function isAdmin() - { - return $this->groups->contains(1); - } - - public function guest() - { - return false; - } - - public function groups() - { - return $this->belongsToMany('Flarum\Core\Groups\Group', 'users_groups'); - } - - public function activity() - { - return $this->hasMany('Flarum\Core\Activity\Activity'); - } - - /** - * Get the unique identifier for the user. - * - * @return mixed - */ - public function getAuthIdentifier() - { - return $this->getKey(); - } - - /** - * Get the password for the user. - * - * @return string - */ - public function getAuthPassword() - { - return $this->password; - } - - /** - * Get the e-mail address where password reminders are sent. - * - * @return string - */ - public function getReminderEmail() - { - return $this->email; - } -} diff --git a/src/Flarum/Core/Users/UserFinder.php b/src/Flarum/Core/Users/UserFinder.php deleted file mode 100644 index 3672ea5..0000000 --- a/src/Flarum/Core/Users/UserFinder.php +++ /dev/null @@ -1,167 +0,0 @@ - ['username', 'asc'], - 'posts' => ['count_posts', 'desc'], - 'discussions' => ['count_discussions', 'desc'], - 'last_active' => ['last_action_time', 'desc'], - 'created' => ['join_time', 'asc'] - ]; - - protected $order; - - protected $key; - - protected $count; - - protected $areMoreResults; - - public function __construct($user = null, $tokens = null, $sort = null, $order = '', $key = null) - { - $this->user = $user; - $this->tokens = $tokens; - $this->sort = $sort; - $this->order = $order; - $this->key = $key; - } - - public function getUser() - { - return $this->user; - } - - public function setUser($user) - { - $this->user = $user; - } - - public function getTokens() - { - return $this->tokens; - } - - public function setTokens($tokens) - { - $this->tokens = $tokens; - } - - public function setQuery($query) - { - $tokenizer = new Tokenizer($query); - $this->setTokens($tokenizer->tokenize()); - } - - public function getSort() - { - return $this->sort; - } - - public function setSort($sort) - { - $this->sort = $sort; - } - - public function getOrder() - { - return $this->order; - } - - public function setOrder($order) - { - $this->order = $order; - } - - public function getKey() - { - return $this->key; - } - - public function setKey($key) - { - $this->key = $key; - } - - protected function getCacheKey() - { - return 'users.'.$this->key; - } - - public function getCount() - { - return $this->count; - } - - public function areMoreResults() - { - return $this->areMoreResults; - } - - public function results($count = null, $start = 0) - { - $ids = null; - $query = User::whereCan($this->user, 'view'); - - // not sure if we need any of this stuff - especially ID filters? - - // if ($this->key and Cache::has($key = $this->getCacheKey())) - // { - // $ids = Cache::get($key); - // } - // elseif (count($this->tokens)) - // { - // // parse the tokens. - // // run ID filters. - // /* - // for fulltext token: - // if ( ! $this->sort) $this->sort = 'relevance'; - // */ - // if ( ! is_null($ids)) - // { - // $this->key = str_random(); - // } - - // // run other tokens - // // $discussions->where(''); - // } - - // if ( ! is_null($ids)) - // { - // Cache::put($this->getCacheKey(), $ids, 10); // recache - // $this->count = count($ids); - - // if ( ! $ids) return false; - // $query->whereIn('id', $ids); - // } - - $this->count = (int) $query->count(); - - if (empty($this->sort)) { - reset($this->sortMap); - $this->sort = key($this->sortMap); - } - if (! empty($this->sortMap[$this->sort])) { - list($column, $order) = $this->sortMap[$this->sort]; - $query->orderBy($column, $this->order ?: $order); - } - - if ($start > 0) { - $query->skip($count); - } - if ($count > 0) { - $query->take($count); - } - return $query->get(); - } -} diff --git a/src/Flarum/Core/Users/UserRepository.php b/src/Flarum/Core/Users/UserRepository.php deleted file mode 100644 index 111fe9b..0000000 --- a/src/Flarum/Core/Users/UserRepository.php +++ /dev/null @@ -1,48 +0,0 @@ -whereCanView($user); - } - - return $query->findOrFail($id); - } - - public function save(User $user) - { - $user->assertValid(); - $user->save(); - } - - public function delete(User $user) - { - $user->delete(); - - // do something with their posts/discussions? - } - - public function attachGroup(User $user, $groupId) - { - $user->groups()->attach($groupId); - } - - public function detachGroup(User $user, $groupId) - { - $user->groups()->detach($groupId); - } - - public function syncGroups(User $user, $groupIds) - { - $user->groups()->sync($groupIds); - } -} diff --git a/src/Flarum/Core/Users/UsernameValidator.php b/src/Flarum/Core/Users/UsernameValidator.php deleted file mode 100644 index 3681dec..0000000 --- a/src/Flarum/Core/Users/UsernameValidator.php +++ /dev/null @@ -1,10 +0,0 @@ -files = $files; - $this->publishPath = $publishPath; - } - - protected function getPackageDir($package) - { - // TODO: First search vendor, then search workbench. - // TODO: inject path.base - return app('path.base').'/workbench/'.$package.'/dist/'; - } - - public function add($package, $files) - { - $packageDir = $this->getPackageDir($package); - - foreach ((array) $files as $file) - { - $ext = pathinfo($file, PATHINFO_EXTENSION); - switch ($ext) - { - case 'css': - $this->css[] = 'packages/'.$package.'/'.$file; - break; - - case 'js': - $this->js[] = 'packages/'.$package.'/'.$file; - break; - } - } - } - - public function getCSSFiles() - { - // TODO: in a production environment, we would concat+minify all the CSS files together - // (would probably need to check filemtimes etc.) - - // But in a development environment, we just copy all the css files to the public directory. - // foreach ($this->css as $file) - // { - - // } - - return $this->css; - } - - public function getJSFiles() - { - return $this->js; - } - - public function styles() - { - $output = ''; - - foreach ($this->getCSSFiles() as $file) - { - $output .= ''.PHP_EOL; - } - - return $output; - } - - public function scripts() - { - $output = ''; - - foreach ($this->getJSFiles() as $file) - { - $output .= ''.PHP_EOL; - } - - return $output; - } - -} diff --git a/src/Flarum/Web/WebServiceProvider.php b/src/Flarum/Web/WebServiceProvider.php deleted file mode 100644 index 6571073..0000000 --- a/src/Flarum/Web/WebServiceProvider.php +++ /dev/null @@ -1,67 +0,0 @@ -package('flarum/web', 'flarum.web'); - - - // Shouldn't do all this asset stuff in boot, because then it gets called on API requests - $assetManager = $this->app['flarum.web.assetManager']; - - $assetManager->add('flarum/core', [ - 'assets/vendor.css', - 'assets/flarum.css', - 'assets/vendor.js', - 'assets/flarum.js' - ]); - - // publish assets in dev environment - $publisher = new AssetPublisher($this->app['files'], $this->app['path.public']); - $publisher->setPackagePath(base_path().'/'.(strpos($this->guessPackagePath(), 'workbench') === false ? 'vendor' : 'workbench')); - $publisher->publishPackage('flarum/core'); - - include __DIR__.'/../../routes.php'; - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - $this->app['flarum.web.assetManager'] = $this->app->share(function($app) - { - return new AssetManager($app['files'], $app['path.public']); - }); - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { - return array(); - } - -} diff --git a/src/config/.gitkeep b/src/config/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/config/config.php b/src/config/config.php deleted file mode 100644 index d0b19a5..0000000 --- a/src/config/config.php +++ /dev/null @@ -1,13 +0,0 @@ - true, - 'title' => 'Flarum Prototype Forum', - - 'route_rules' => array( - // 'prefix' => 'blog', - // 'domain' => 'blog.site.com' - ) - -); diff --git a/src/lang/.gitkeep b/src/lang/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/lang/en/reminders.php b/src/lang/en/reminders.php deleted file mode 100644 index 4a9f176..0000000 --- a/src/lang/en/reminders.php +++ /dev/null @@ -1,22 +0,0 @@ - "Passwords must be six characters and match the confirmation.", - - "user" => "We can't find a user with that e-mail address.", - - "token" => "This password reset token is invalid.", - -); \ No newline at end of file diff --git a/src/lang/en/validation.php b/src/lang/en/validation.php deleted file mode 100644 index 85a62aa..0000000 --- a/src/lang/en/validation.php +++ /dev/null @@ -1,93 +0,0 @@ - "The :attribute must be accepted.", - "active_url" => "The :attribute is not a valid URL.", - "after" => "The :attribute must be a date after :date.", - "alpha" => "The :attribute may only contain letters.", - "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", - "alpha_num" => "The :attribute may only contain letters and numbers.", - "before" => "The :attribute must be a date before :date.", - "between" => array( - "numeric" => "The :attribute must be between :min - :max.", - "file" => "The :attribute must be between :min - :max kilobytes.", - "string" => "The :attribute must be between :min - :max characters.", - ), - "confirmed" => "The :attribute confirmation does not match.", - "date" => "The :attribute is not a valid date.", - "date_format" => "The :attribute does not match the format :format.", - "different" => "The :attribute and :other must be different.", - "digits" => "The :attribute must be :digits digits.", - "digits_between" => "The :attribute must be between :min and :max digits.", - "email" => "The :attribute format is invalid.", - "exists" => "The selected :attribute is invalid.", - "image" => "The :attribute must be an image.", - "in" => "The selected :attribute is invalid.", - "integer" => "The :attribute must be an integer.", - "ip" => "The :attribute must be a valid IP address.", - "max" => array( - "numeric" => "The :attribute may not be greater than :max.", - "file" => "The :attribute may not be greater than :max kilobytes.", - "string" => "The :attribute may not be greater than :max characters.", - ), - "mimes" => "The :attribute must be a file of type: :values.", - "min" => array( - "numeric" => "The :attribute must be at least :min.", - "file" => "The :attribute must be at least :min kilobytes.", - "string" => "The :attribute must be at least :min characters.", - ), - "not_in" => "The selected :attribute is invalid.", - "numeric" => "The :attribute must be a number.", - "regex" => "The :attribute format is invalid.", - "required" => "The :attribute field is required.", - "required_if" => "The :attribute field is required when :other is :value.", - "required_with" => "The :attribute field is required when :values is present.", - "required_without" => "The :attribute field is required when :values is not present.", - "same" => "The :attribute and :other must match.", - "size" => array( - "numeric" => "The :attribute must be :size.", - "file" => "The :attribute must be :size kilobytes.", - "string" => "The :attribute must be :size characters.", - ), - "unique" => "The :attribute has already been taken.", - "url" => "The :attribute format is invalid.", - - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => array(), - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => array(), - -); diff --git a/src/lang/zh_CN/reminders.php b/src/lang/zh_CN/reminders.php deleted file mode 100644 index 7ae63f4..0000000 --- a/src/lang/zh_CN/reminders.php +++ /dev/null @@ -1,17 +0,0 @@ - "密码必须是 6 个字符并且和确认密码一致。", - - "user" => "未找到与该 Email 对应的用户。", - - "token" => "此次重置密码的 token 无效。", - -); \ No newline at end of file diff --git a/src/lang/zh_CN/validation.php b/src/lang/zh_CN/validation.php deleted file mode 100644 index 1d82c1a..0000000 --- a/src/lang/zh_CN/validation.php +++ /dev/null @@ -1,88 +0,0 @@ - ":attribute 必须接受。", - "active_url" => ":attribute 不是有效的 URL。", - "after" => ":attribute 必须是 :date 之后的日期。", - "alpha" => ":attribute 只能包含字母。", - "alpha_dash" => ":attribute 只能包含字母、数字、中下划线、破折号。", - "alpha_num" => ":attribute 只能包含字母、数字。", - "before" => ":attribute 必须是 :date 之前的日期。", - "between" => array( - "numeric" => ":attribute 必须在 :min 和 :max 之间。", - "file" => ":attribute 必须在 :min KB 到 :max KB 之间。", - "string" => ":attribute 必须在 :min 到 :max 个字符之间。", - ), - "confirmed" => ":attribute 与重复输入不匹配。", - "date" => ":attribute 不是有效的日期。", - "date_format" => ":attribute 没有匹配规定的日期格式 :format", - "different" => ":attribute 与 :other 必须不相同。", - "digits" => ":attribute 必须是 :digits 位数字。", - "digits_between" => ":attribute 必须在 :min 到 :max 位数字之间。", - "email" => ":attribute 格式不正确。", - "exists" => "已经选择的 :attribute 不是有效的值。", - "image" => ":attribute 必须是一张图片。", - "in" => "已选的 :attribute 非法。", - "integer" => ":attribute 必须是一个整数。", - "ip" => ":attribute 必须是一个有效的IP地址。", - "max" => array( - "numeric" => ":attribute 必须不能大于 :max 。", - "file" => ":attribute 必须不能大于 :max KB。", - "string" => ":attribute 必须不能大于 :max 个字符。", - ), - "mimes" => ":attribute 必须是一个 :values 类型的文件。", - "min" => array( - "numeric" => ":attribute 必须不能小于 :min 。", - "file" => ":attribute 必须不能小于 :min KB。", - "string" => ":attribute 必须不能小于 :min 个字符。", - ), - "not_in" => "已选的 :attribute 非法。", - "numeric" => ":attribute 必须是一个数字。", - "regex" => ":attribute 格式不正确。", - "required" => ":attribute 不能为空。", - "required_if" => "当 :other 为 :value 时 :attribute 不能为空。", - "required_with" => "当 :values 存在时 :attribute 不能为空。", - "required_without" => "当 :values 不存在时 :attribute 不能为空。", - "same" => ":attribute 和 :other 必须匹配。", - "size" => array( - "numeric" => ":attribute 大小必须是 :size", - "file" => ":attribute 大小必须是 :size KB。", - "string" => ":attribute 必须是 :size 个字符。", - ), - "unique" => ":attribute 已经存在。", - "url" => ":attribute 不是一个有效的 URL。", - - /* - |-------------------------------------------------------------------------- - | 自定义验证规则 - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => array(), - - /* - |-------------------------------------------------------------------------- - | 自定义验证属性 - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => array(), - -); diff --git a/src/lang/zh_TW/reminders.php b/src/lang/zh_TW/reminders.php deleted file mode 100644 index 6733c55..0000000 --- a/src/lang/zh_TW/reminders.php +++ /dev/null @@ -1,17 +0,0 @@ - "密碼必須是 6 個字符並且和確認密碼壹致。", - - "user" => "未找到與該 Email 對應的用護。", - - "token" => "此次重置密碼的 token 無效。", - -); \ No newline at end of file diff --git a/src/lang/zh_TW/validation.php b/src/lang/zh_TW/validation.php deleted file mode 100644 index 017279f..0000000 --- a/src/lang/zh_TW/validation.php +++ /dev/null @@ -1,89 +0,0 @@ - ":attribute 必須接受。", - "active_url" => ":attribute 不是有效的 URL。", - "after" => ":attribute 必須是 :date 之後的日期。", - "alpha" => ":attribute 只能包含字母。", - "alpha_dash" => ":attribute 只能包含字母、數字、中下劃線、破折號。", - "alpha_num" => ":attribute 只能包含字母、數字。", - "before" => ":attribute 必須是 :date 之前的日期。", - "between" => array( - "numeric" => ":attribute 必須在 :min 和 :max 之間。", - "file" => ":attribute 必須在 :min KB 到 :max KB 之間。", - "string" => ":attribute 必須在 :min 到 :max 個字符之間。", - ), - "confirmed" => ":attribute 與重復輸入不匹配。", - "date" => ":attribute 不是有效的日期。", - "date_format" => ":attribute 沒有匹配規定的日期格式 :format", - "different" => ":attribute 與 :other 必須不相同。", - "digits" => ":attribute 必須是 :digits 位數字。", - "digits_between" => ":attribute 必須在 :min 到 :max 位數字之間。", - "email" => ":attribute 格式不正確。", - "exists" => "已經選擇的 :attribute 不是有效的值。", - "image" => ":attribute 必須是壹張圖片。", - "in" => "已選的 :attribute 非法。", - "integer" => ":attribute 必須是壹個整數。", - "ip" => ":attribute 必須是壹個有效的IP地址。", - "max" => array( - "numeric" => ":attribute 必須不能大於 :max 。", - "file" => ":attribute 必須不能大於 :max KB。", - "string" => ":attribute 必須不能大於 :max 個字符。", - ), - "mimes" => ":attribute 必須是壹個 :values 類型的文件。", - "min" => array( - "numeric" => ":attribute 必須不能小於 :min 。", - "file" => ":attribute 必須不能小於 :min KB。", - "string" => ":attribute 必須不能小於 :min 個字符。", - ), - "not_in" => "已選的 :attribute 非法。", - "numeric" => ":attribute 必須是壹個數字。", - "regex" => ":attribute 格式不正確。", - "required" => ":attribute 不能為空。", - "required_if" => "當 :other 為 :value 時 :attribute 不能為空。", - "required_with" => "當 :values 存在時 :attribute 不能為空。", - "required_without" => "當 :values 不存在時 :attribute 不能為空。", - "same" => ":attribute 和 :other 必須匹配。", - "size" => array( - "numeric" => ":attribute 大小必須是 :size", - "file" => ":attribute 大小必須是 :size KB。", - "string" => ":attribute 必須是 :size 個字符。", - ), - "unique" => ":attribute 已經存在。", - "url" => ":attribute 不是壹個有效的 URL。", - - /* - |-------------------------------------------------------------------------- - | 自定義驗證規則 - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - 'custom' => array(), - - /* - |-------------------------------------------------------------------------- - | 自定義驗證屬性 - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - - 'attributes' => array(), - -); diff --git a/src/migrations/.gitkeep b/src/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/migrations/2014_01_14_231259_create_config_table.php b/src/migrations/2014_01_14_231259_create_config_table.php deleted file mode 100644 index b74c9f1..0000000 --- a/src/migrations/2014_01_14_231259_create_config_table.php +++ /dev/null @@ -1,32 +0,0 @@ -string('key')->primary(); - $table->binary('value')->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('config'); - } - -} diff --git a/src/migrations/2014_01_14_231321_create_discussions_table.php b/src/migrations/2014_01_14_231321_create_discussions_table.php deleted file mode 100644 index e842e98..0000000 --- a/src/migrations/2014_01_14_231321_create_discussions_table.php +++ /dev/null @@ -1,45 +0,0 @@ -engine = 'MyISAM'; - - $table->increments('id'); - $table->string('title'); - $table->integer('posts_count')->unsigned()->default(0); - $table->integer('number_index')->unsigned()->default(0); - - $table->dateTime('start_time'); - $table->integer('start_user_id')->unsigned()->nullable(); - $table->integer('start_post_id')->unsigned()->nullable(); - - $table->dateTime('last_time')->nullable(); - $table->integer('last_user_id')->unsigned()->nullable(); - $table->integer('last_post_id')->unsigned()->nullable(); - $table->integer('last_post_number')->unsigned()->nullable(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('discussions'); - } - -} diff --git a/src/migrations/2014_01_14_231334_create_groups_table.php b/src/migrations/2014_01_14_231334_create_groups_table.php deleted file mode 100644 index 34e5b35..0000000 --- a/src/migrations/2014_01_14_231334_create_groups_table.php +++ /dev/null @@ -1,32 +0,0 @@ -increments('id'); - $table->string('name'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('groups'); - } - -} diff --git a/src/migrations/2014_01_14_231343_create_permissions_table.php b/src/migrations/2014_01_14_231343_create_permissions_table.php deleted file mode 100644 index 8bdef6a..0000000 --- a/src/migrations/2014_01_14_231343_create_permissions_table.php +++ /dev/null @@ -1,34 +0,0 @@ -string('grantee'); - $table->string('entity'); - $table->string('permission'); - $table->primary(['grantee', 'entity', 'permission']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('permissions'); - } - -} diff --git a/src/migrations/2014_01_14_231350_create_posts_table.php b/src/migrations/2014_01_14_231350_create_posts_table.php deleted file mode 100644 index 3e0ebbb..0000000 --- a/src/migrations/2014_01_14_231350_create_posts_table.php +++ /dev/null @@ -1,49 +0,0 @@ -engine = 'MyISAM'; - - $table->increments('id'); - $table->integer('discussion_id')->unsigned(); - $table->integer('number')->unsigned()->nullable(); - - $table->dateTime('time'); - $table->integer('user_id')->unsigned()->nullable(); - $table->string('type')->nullable(); - $table->text('content'); - $table->text('html_content'); - - $table->dateTime('edit_time')->nullable(); - $table->integer('edit_user_id')->unsigned()->nullable(); - $table->dateTime('delete_time')->nullable(); - $table->integer('delete_user_id')->unsigned()->nullable(); - }); - - // add fulltext index to content (and title?) - // add unique index on [discussion_id, number] !!! - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('posts'); - } - -} diff --git a/src/migrations/2014_01_14_231357_create_sessions_table.php b/src/migrations/2014_01_14_231357_create_sessions_table.php deleted file mode 100644 index e383784..0000000 --- a/src/migrations/2014_01_14_231357_create_sessions_table.php +++ /dev/null @@ -1,33 +0,0 @@ -string('id')->unique(); - $table->binary('payload'); - $table->integer('last_activity'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('sessions'); - } - -} diff --git a/src/migrations/2014_01_14_231404_create_users_table.php b/src/migrations/2014_01_14_231404_create_users_table.php deleted file mode 100644 index 8ce3cb2..0000000 --- a/src/migrations/2014_01_14_231404_create_users_table.php +++ /dev/null @@ -1,39 +0,0 @@ -increments('id'); - $table->string('username'); - $table->string('email'); - $table->string('password'); - $table->dateTime('join_time'); - $table->string('time_zone'); - $table->dateTime('last_seen_time')->nullable(); - $table->integer('discussions_count')->unsigned()->default(0); - $table->integer('posts_count')->unsigned()->default(0); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('users'); - } - -} diff --git a/src/migrations/2014_01_14_231455_create_users_discussions_table.php b/src/migrations/2014_01_14_231455_create_users_discussions_table.php deleted file mode 100644 index 6b0108a..0000000 --- a/src/migrations/2014_01_14_231455_create_users_discussions_table.php +++ /dev/null @@ -1,35 +0,0 @@ -integer('user_id')->unsigned(); - $table->integer('discussion_id')->unsigned(); - $table->dateTime('read_time')->nullable(); - $table->integer('read_number')->unsigned()->nullable(); - $table->primary(['user_id', 'discussion_id']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('users_discussions'); - } - -} diff --git a/src/migrations/2014_01_14_231503_create_users_groups_table.php b/src/migrations/2014_01_14_231503_create_users_groups_table.php deleted file mode 100644 index 386036c..0000000 --- a/src/migrations/2014_01_14_231503_create_users_groups_table.php +++ /dev/null @@ -1,33 +0,0 @@ -integer('user_id')->unsigned(); - $table->integer('group_id')->unsigned(); - $table->primary(['user_id', 'group_id']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('users_groups'); - } - -} diff --git a/src/migrations/2014_01_19_232631_create_activity_table.php b/src/migrations/2014_01_19_232631_create_activity_table.php deleted file mode 100644 index 43169a6..0000000 --- a/src/migrations/2014_01_19_232631_create_activity_table.php +++ /dev/null @@ -1,38 +0,0 @@ -increments('id'); - $table->integer('user_id')->unsigned(); - $table->integer('from_user_id')->unsigned()->nullable(); - $table->string('subject'); - $table->integer('subject_id')->unsigned()->nullable(); - $table->binary('data')->nullable(); - $table->dateTime('time'); - $table->boolean('is_read')->default(0); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('activity'); - } - -} diff --git a/src/routes.api.php b/src/routes.api.php deleted file mode 100644 index 5fbd5d8..0000000 --- a/src/routes.api.php +++ /dev/null @@ -1,178 +0,0 @@ -parameters(); - return $action->handle($request, $parameters); - }; -} - -Route::group(['prefix' => 'api'], function () { - - /* - |-------------------------------------------------------------------------- - | Users - |-------------------------------------------------------------------------- - */ - - // List users - Route::get('users', [ - 'as' => 'flarum.api.users.index', - 'uses' => action_handler('Flarum\Api\Actions\Users\Index') - ]); - - // Register a user - Route::post('users', [ - 'as' => 'flarum.api.users.create', - 'uses' => action_handler('Flarum\Api\Actions\Users\Create') - ]); - - // Get a single user - Route::get('users/{id}', [ - 'as' => 'flarum.api.users.show', - 'uses' => action_handler('Flarum\Api\Actions\Users\Show') - ]); - - // Edit a user - Route::put('users/{id}', [ - 'as' => 'flarum.api.users.update', - 'uses' => action_handler('Flarum\Api\Actions\Users\Update') - ]); - - // Delete a user - Route::delete('users/{id}', [ - 'as' => 'flarum.api.users.delete', - 'uses' => action_handler('Flarum\Api\Actions\Users\Delete') - ]); - - /* - |-------------------------------------------------------------------------- - | Activity - |-------------------------------------------------------------------------- - */ - - // List activity - Route::get('activity', [ - 'as' => 'flarum.api.activity.index', - 'uses' => action_handler('Flarum\Api\Actions\Activity\Index') - ]); - - // List notifications for the current user - Route::get('notifications', [ - 'as' => 'flarum.api.notifications.index', - 'uses' => action_handler('Flarum\Api\Actions\Notifications\Index') - ]); - - /* - |-------------------------------------------------------------------------- - | Discussions - |-------------------------------------------------------------------------- - */ - - // List discussions - Route::get('discussions', [ - 'as' => 'flarum.api.discussions.index', - 'uses' => action_handler('Flarum\Api\Actions\Discussions\Index') - ]); - - // Create a discussion - Route::post('discussions', [ - 'as' => 'flarum.api.discussions.create', - 'uses' => action_handler('Flarum\Api\Actions\Discussions\Create') - ]); - - // Show a single discussion - Route::get('discussions/{id}', [ - 'as' => 'flarum.api.discussions.show', - 'uses' => action_handler('Flarum\Api\Actions\Discussions\Show') - ]); - - // Edit a discussion - Route::put('discussions/{id}', [ - 'as' => 'flarum.api.discussions.update', - 'uses' => action_handler('Flarum\Api\Actions\Discussions\Update') - ]); - - // Delete a discussion - Route::delete('discussions/{id}', [ - 'as' => 'flarum.api.discussions.delete', - 'uses' => action_handler('Flarum\Api\Actions\Discussions\Delete') - ]); - - /* - |-------------------------------------------------------------------------- - | Posts - |-------------------------------------------------------------------------- - */ - - // List posts - Route::get('posts', [ - 'as' => 'flarum.api.posts.index', - 'uses' => action_handler('Flarum\Api\Actions\Posts\Index') - ]); - - // Create a post - // @todo consider 'discussions/{id}/links/posts'? - Route::post('posts', [ - 'as' => 'flarum.api.posts.create', - 'uses' => action_handler('Flarum\Api\Actions\Posts\Create') - ]); - - // Show a single post - Route::get('posts/{id}', [ - 'as' => 'flarum.api.posts.show', - 'uses' => action_handler('Flarum\Api\Actions\Posts\Show') - ]); - - // Edit a post - Route::put('posts/{id}', [ - 'as' => 'flarum.api.posts.update', - 'uses' => action_handler('Flarum\Api\Actions\Posts\Update') - ]); - - // Delete a post - Route::delete('posts/{id}', [ - 'as' => 'flarum.api.posts.delete', - 'uses' => action_handler('Flarum\Api\Actions\Posts\Delete') - ]); - - /* - |-------------------------------------------------------------------------- - | Groups - |-------------------------------------------------------------------------- - */ - - // List groups - Route::get('groups', [ - 'as' => 'flarum.api.groups.index', - 'uses' => action_handler('Flarum\Api\Actions\Groups\Index') - ]); - - // Create a group - Route::post('groups', [ - 'as' => 'flarum.api.groups.create', - 'uses' => action_handler('Flarum\Api\Actions\Groups\Create') - ]); - - // Show a single group - Route::get('groups/{id}', [ - 'as' => 'flarum.api.groups.show', - 'uses' => action_handler('Flarum\Api\Actions\Groups\Show') - ]); - - // Edit a group - Route::put('groups/{id}', [ - 'as' => 'flarum.api.groups.update', - 'uses' => action_handler('Flarum\Api\Actions\Groups\Update') - ]); - - // Delete a group - Route::delete('groups/{id}', [ - 'as' => 'flarum.api.groups.delete', - 'uses' => action_handler('Flarum\Api\Actions\Groups\Delete') - ]); - -}); diff --git a/src/routes.php b/src/routes.php deleted file mode 100644 index 427d8e0..0000000 --- a/src/routes.php +++ /dev/null @@ -1,7 +0,0 @@ -with('title', Config::get('flarum::forum_title', 'Flarum Support Forum')); -}); \ No newline at end of file diff --git a/src/views/.gitkeep b/src/views/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/views/index.blade.php b/src/views/index.blade.php deleted file mode 100644 index 6befa5b..0000000 --- a/src/views/index.blade.php +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - {{ $title }} - - - - - - - - - - {{ app('flarum.web.assetManager')->styles() }} - - - - {{ app('flarum.web.assetManager')->scripts() }} - - - diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php deleted file mode 100644 index 243f9c8..0000000 --- a/tests/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -scenario->runStep(new \Codeception\Step\Action('setHeader', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Authenticates user for HTTP_AUTH - * - * @param $username - * @param $password - * @see \Codeception\Module\PhpBrowser::amHttpAuthenticated() - */ - public function amHttpAuthenticated($username, $password) { - return $this->scenario->runStep(new \Codeception\Step\Condition('amHttpAuthenticated', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Opens the page for the given relative URI. - * - * ``` php - * amOnPage('/'); - * // opens /register page - * $I->amOnPage('/register'); - * ?> - * ``` - * - * @param $page - * @see \Codeception\Module\PhpBrowser::amOnPage() - */ - public function amOnPage($page) { - return $this->scenario->runStep(new \Codeception\Step\Condition('amOnPage', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Open web page at the given absolute URL and sets its hostname as the base host. - * - * ``` php - * amOnUrl('http://codeception.com'); - * $I->amOnPage('/quickstart'); // moves to http://codeception.com/quickstart - * ?> - * ``` - * @see \Codeception\Module\PhpBrowser::amOnUrl() - */ - public function amOnUrl($url) { - return $this->scenario->runStep(new \Codeception\Step\Condition('amOnUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Changes the subdomain for the 'url' configuration parameter. - * Does not open a page; use `amOnPage` for that. - * - * ``` php - * amOnSubdomain('user'); - * $I->amOnPage('/'); - * // moves to http://user.mysite.com/ - * ?> - * ``` - * - * @param $subdomain - * - * @return mixed - * @see \Codeception\Module\PhpBrowser::amOnSubdomain() - */ - public function amOnSubdomain($subdomain) { - return $this->scenario->runStep(new \Codeception\Step\Condition('amOnSubdomain', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Low-level API method. - * If Codeception commands are not enough, use [Guzzle HTTP Client](http://guzzlephp.org/) methods directly - * - * Example: - * - * ``` php - * executeInGuzzle(function (\GuzzleHttp\Client $client) { - * $client->get('/get', ['query' => ['foo' => 'bar']]); - * }); - * ?> - * ``` - * - * It is not recommended to use this command on a regular basis. - * If Codeception lacks important Guzzle Client methods, implement them and submit patches. - * - * @param callable $function - * @see \Codeception\Module\PhpBrowser::executeInGuzzle() - */ - public function executeInGuzzle($function) { - return $this->scenario->runStep(new \Codeception\Step\Action('executeInGuzzle', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Perform a click on a link or a button, given by a locator. - * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string. - * For buttons, the "value" attribute, "name" attribute, and inner text are searched. - * For links, the link text is searched. - * For images, the "alt" attribute and inner text of any parent links are searched. - * - * The second parameter is a context (CSS or XPath locator) to narrow the search. - * - * Note that if the locator matches a button of type `submit`, the form will be submitted. - * - * ``` php - * click('Logout'); - * // button of form - * $I->click('Submit'); - * // CSS button - * $I->click('#form input[type=submit]'); - * // XPath - * $I->click('//form/*[@type=submit]'); - * // link in context - * $I->click('Logout', '#nav'); - * // using strict locator - * $I->click(['link' => 'Login']); - * ?> - * ``` - * - * @param $link - * @param $context - * @see \Codeception\Lib\InnerBrowser::click() - */ - public function click($link, $context = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('click', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string. - * Specify a locator as the second parameter to match a specific region. - * - * ``` php - * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up','h1'); // I can suppose it's a signup page - * $I->see('Sign Up','//body/h1'); // with XPath - * ?> - * ``` - * - * @param $text - * @param null $selector - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::see() - */ - public function canSee($text, $selector = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('see', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string. - * Specify a locator as the second parameter to match a specific region. - * - * ``` php - * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up','h1'); // I can suppose it's a signup page - * $I->see('Sign Up','//body/h1'); // with XPath - * ?> - * ``` - * - * @param $text - * @param null $selector - * @see \Codeception\Lib\InnerBrowser::see() - */ - public function see($text, $selector = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('see', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page doesn't contain the text specified. - * Give a locator as the second parameter to match a specific region. - * - * ```php - * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath - * ?> - * ``` - * - * @param $text - * @param null $selector - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSee() - */ - public function cantSee($text, $selector = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSee', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page doesn't contain the text specified. - * Give a locator as the second parameter to match a specific region. - * - * ```php - * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath - * ?> - * ``` - * - * @param $text - * @param null $selector - * @see \Codeception\Lib\InnerBrowser::dontSee() - */ - public function dontSee($text, $selector = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSee', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there's a link with the specified text. - * Give a full URL as the second parameter to match links with that exact URL. - * - * ``` php - * seeLink('Logout'); // matches Logout - * $I->seeLink('Logout','/logout'); // matches Logout - * ?> - * ``` - * - * @param $text - * @param null $url - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeLink() - */ - public function canSeeLink($text, $url = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeLink', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there's a link with the specified text. - * Give a full URL as the second parameter to match links with that exact URL. - * - * ``` php - * seeLink('Logout'); // matches Logout - * $I->seeLink('Logout','/logout'); // matches Logout - * ?> - * ``` - * - * @param $text - * @param null $url - * @see \Codeception\Lib\InnerBrowser::seeLink() - */ - public function seeLink($text, $url = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeLink', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page doesn't contain a link with the given string. - * If the second parameter is given, only links with a matching "href" attribute will be checked. - * - * ``` php - * dontSeeLink('Logout'); // I suppose user is not logged in - * $I->dontSeeLink('Checkout now', '/store/cart.php'); - * ?> - * ``` - * - * @param $text - * @param null $url - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeLink() - */ - public function cantSeeLink($text, $url = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeLink', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page doesn't contain a link with the given string. - * If the second parameter is given, only links with a matching "href" attribute will be checked. - * - * ``` php - * dontSeeLink('Logout'); // I suppose user is not logged in - * $I->dontSeeLink('Checkout now', '/store/cart.php'); - * ?> - * ``` - * - * @param $text - * @param null $url - * @see \Codeception\Lib\InnerBrowser::dontSeeLink() - */ - public function dontSeeLink($text, $url = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeLink', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current URI contains the given string. - * - * ``` php - * seeInCurrentUrl('home'); - * // to match: /users/1 - * $I->seeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInCurrentUrl() - */ - public function canSeeInCurrentUrl($uri) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInCurrentUrl', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current URI contains the given string. - * - * ``` php - * seeInCurrentUrl('home'); - * // to match: /users/1 - * $I->seeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param $uri - * @see \Codeception\Lib\InnerBrowser::seeInCurrentUrl() - */ - public function seeInCurrentUrl($uri) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInCurrentUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URI doesn't contain the given string. - * - * ``` php - * dontSeeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInCurrentUrl() - */ - public function cantSeeInCurrentUrl($uri) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInCurrentUrl', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URI doesn't contain the given string. - * - * ``` php - * dontSeeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param $uri - * @see \Codeception\Lib\InnerBrowser::dontSeeInCurrentUrl() - */ - public function dontSeeInCurrentUrl($uri) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeInCurrentUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL is equal to the given string. - * Unlike `seeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * seeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlEquals() - */ - public function canSeeCurrentUrlEquals($uri) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlEquals', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL is equal to the given string. - * Unlike `seeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * seeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param $uri - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlEquals() - */ - public function seeCurrentUrlEquals($uri) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCurrentUrlEquals', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL doesn't equal the given string. - * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * dontSeeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlEquals() - */ - public function cantSeeCurrentUrlEquals($uri) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlEquals', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL doesn't equal the given string. - * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * dontSeeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param $uri - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlEquals() - */ - public function dontSeeCurrentUrlEquals($uri) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlEquals', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL matches the given regular expression. - * - * ``` php - * seeCurrentUrlMatches('~$/users/(\d+)~'); - * ?> - * ``` - * - * @param $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlMatches() - */ - public function canSeeCurrentUrlMatches($uri) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlMatches', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL matches the given regular expression. - * - * ``` php - * seeCurrentUrlMatches('~$/users/(\d+)~'); - * ?> - * ``` - * - * @param $uri - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlMatches() - */ - public function seeCurrentUrlMatches($uri) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCurrentUrlMatches', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current url doesn't match the given regular expression. - * - * ``` php - * dontSeeCurrentUrlMatches('~$/users/(\d+)~'); - * ?> - * ``` - * - * @param $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlMatches() - */ - public function cantSeeCurrentUrlMatches($uri) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlMatches', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current url doesn't match the given regular expression. - * - * ``` php - * dontSeeCurrentUrlMatches('~$/users/(\d+)~'); - * ?> - * ``` - * - * @param $uri - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlMatches() - */ - public function dontSeeCurrentUrlMatches($uri) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlMatches', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Executes the given regular expression against the current URI and returns the first match. - * If no parameters are provided, the full URI is returned. - * - * ``` php - * grabFromCurrentUrl('~$/user/(\d+)/~'); - * $uri = $I->grabFromCurrentUrl(); - * ?> - * ``` - * - * @param null $uri - * - * @internal param $url - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabFromCurrentUrl() - */ - public function grabFromCurrentUrl($uri = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('grabFromCurrentUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the specified checkbox is checked. - * - * ``` php - * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. - * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); - * ?> - * ``` - * - * @param $checkbox - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCheckboxIsChecked() - */ - public function canSeeCheckboxIsChecked($checkbox) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCheckboxIsChecked', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the specified checkbox is checked. - * - * ``` php - * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. - * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); - * ?> - * ``` - * - * @param $checkbox - * @see \Codeception\Lib\InnerBrowser::seeCheckboxIsChecked() - */ - public function seeCheckboxIsChecked($checkbox) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCheckboxIsChecked', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Check that the specified checkbox is unchecked. - * - * ``` php - * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. - * ?> - * ``` - * - * @param $checkbox - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCheckboxIsChecked() - */ - public function cantSeeCheckboxIsChecked($checkbox) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCheckboxIsChecked', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Check that the specified checkbox is unchecked. - * - * ``` php - * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. - * ?> - * ``` - * - * @param $checkbox - * @see \Codeception\Lib\InnerBrowser::dontSeeCheckboxIsChecked() - */ - public function dontSeeCheckboxIsChecked($checkbox) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCheckboxIsChecked', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given input field or textarea contains the given value. - * For fuzzy locators, fields are matched by label text, the "name" attribute, CSS, and XPath. - * - * ``` php - * seeInField('Body','Type your comment here'); - * $I->seeInField('form textarea[name=body]','Type your comment here'); - * $I->seeInField('form input[type=hidden]','hidden_value'); - * $I->seeInField('#searchform input','Search'); - * $I->seeInField('//form/*[@name=search]','Search'); - * $I->seeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInField() - */ - public function canSeeInField($field, $value) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInField', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given input field or textarea contains the given value. - * For fuzzy locators, fields are matched by label text, the "name" attribute, CSS, and XPath. - * - * ``` php - * seeInField('Body','Type your comment here'); - * $I->seeInField('form textarea[name=body]','Type your comment here'); - * $I->seeInField('form input[type=hidden]','hidden_value'); - * $I->seeInField('#searchform input','Search'); - * $I->seeInField('//form/*[@name=search]','Search'); - * $I->seeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * @see \Codeception\Lib\InnerBrowser::seeInField() - */ - public function seeInField($field, $value) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInField', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that an input field or textarea doesn't contain the given value. - * For fuzzy locators, the field is matched by label text, CSS and XPath. - * - * ``` php - * dontSeeInField('Body','Type your comment here'); - * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); - * $I->dontSeeInField('form input[type=hidden]','hidden_value'); - * $I->dontSeeInField('#searchform input','Search'); - * $I->dontSeeInField('//form/*[@name=search]','Search'); - * $I->dontSeeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInField() - */ - public function cantSeeInField($field, $value) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInField', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that an input field or textarea doesn't contain the given value. - * For fuzzy locators, the field is matched by label text, CSS and XPath. - * - * ``` php - * dontSeeInField('Body','Type your comment here'); - * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); - * $I->dontSeeInField('form input[type=hidden]','hidden_value'); - * $I->dontSeeInField('#searchform input','Search'); - * $I->dontSeeInField('//form/*[@name=search]','Search'); - * $I->dontSeeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * @see \Codeception\Lib\InnerBrowser::dontSeeInField() - */ - public function dontSeeInField($field, $value) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeInField', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Submits the given form on the page, optionally with the given form values. - * Give the form fields values as an array. - * - * Skipped fields will be filled by their values from the page. - * You don't need to click the 'Submit' button afterwards. - * This command itself triggers the request to form's action. - * - * You can optionally specify what button's value to include - * in the request with the last parameter as an alternative to - * explicitly setting its value in the second parameter, as - * button values are not otherwise included in the request. - * - * Examples: - * - * ``` php - * submitForm('#login', array('login' => 'davert', 'password' => '123456')); - * // or - * $I->submitForm('#login', array('login' => 'davert', 'password' => '123456'), 'submitButtonName'); - * - * ``` - * - * For example, given this sample "Sign Up" form: - * - * ``` html - *
    - * Login:
    - * Password:
    - * Do you agree to out terms?
    - * Select pricing plan - * - *
    - * ``` - * - * You could write the following to submit it: - * - * ``` php - * submitForm('#userForm', array('user' => array('login' => 'Davert', 'password' => '123456', 'agree' => true)), 'submitButton'); - * - * ``` - * Note that "2" will be the submitted value for the "plan" field, as it is the selected option. - * - * You can also emulate a JavaScript submission by not specifying any buttons in the third parameter to submitForm. - * - * ```php - * submitForm('#userForm', array('user' => array('login' => 'Davert', 'password' => '123456', 'agree' => true))); - * - * ``` - * - * @param $selector - * @param $params - * @param $button - * @see \Codeception\Lib\InnerBrowser::submitForm() - */ - public function submitForm($selector, $params, $button = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('submitForm', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Fills a text field or textarea with the given string. - * - * ``` php - * fillField("//input[@type='text']", "Hello World!"); - * $I->fillField(['name' => 'email'], 'jon@mail.com'); - * ?> - * ``` - * - * @param $field - * @param $value - * @see \Codeception\Lib\InnerBrowser::fillField() - */ - public function fillField($field, $value) { - return $this->scenario->runStep(new \Codeception\Step\Action('fillField', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Selects an option in a select tag or in radio button group. - * - * ``` php - * selectOption('form select[name=account]', 'Premium'); - * $I->selectOption('form input[name=payment]', 'Monthly'); - * $I->selectOption('//form/select[@name=account]', 'Monthly'); - * ?> - * ``` - * - * Provide an array for the second argument to select multiple options: - * - * ``` php - * selectOption('Which OS do you use?', array('Windows','Linux')); - * ?> - * ``` - * - * @param $select - * @param $option - * @see \Codeception\Lib\InnerBrowser::selectOption() - */ - public function selectOption($select, $option) { - return $this->scenario->runStep(new \Codeception\Step\Action('selectOption', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Ticks a checkbox. For radio buttons, use the `selectOption` method instead. - * - * ``` php - * checkOption('#agree'); - * ?> - * ``` - * - * @param $option - * @see \Codeception\Lib\InnerBrowser::checkOption() - */ - public function checkOption($option) { - return $this->scenario->runStep(new \Codeception\Step\Action('checkOption', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Unticks a checkbox. - * - * ``` php - * uncheckOption('#notify'); - * ?> - * ``` - * - * @param $option - * @see \Codeception\Lib\InnerBrowser::uncheckOption() - */ - public function uncheckOption($option) { - return $this->scenario->runStep(new \Codeception\Step\Action('uncheckOption', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Attaches a file relative to the Codeception data directory to the given file upload field. - * - * ``` php - * attachFile('input[@type="file"]', 'prices.xls'); - * ?> - * ``` - * - * @param $field - * @param $filename - * @see \Codeception\Lib\InnerBrowser::attachFile() - */ - public function attachFile($field, $filename) { - return $this->scenario->runStep(new \Codeception\Step\Action('attachFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * If your page triggers an ajax request, you can perform it manually. - * This action sends a GET ajax request with specified params. - * - * See ->sendAjaxPostRequest for examples. - * - * @param $uri - * @param $params - * @see \Codeception\Lib\InnerBrowser::sendAjaxGetRequest() - */ - public function sendAjaxGetRequest($uri, $params = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('sendAjaxGetRequest', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * If your page triggers an ajax request, you can perform it manually. - * This action sends a POST ajax request with specified params. - * Additional params can be passed as array. - * - * Example: - * - * Imagine that by clicking checkbox you trigger ajax request which updates user settings. - * We emulate that click by running this ajax request manually. - * - * ``` php - * sendAjaxPostRequest('/updateSettings', array('notifications' => true)); // POST - * $I->sendAjaxGetRequest('/updateSettings', array('notifications' => true)); // GET - * - * ``` - * - * @param $uri - * @param $params - * @see \Codeception\Lib\InnerBrowser::sendAjaxPostRequest() - */ - public function sendAjaxPostRequest($uri, $params = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('sendAjaxPostRequest', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * If your page triggers an ajax request, you can perform it manually. - * This action sends an ajax request with specified method and params. - * - * Example: - * - * You need to perform an ajax request specifying the HTTP method. - * - * ``` php - * sendAjaxRequest('PUT', '/posts/7', array('title' => 'new title')); - * - * ``` - * - * @param $method - * @param $uri - * @param $params - * @see \Codeception\Lib\InnerBrowser::sendAjaxRequest() - */ - public function sendAjaxRequest($method, $uri, $params = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('sendAjaxRequest', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Finds and returns the text contents of the given element. - * If a fuzzy locator is used, the element is found using CSS, XPath, and by matching the full page source by regular expression. - * - * ``` php - * grabTextFrom('h1'); - * $heading = $I->grabTextFrom('descendant-or-self::h1'); - * $value = $I->grabTextFrom('~ - * ``` - * - * @param $cssOrXPathOrRegex - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabTextFrom() - */ - public function grabTextFrom($cssOrXPathOrRegex) { - return $this->scenario->runStep(new \Codeception\Step\Action('grabTextFrom', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Grabs the value of the given attribute value from the given element. - * Fails if element is not found. - * - * ``` php - * grabAttributeFrom('#tooltip', 'title'); - * ?> - * ``` - * - * - * @param $cssOrXpath - * @param $attribute - * @internal param $element - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabAttributeFrom() - */ - public function grabAttributeFrom($cssOrXpath, $attribute) { - return $this->scenario->runStep(new \Codeception\Step\Action('grabAttributeFrom', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * @param $field - * - * @return array|mixed|null|string - * @see \Codeception\Lib\InnerBrowser::grabValueFrom() - */ - public function grabValueFrom($field) { - return $this->scenario->runStep(new \Codeception\Step\Action('grabValueFrom', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Sets a cookie with the given name and value. - * - * ``` php - * setCookie('PHPSESSID', 'el4ukv0kqbvoirg7nkp4dncpk3'); - * ?> - * ``` - * - * @param $cookie - * @param $value - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::setCookie() - */ - public function setCookie($name, $val) { - return $this->scenario->runStep(new \Codeception\Step\Action('setCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Grabs a cookie value. - * - * @param $cookie - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabCookie() - */ - public function grabCookie($name) { - return $this->scenario->runStep(new \Codeception\Step\Action('grabCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that a cookie with the given name is set. - * - * ``` php - * seeCookie('PHPSESSID'); - * ?> - * ``` - * - * @param $cookie - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCookie() - */ - public function canSeeCookie($name) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that a cookie with the given name is set. - * - * ``` php - * seeCookie('PHPSESSID'); - * ?> - * ``` - * - * @param $cookie - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeCookie() - */ - public function seeCookie($name) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there isn't a cookie with the given name. - * - * @param $cookie - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCookie() - */ - public function cantSeeCookie($name) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there isn't a cookie with the given name. - * - * @param $cookie - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::dontSeeCookie() - */ - public function dontSeeCookie($name) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Unsets cookie with the given name. - * - * @param $cookie - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::resetCookie() - */ - public function resetCookie($name) { - return $this->scenario->runStep(new \Codeception\Step\Action('resetCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element exists on the page and is visible. - * You can also specify expected attributes of this element. - * - * ``` php - * seeElement('.error'); - * $I->seeElement('//form/input[1]'); - * $I->seeElement('input', ['name' => 'login']); - * $I->seeElement('input', ['value' => '123456']); - * - * // strict locator in first arg, attributes in second - * $I->seeElement(['css' => 'form input'], ['name' => 'login']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * @return - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeElement() - */ - public function canSeeElement($selector, $attributes = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeElement', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element exists on the page and is visible. - * You can also specify expected attributes of this element. - * - * ``` php - * seeElement('.error'); - * $I->seeElement('//form/input[1]'); - * $I->seeElement('input', ['name' => 'login']); - * $I->seeElement('input', ['value' => '123456']); - * - * // strict locator in first arg, attributes in second - * $I->seeElement(['css' => 'form input'], ['name' => 'login']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * @return - * @see \Codeception\Lib\InnerBrowser::seeElement() - */ - public function seeElement($selector, $attributes = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeElement', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element is invisible or not present on the page. - * You can also specify expected attributes of this element. - * - * ``` php - * dontSeeElement('.error'); - * $I->dontSeeElement('//form/input[1]'); - * $I->dontSeeElement('input', ['name' => 'login']); - * $I->dontSeeElement('input', ['value' => '123456']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeElement() - */ - public function cantSeeElement($selector, $attributes = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeElement', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element is invisible or not present on the page. - * You can also specify expected attributes of this element. - * - * ``` php - * dontSeeElement('.error'); - * $I->dontSeeElement('//form/input[1]'); - * $I->dontSeeElement('input', ['name' => 'login']); - * $I->dontSeeElement('input', ['value' => '123456']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * @see \Codeception\Lib\InnerBrowser::dontSeeElement() - */ - public function dontSeeElement($selector, $attributes = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeElement', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there are a certain number of elements matched by the given locator on the page. - * - * ``` php - * seeNumberOfElements('tr', 10); - * $I->seeNumberOfElements('tr', [0,10]); //between 0 and 10 elements - * ?> - * ``` - * @param $selector - * @param mixed $expected: - * - string: strict number - * - array: range of numbers [0,10] - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeNumberOfElements() - */ - public function canSeeNumberOfElements($selector, $expected) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberOfElements', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there are a certain number of elements matched by the given locator on the page. - * - * ``` php - * seeNumberOfElements('tr', 10); - * $I->seeNumberOfElements('tr', [0,10]); //between 0 and 10 elements - * ?> - * ``` - * @param $selector - * @param mixed $expected: - * - string: strict number - * - array: range of numbers [0,10] - * @see \Codeception\Lib\InnerBrowser::seeNumberOfElements() - */ - public function seeNumberOfElements($selector, $expected) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeNumberOfElements', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is selected. - * - * ``` php - * seeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeOptionIsSelected() - */ - public function canSeeOptionIsSelected($select, $optionText) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeOptionIsSelected', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is selected. - * - * ``` php - * seeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeOptionIsSelected() - */ - public function seeOptionIsSelected($select, $optionText) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeOptionIsSelected', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is not selected. - * - * ``` php - * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeOptionIsSelected() - */ - public function cantSeeOptionIsSelected($select, $optionText) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeOptionIsSelected', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is not selected. - * - * ``` php - * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::dontSeeOptionIsSelected() - */ - public function dontSeeOptionIsSelected($select, $optionText) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeOptionIsSelected', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Asserts that current page has 404 response status code. - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seePageNotFound() - */ - public function canSeePageNotFound() { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seePageNotFound', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Asserts that current page has 404 response status code. - * @see \Codeception\Lib\InnerBrowser::seePageNotFound() - */ - public function seePageNotFound() { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seePageNotFound', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is equal to value provided. - * - * @param $code - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIs() - */ - public function canSeeResponseCodeIs($code) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIs', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is equal to value provided. - * - * @param $code - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIs() - */ - public function seeResponseCodeIs($code) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeResponseCodeIs', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title contains the given string. - * - * ``` php - * seeInTitle('Blog - Post #1'); - * ?> - * ``` - * - * @param $title - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInTitle() - */ - public function canSeeInTitle($title) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInTitle', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title contains the given string. - * - * ``` php - * seeInTitle('Blog - Post #1'); - * ?> - * ``` - * - * @param $title - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeInTitle() - */ - public function seeInTitle($title) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInTitle', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title does not contain the given string. - * - * @param $title - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInTitle() - */ - public function cantSeeInTitle($title) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInTitle', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title does not contain the given string. - * - * @param $title - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::dontSeeInTitle() - */ - public function dontSeeInTitle($title) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeInTitle', func_get_args())); - } -} diff --git a/tests/acceptance/_bootstrap.php b/tests/acceptance/_bootstrap.php deleted file mode 100644 index 8a88555..0000000 --- a/tests/acceptance/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -scenario->runStep(new \Codeception\Step\Condition('amInPath', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Opens a file and stores it's content. - * - * Usage: - * - * ``` php - * openFile('composer.json'); - * $I->seeInThisFile('codeception/codeception'); - * ?> - * ``` - * - * @param $filename - * @see \Codeception\Module\Filesystem::openFile() - */ - public function openFile($filename) { - return $this->scenario->runStep(new \Codeception\Step\Action('openFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Deletes a file - * - * ``` php - * deleteFile('composer.lock'); - * ?> - * ``` - * - * @param $filename - * @see \Codeception\Module\Filesystem::deleteFile() - */ - public function deleteFile($filename) { - return $this->scenario->runStep(new \Codeception\Step\Action('deleteFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Deletes directory with all subdirectories - * - * ``` php - * deleteDir('vendor'); - * ?> - * ``` - * - * @param $dirname - * @see \Codeception\Module\Filesystem::deleteDir() - */ - public function deleteDir($dirname) { - return $this->scenario->runStep(new \Codeception\Step\Action('deleteDir', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Copies directory with all contents - * - * ``` php - * copyDir('vendor','old_vendor'); - * ?> - * ``` - * - * @param $src - * @param $dst - * @see \Codeception\Module\Filesystem::copyDir() - */ - public function copyDir($src, $dst) { - return $this->scenario->runStep(new \Codeception\Step\Action('copyDir', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks If opened file has `text` in it. - * - * Usage: - * - * ``` php - * openFile('composer.json'); - * $I->seeInThisFile('codeception/codeception'); - * ?> - * ``` - * - * @param $text - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\Filesystem::seeInThisFile() - */ - public function canSeeInThisFile($text) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeInThisFile', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks If opened file has `text` in it. - * - * Usage: - * - * ``` php - * openFile('composer.json'); - * $I->seeInThisFile('codeception/codeception'); - * ?> - * ``` - * - * @param $text - * @see \Codeception\Module\Filesystem::seeInThisFile() - */ - public function seeInThisFile($text) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeInThisFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks the strict matching of file contents. - * Unlike `seeInThisFile` will fail if file has something more than expected lines. - * Better to use with HEREDOC strings. - * Matching is done after removing "\r" chars from file content. - * - * ``` php - * openFile('process.pid'); - * $I->seeFileContentsEqual('3192'); - * ?> - * ``` - * - * @param $text - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\Filesystem::seeFileContentsEqual() - */ - public function canSeeFileContentsEqual($text) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFileContentsEqual', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks the strict matching of file contents. - * Unlike `seeInThisFile` will fail if file has something more than expected lines. - * Better to use with HEREDOC strings. - * Matching is done after removing "\r" chars from file content. - * - * ``` php - * openFile('process.pid'); - * $I->seeFileContentsEqual('3192'); - * ?> - * ``` - * - * @param $text - * @see \Codeception\Module\Filesystem::seeFileContentsEqual() - */ - public function seeFileContentsEqual($text) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFileContentsEqual', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks If opened file doesn't contain `text` in it - * - * ``` php - * openFile('composer.json'); - * $I->dontSeeInThisFile('codeception/codeception'); - * ?> - * ``` - * - * @param $text - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\Filesystem::dontSeeInThisFile() - */ - public function cantSeeInThisFile($text) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInThisFile', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks If opened file doesn't contain `text` in it - * - * ``` php - * openFile('composer.json'); - * $I->dontSeeInThisFile('codeception/codeception'); - * ?> - * ``` - * - * @param $text - * @see \Codeception\Module\Filesystem::dontSeeInThisFile() - */ - public function dontSeeInThisFile($text) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeInThisFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Deletes a file - * @see \Codeception\Module\Filesystem::deleteThisFile() - */ - public function deleteThisFile() { - return $this->scenario->runStep(new \Codeception\Step\Action('deleteThisFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if file exists in path. - * Opens a file when it's exists - * - * ``` php - * seeFileFound('UserModel.php','app/models'); - * ?> - * ``` - * - * @param $filename - * @param string $path - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\Filesystem::seeFileFound() - */ - public function canSeeFileFound($filename, $path = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('seeFileFound', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if file exists in path. - * Opens a file when it's exists - * - * ``` php - * seeFileFound('UserModel.php','app/models'); - * ?> - * ``` - * - * @param $filename - * @param string $path - * @see \Codeception\Module\Filesystem::seeFileFound() - */ - public function seeFileFound($filename, $path = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('seeFileFound', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if file does not exists in path - * - * @param $filename - * @param string $path - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\Filesystem::dontSeeFileFound() - */ - public function cantSeeFileFound($filename, $path = null) { - return $this->scenario->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeFileFound', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if file does not exists in path - * - * @param $filename - * @param string $path - * @see \Codeception\Module\Filesystem::dontSeeFileFound() - */ - public function dontSeeFileFound($filename, $path = null) { - return $this->scenario->runStep(new \Codeception\Step\Assertion('dontSeeFileFound', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Erases directory contents - * - * ``` php - * cleanDir('logs'); - * ?> - * ``` - * - * @param $dirname - * @see \Codeception\Module\Filesystem::cleanDir() - */ - public function cleanDir($dirname) { - return $this->scenario->runStep(new \Codeception\Step\Action('cleanDir', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Saves contents to file - * - * @param $filename - * @param $contents - * @see \Codeception\Module\Filesystem::writeToFile() - */ - public function writeToFile($filename, $contents) { - return $this->scenario->runStep(new \Codeception\Step\Action('writeToFile', func_get_args())); - } -} diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php deleted file mode 100644 index 8a88555..0000000 --- a/tests/functional/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -scenario->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that two variables are not equal - * - * @param $expected - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertNotEquals() - */ - public function assertNotEquals($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that expected is greater than actual - * - * @param $expected - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertGreaterThan() - */ - public function assertGreaterThan($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThan', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * @deprecated - * @see \Codeception\Module\Asserts::assertGreaterThen() - */ - public function assertGreaterThen($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThen', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that expected is greater or equal than actual - * - * @param $expected - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertGreaterThanOrEqual() - */ - public function assertGreaterThanOrEqual($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThanOrEqual', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * @deprecated - * @see \Codeception\Module\Asserts::assertGreaterThenOrEqual() - */ - public function assertGreaterThenOrEqual($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertGreaterThenOrEqual', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that expected is less than actual - * - * @param $expected - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertLessThan() - */ - public function assertLessThan($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertLessThan', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that expected is less or equal than actual - * - * @param $expected - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertLessThanOrEqual() - */ - public function assertLessThanOrEqual($expected, $actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertLessThanOrEqual', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that haystack contains needle - * - * @param $needle - * @param $haystack - * @param string $message - * @see \Codeception\Module\Asserts::assertContains() - */ - public function assertContains($needle, $haystack, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertContains', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that haystack doesn't contain needle. - * - * @param $needle - * @param $haystack - * @param string $message - * @see \Codeception\Module\Asserts::assertNotContains() - */ - public function assertNotContains($needle, $haystack, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertNotContains', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that variable is empty. - * - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertEmpty() - */ - public function assertEmpty($actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertEmpty', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that variable is not empty. - * - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertNotEmpty() - */ - public function assertNotEmpty($actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertNotEmpty', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that variable is NULL - * - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertNull() - */ - public function assertNull($actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertNull', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that variable is not NULL - * - * @param $actual - * @param string $message - * @see \Codeception\Module\Asserts::assertNotNull() - */ - public function assertNotNull($actual, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertNotNull', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that condition is positive. - * - * @param $condition - * @param string $message - * @see \Codeception\Module\Asserts::assertTrue() - */ - public function assertTrue($condition, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertTrue', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that condition is negative. - * - * @param $condition - * @param string $message - * @see \Codeception\Module\Asserts::assertFalse() - */ - public function assertFalse($condition, $message = null) { - return $this->scenario->runStep(new \Codeception\Step\Action('assertFalse', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Fails the test with message. - * - * @param $message - * @see \Codeception\Module\Asserts::fail() - */ - public function fail($message) { - return $this->scenario->runStep(new \Codeception\Step\Action('fail', func_get_args())); - } -} diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php deleted file mode 100644 index 8a88555..0000000 --- a/tests/unit/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ - Date: Fri, 19 Jun 2015 09:50:53 +0800 Subject: [PATCH 17/27] add weixin group --- README.md | 4 ++++ images/weixin_group.png | Bin 0 -> 44873 bytes 2 files changed, 4 insertions(+) create mode 100644 images/weixin_group.png diff --git a/README.md b/README.md index 084ad2b..56e30c0 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ QQ 交流群:188723593 +微信群: + +![微信群二维码](images/weixin_group.png) + 中文开发者论坛:http://discuss.flarum.org.cn Live Demo http://demo.flarum.org diff --git a/images/weixin_group.png b/images/weixin_group.png new file mode 100644 index 0000000000000000000000000000000000000000..58e6403149c8384a1f1740af2ce290405303d114 GIT binary patch literal 44873 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 Date: Tue, 23 Jun 2015 14:28:00 +0800 Subject: [PATCH 18/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56e30c0..c6fb3c5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ QQ 交流群:188723593 -微信群: - -![微信群二维码](https://raw.githubusercontent.com/justjavac/flarum/master/images/weixin_group.png) - 中文开发者论坛:http://discuss.flarum.org.cn Live Demo http://discuss.flarum.org From cb0473bd89024c782217af488b61cef7113efb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Sat, 29 Aug 2015 12:17:55 +0800 Subject: [PATCH 22/27] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 593e705..6f311f5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ 中文语言包:https://github.com/justjavac/flarum-i18n-zh +中文文档:https://www.gitbook.com/book/justjavac/flarum/details + QQ 交流群:188723593 中文开发者论坛:http://discuss.flarum.org.cn From 5c1b6ee86f57f545d2ad4093967bcb8967a6e656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Sun, 30 Aug 2015 13:31:49 +0800 Subject: [PATCH 23/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f311f5..218c1ca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 源项目地址:https://github.com/flarum/flarum -中文语言包:https://github.com/justjavac/flarum-i18n-zh +中文语言包:https://github.com/Flarum-Chinese/Flarum-zh-CN 中文文档:https://www.gitbook.com/book/justjavac/flarum/details From 146579b2c1b51f8a5c341a6bdf3cda8087f54bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Wed, 2 Sep 2015 18:49:09 +0800 Subject: [PATCH 24/27] Update README.md --- README.md | 60 ++++++++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 218c1ca..260f6bd 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,39 @@ -源项目地址:https://github.com/flarum/flarum +![Flarum](http://flarum.org/img/logo.png) -中文语言包:https://github.com/Flarum-Chinese/Flarum-zh-CN +Flarum 是一款**自由**、**开源**的论坛软件,它专注于简单。通过 Flarum 您可以轻松地为您的网站搭建一个论坛。 -中文文档:https://www.gitbook.com/book/justjavac/flarum/details +[源码(英文)](https://github.com/flarum/flarum) - +[下载](http://flarum.org/download) - +[中文文档](https://github.com/justjavac/flarum-doc) - +[中文语言包](https://github.com/Flarum-Chinese/Flarum-zh-CN) - +[中文开发者社区](http://discuss.flarum.org.cn) - +QQ群 - +[演示站](http://demo.flarum.org.cn) - +[捐赠](http://flarum.org/donate) -QQ 交流群:188723593 +![截图](http://flarum.org/img/screenshot.png) -中文开发者论坛:http://discuss.flarum.org.cn +## 目标 -Live Demo http://discuss.flarum.org +Flarum 继承于 [esoTalk](http://esotalk.org) 和 [FluxBB](http://fluxbb.org)。 它的目标是: --------------------- +- **快速、简单** 没有混乱,没有膨胀,没有复杂的依赖关系。Flarum 使用 PHP 构建,因此它很容易部署。界面使用 Mithril,它是一个高性能 JavaScript 框架。 -我是 [Toby Zerner](http://tobyzerner.com), [esoTalk](http://esotalk.org) 的开发者。 -年前,我构建了一个新颖的,轻量级的 esoTalk 论坛的替代者。 -esoTalk 非常好,但是它[没有构建在一个可持续发展的基础上](http://bbs.justjavac.com/6)(译注:esoTalk 的底层框架重新发明了轮子)。 +- **漂亮、响应式** Flarum 由我们的设计师精心设计,它是跨平台的、开箱即用的。界面布局使用了 LESS,所以主题风格只是小事一桩。 -**基于此,我们需要一个现代的,优雅的,简洁的,强大的论坛软件,并且易于使用和托管。** -这就是 Flarum。但我是一个全职学生,我没有足够的时间独立完成这个项目。 -于是我将采用开放协作的方式来开发 Flarum —— 让全世界的开发者一起开发 Flarum。 +- **强大、可扩展** 为了满足您的社区需求,您可以定制、扩展和集成 Flarum。Flarum 的架构非常灵活,它拥有非常全面的 API 和文档。 -## 安装 - -**Flarum is currently in development and will be ready to use later this year.** ([Roadmap](http://tobyzerner.com/flarum/)) If you want to give the development version a spin or are interested in contributing, for now you can install Flarum's Vagrant image. An easier installation process will become a priority once Flarum is more stable. - -1. 安装 [Vagrant](https://www.vagrantup.com) 和 [VirtualBox](https://www.virtualbox.org). -2. Clone this repository and set up the Vagrant box: - - ```sh - git clone --recursive https://github.com/flarum/flarum.git - cd flarum - vagrant up - ``` +- **自由、开放** Flarum 基于 [MIT license](https://github.com/flarum/flarum/blob/master/LICENSE) 发布。 -3. Add an entry to your /etc/hosts file: - - ```192.168.29.29 flarum.dev``` - -4. Visit flarum.dev in a browser. +## 安装 -## Contributing +> **Flarum 目前处于测试阶段,因此不要将它用在生产怀旧中。** Flarum 的源码在 GitHub。查看开发进度的[路线图](http://flarum.org/roadmap)。 -Interested in contributing to Flarum? Read the [Contribution Guide](https://github.com/flarum/flarum/blob/master/CONTRIBUTING.md)! +想立即使用 Flarum,可以到[下载](http://flarum.org/download)页面。你需要一个安装了 **PHP 5.5+** 和 **MySQL 5.5+** 的服务器。 -Bug reports should go in [flarum/core](https://github.com/flarum/core/issues) or the [relevant extension repository](https://github.com/flarum). +如果你想深入研究 Flarum,你需要建立一个开发环境。这部分内容可以阅读文档的[贡献](http://justjavac.gitbooks.io/flarum/content/preface/contributing.html)章节! -### 核心团队 +## 核心团队 -- Toby Zerner ([esoTalk](http://esotalk.org), [@tobscure](http://twitter.com/tobscure)) -- Franz Liedke ([FluxBB](http://fluxbb.org), [@franzliedke](http://twitter.com/franzliedke)) +- Toby Zerner ([GitHub](http://github.com/tobscure), [Twitter](http://twitter.com/tobscure)) +- Franz Liedke ([GitHub](http://github.com/franzliedke), [Twitter](http://twitter.com/franzliedke)) From f58567fd468de573e2579485aa3fff41a3b856e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Wed, 2 Sep 2015 18:50:31 +0800 Subject: [PATCH 25/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 260f6bd..f239dcc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Flarum 是一款**自由**、**开源**的论坛软件,它专注于简单。通过 Flarum 您可以轻松地为您的网站搭建一个论坛。 -[源码(英文)](https://github.com/flarum/flarum) - +[源码](https://github.com/flarum/flarum) - [下载](http://flarum.org/download) - [中文文档](https://github.com/justjavac/flarum-doc) - [中文语言包](https://github.com/Flarum-Chinese/Flarum-zh-CN) - From c71e36d83fba4a49acc66e5e78e0dcbb24d9f0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Mon, 7 Sep 2015 13:56:26 +0800 Subject: [PATCH 26/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f239dcc..d2f83af 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Flarum 继承于 [esoTalk](http://esotalk.org) 和 [FluxBB](http://fluxbb.org) ## 安装 -> **Flarum 目前处于测试阶段,因此不要将它用在生产怀旧中。** Flarum 的源码在 GitHub。查看开发进度的[路线图](http://flarum.org/roadmap)。 +> **Flarum 目前处于测试阶段,因此不要将它用在生产环境中。** Flarum 的源码在 GitHub。查看开发进度的[路线图](Apply a className to various elements based on the active tags)。 想立即使用 Flarum,可以到[下载](http://flarum.org/download)页面。你需要一个安装了 **PHP 5.5+** 和 **MySQL 5.5+** 的服务器。 From 2cb582fe439290640f2a8834bb32f7d09d8a3702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=B7=E6=B8=A1?= Date: Mon, 7 Sep 2015 13:56:47 +0800 Subject: [PATCH 27/27] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E2=80=9C=E8=B7=AF?= =?UTF-8?q?=E7=BA=BF=E5=9B=BE=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2f83af..90fbc6a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Flarum 继承于 [esoTalk](http://esotalk.org) 和 [FluxBB](http://fluxbb.org) ## 安装 -> **Flarum 目前处于测试阶段,因此不要将它用在生产环境中。** Flarum 的源码在 GitHub。查看开发进度的[路线图](Apply a className to various elements based on the active tags)。 +> **Flarum 目前处于测试阶段,因此不要将它用在生产环境中。** Flarum 的源码在 GitHub。查看开发进度的[路线图](https://github.com/justjavac/flarum/issues/3)。 想立即使用 Flarum,可以到[下载](http://flarum.org/download)页面。你需要一个安装了 **PHP 5.5+** 和 **MySQL 5.5+** 的服务器。