├── resources
├── sass
│ └── tool.scss
├── css
│ └── app.css
├── js
│ ├── components
│ │ ├── icons
│ │ │ ├── IconSuccess.vue
│ │ │ └── IconFailed.vue
│ │ ├── NotificationsCard.vue
│ │ ├── NotificationModalFooter.vue
│ │ ├── NotificationParamForm.vue
│ │ ├── NotificationParamInput.vue
│ │ ├── NotificationsOverview.vue
│ │ ├── NotificationsTable.vue
│ │ ├── NotificationsSend.vue
│ │ └── NotificationsParamModal.vue
│ └── tool.js
├── lang
│ ├── en.json
│ ├── fr.json
│ └── it.json
└── views
│ └── navigation.blade.php
├── dist
├── css
│ └── tool.css
└── js
│ └── tool.js
├── .styleci.yml
├── images
├── screenshot_send.png
├── screenshot_overview.png
└── screenshot_parameters.png
├── mix-manifest.json
├── .gitignore
├── tests
├── Models
│ └── TestModel.php
├── 2018_09_04_000000_create_test_models_table.php
├── Controllers
│ ├── NotificationStatsControllerTest.php
│ ├── NotificationClassesControllerTest.php
│ ├── NotifiableControllerTest.php
│ └── NotificationControllerTest.php
├── ClassFinderTest.php
├── Notifications
│ └── TestNotification.php
└── TestCase.php
├── webpack.mix.js
├── src
├── NovaNotification.php
├── Http
│ ├── Middleware
│ │ └── Authorize.php
│ └── Controllers
│ │ ├── ApiController.php
│ │ ├── NotificationStatsController.php
│ │ ├── NotificationController.php
│ │ ├── NotifiableController.php
│ │ └── NotificationClassesController.php
├── NovaNotifications.php
├── ClassFinder.php
└── ToolServiceProvider.php
├── config
└── nova-notifications.php
├── .travis.yml
├── database
├── factories
│ ├── TestModelFactory.php
│ └── NovaNotificationFactory.php
└── migrations
│ └── 2018_09_04_000000_create_nova_notifications_table.php
├── phpunit.xml.dist
├── CHANGELOG.md
├── package.json
├── LICENSE.md
├── routes
└── api.php
├── composer.json
├── README.md
├── coverage.clover
└── tailwind.js
/resources/sass/tool.scss:
--------------------------------------------------------------------------------
1 | // Nova Tool CSS
2 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
1 | /*@tailwind components;*/
2 | /*@tailwind utilities;*/
--------------------------------------------------------------------------------
/dist/css/tool.css:
--------------------------------------------------------------------------------
1 | /*@tailwind components;*/
2 |
3 | /*@tailwind utilities;*/
4 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
2 |
3 | disabled:
4 | - single_class_element_per_statement
--------------------------------------------------------------------------------
/images/screenshot_send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christophrumpel/nova-notifications/HEAD/images/screenshot_send.png
--------------------------------------------------------------------------------
/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/dist/js/tool.js": "/dist/js/tool.js",
3 | "/dist/css/tool.css": "/dist/css/tool.css"
4 | }
--------------------------------------------------------------------------------
/images/screenshot_overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christophrumpel/nova-notifications/HEAD/images/screenshot_overview.png
--------------------------------------------------------------------------------
/images/screenshot_parameters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christophrumpel/nova-notifications/HEAD/images/screenshot_parameters.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /vendor
3 | /node_modules
4 | package-lock.json
5 | composer.phar
6 | composer.lock
7 | phpunit.xml
8 | .phpunit.result.cache
9 | .DS_Store
10 | Thumbs.db
11 |
--------------------------------------------------------------------------------
/tests/Models/TestModel.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/resources/js/components/icons/IconFailed.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/config/nova-notifications.php:
--------------------------------------------------------------------------------
1 | [
8 | 'App',
9 | ],
10 | /*
11 | * The namespaces you want to check for Notifiable classes.
12 | */
13 | 'notificationNamespaces' => [
14 | 'App\Notifications',
15 | 'Illuminate',
16 | ],
17 | ];
18 |
--------------------------------------------------------------------------------
/resources/js/tool.js:
--------------------------------------------------------------------------------
1 |
2 | Nova.booting((Vue, router) => {
3 | router.addRoutes([
4 | {
5 | name: 'nova-notifications',
6 | path: '/nova-notifications',
7 | component: require('./components/NotificationsOverview'),
8 | },
9 | {
10 | name: 'nova-notifications-send',
11 | path: '/nova-notifications-send',
12 | component: require('./components/NotificationsSend'),
13 | },
14 | ])
15 |
16 | })
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.2
5 | - 7.3
6 |
7 | env:
8 | matrix:
9 | - COMPOSER_FLAGS="--prefer-lowest"
10 | - COMPOSER_FLAGS=""
11 |
12 | before_script:
13 | - travis_retry composer self-update
14 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source
15 |
16 | script:
17 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
18 |
19 | after_script:
20 | - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover
--------------------------------------------------------------------------------
/src/Http/Middleware/Authorize.php:
--------------------------------------------------------------------------------
1 | authorize($request) ? $next($request) : abort(403);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/database/factories/TestModelFactory.php:
--------------------------------------------------------------------------------
1 | define(TestModel::class, function (Faker $faker) {
18 | return [
19 | ];
20 | });
21 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ApiController.php:
--------------------------------------------------------------------------------
1 | isSubclassOf('Illuminate\Database\Eloquent\Model');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationsCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ header }}
4 |
5 |
6 |
7 | {{ value }}
8 |
9 |
10 |
11 |
12 |
13 |
28 |
29 |
32 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 | src/
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/2018_09_04_000000_create_test_models_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::dropIfExists('nova_notifications');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Http/Controllers/NotificationStatsController.php:
--------------------------------------------------------------------------------
1 | $this->getNotificationsCount(),
13 | 'failed' => $this->getFailedNotificationsCount(),
14 | ];
15 | }
16 |
17 | private function getNotificationsCount()
18 | {
19 | return DB::table('nova_notifications')
20 | ->count();
21 | }
22 |
23 | private function getFailedNotificationsCount()
24 | {
25 | return DB::table('nova_notifications')
26 | ->where('failed', true)
27 | ->count();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/NovaNotifications.php:
--------------------------------------------------------------------------------
1 | define(NovaNotification::class, function (Faker $faker) {
18 | return [
19 | 'notification' => $faker->name,
20 | 'notifiable_type' => $faker->unique()->safeEmail,
21 | 'notifiable_id' => $faker->numberBetween(1, 100),
22 | 'channel' => collect(['twitter', 'email', 'Database'])->random(),
23 | 'failed' => false,
24 | ];
25 | });
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `nova-notifications` will be documented in this file.
4 |
5 | ## 1.0.6 - 09.10.2018
6 |
7 | * Remove custom styles to avoid Nova styles overriding
8 |
9 | ## 1.0.5
10 |
11 | * Fix bug where notifications were not shown if there was only one
12 | * Change how notifications and notifiables are found. Works not automatically. So the config is not necessary anymore.
13 |
14 | ## 1.0.4
15 |
16 | * Remove TailwindCSS preflight to prevent overriding styles in Nova itself
17 | * Make notificationClasses per default an array to prevent problem with checking length
18 |
19 | ## 1.0.3
20 |
21 | * Added translation files for EN and FR
22 | * Style CI fixes
23 |
24 | ## 1.0.2 - 2018-09-10
25 |
26 | - The path to the migration directory was wrong. This was fixed.
27 |
28 | ## 1.0.1 - 2018-09-10
29 |
30 | - The error message was not being shown on the send page when there was no notification class. This was fixed.
31 |
32 | ## 1.0.0 - 2018-09-10
33 |
34 | - Initial release
--------------------------------------------------------------------------------
/tests/Controllers/NotificationStatsControllerTest.php:
--------------------------------------------------------------------------------
1 | get('nova-vendor/nova-notifications/notifications/stats')
13 | ->assertSuccessful();
14 | }
15 |
16 | /** @test * */
17 | public function it_returns_stats_for_nova_notifications()
18 | {
19 | factory(NovaNotification::class)
20 | ->times(2)
21 | ->create();
22 |
23 | factory(NovaNotification::class)->create([
24 | 'failed' => true,
25 | ]);
26 |
27 | $this->get('nova-vendor/nova-notifications/notifications/stats')
28 | ->assertSuccessful()
29 | ->assertJson([
30 | 'all' => 3,
31 | 'failed' => 1,
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/ClassFinderTest.php:
--------------------------------------------------------------------------------
1 | app->setBasePath(__DIR__.'/..');
15 |
16 | $classFinder = app(ClassFinder::class);
17 |
18 | $response = $classFinder->find('Illuminate\Database\Eloquent\Model', ['App']);
19 |
20 | $this->assertInstanceOf('Illuminate\Support\Collection', $response);
21 | }
22 |
23 | /**
24 | * @test
25 | **/
26 | public function it_find_classes_which_are_extending_a_specific_class()
27 | {
28 | $this->app->setBasePath(__DIR__.'/..');
29 |
30 | $classFinder = app(ClassFinder::class);
31 |
32 | $response = $classFinder->findByExtending('Illuminate\Database\Eloquent\Model', ['App']);
33 |
34 | $this->assertInstanceOf('Illuminate\Support\Collection', $response);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/2018_09_04_000000_create_nova_notifications_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->string('notification');
19 | $table->string('notifiable_type');
20 | $table->string('notifiable_id');
21 | $table->string('channel');
22 | $table->boolean('failed')->default(false);
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('nova_notifications');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationModalFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
12 |
13 |
14 |
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "cross-env": "^5.0.0",
14 | "laravel-mix": "^1.0",
15 | "tailwindcss": "^0.6.0"
16 | },
17 | "dependencies": {
18 | "vue": "^2.5.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Christoph Rumpel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/resources/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Nova Notifications": "Nova Notifications",
3 | "Define Notification Parameters": "Define Notification Parameters",
4 | "Cancel": "Cancel",
5 | "Channel": "Channel",
6 | "Date": "Date",
7 | "Name": "Name",
8 | "Notifiable": "Notifiable",
9 | "Notifiables": "Notifiables",
10 | "Notifiable ID": "Notifiable ID",
11 | "Notifiable Item": "Notifiable Item",
12 | "Notifiable Type": "Notifiable Type",
13 | "Notification": "Notification",
14 | "Notification has not been chosen.": "Notification has not been chosen.",
15 | "Notification has been sent!": "Notification has been sent!",
16 | "Overview": "Overview",
17 | "Parameters": "Parameters",
18 | "Select": "Select",
19 | "Sent": "Sent",
20 | "Send": "Send",
21 | "Send Notification": "Send Notification",
22 | "Send Notification To": "Send Notification To",
23 | "The notification could not be created with the provided information": "The notification could not be created with the provided information",
24 | "There has been an error!": "There has been an error!",
25 | "You don't have any notification classes yet.": "You don't have any notification classes yet."
26 | }
--------------------------------------------------------------------------------
/resources/lang/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "Nova Notifications": "Notifications",
3 | "Define Notification Parameters": "Paramètres de la notification",
4 | "Cancel": "Annuler",
5 | "Channel": "Channel",
6 | "Date": "Date",
7 | "Name": "Nom",
8 | "Notifiable": "Destinataire",
9 | "Notifiables": "Destinataires",
10 | "Notifiable ID": "ID destinataire",
11 | "Notifiable Item": "Destinataire",
12 | "Notifiable Type": "Type destinataire",
13 | "Notification": "Notification",
14 | "Notification has not been chosen.": "Aucune notification choisie.",
15 | "Notification has been sent!": "La notification a été envoyée !",
16 | "Overview": "Vue d'ensemble",
17 | "Parameters": "Paramètres",
18 | "Select": "Selectionner",
19 | "Sent": "Envoyé",
20 | "Send": "Envoyer",
21 | "Send Notification": "Envoyer la notification",
22 | "Send Notification To": "Destinataire",
23 | "The notification could not be created with the provided information": "La notification ne peut être créée avec le paramètres fournis",
24 | "There has been an error!": "Il y'a eu une erreur !",
25 | "You don't have any notification classes yet.": "Aucune notification n'est disponible."
26 | }
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | =7.2.0",
12 | "laravel/nova": "*"
13 | },
14 | "require-dev": {
15 | "orchestra/testbench": "~3.0",
16 | "mockery/mockery": "^1.0",
17 | "phpunit/phpunit": "8.0"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "Christophrumpel\\NovaNotifications\\": "src/"
22 | }
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "Christophrumpel\\NovaNotifications\\Tests\\": "tests/"
27 | }
28 | },
29 | "extra": {
30 | "laravel": {
31 | "providers": [
32 | "Christophrumpel\\NovaNotifications\\ToolServiceProvider"
33 | ]
34 | }
35 | },
36 | "repositories": [
37 | {
38 | "type": "composer",
39 | "url": "https://nova.laravel.com"
40 | }
41 | ],
42 | "config": {
43 | "sort-packages": true
44 | },
45 | "minimum-stability": "dev",
46 | "prefer-stable": true
47 | }
48 |
--------------------------------------------------------------------------------
/resources/views/navigation.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 | -
13 | {{__('Overview')}}
14 |
15 | -
16 |
17 | {{__('Send')}}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/Notifications/TestNotification.php:
--------------------------------------------------------------------------------
1 | testParam = $testParam;
26 | }
27 |
28 | /**
29 | * Get the notification's delivery channels.
30 | *
31 | * @param mixed $notifiable
32 | * @return array
33 | */
34 | public function via($notifiable)
35 | {
36 | return ['mail'];
37 | }
38 |
39 | /**
40 | * Get the mail representation of the notification.
41 | *
42 | * @param mixed $notifiable
43 | * @return \Illuminate\Notifications\Messages\MailMessage
44 | */
45 | public function toMail($notifiable)
46 | {
47 | return (new MailMessage)
48 | ->line('The introduction to the notification.')
49 | ->action('Notification Action', url('/'))
50 | ->line('Thank you for using our application!');
51 | }
52 |
53 | /**
54 | * Get the array representation of the notification.
55 | *
56 | * @param mixed $notifiable
57 | * @return array
58 | */
59 | public function toArray($notifiable)
60 | {
61 | return [
62 | //
63 | ];
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Http/Controllers/NotificationController.php:
--------------------------------------------------------------------------------
1 | get();
15 | }
16 |
17 | /**
18 | * @param Request $request
19 | * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
20 | */
21 | public function send(Request $request)
22 | {
23 | $notificationClass = $request->get('notification')['name'];
24 | $params = collect($request->get('notificationParameters'))->map(function ($param) {
25 | if ($this->isEloquentModelClass($param['type'])) {
26 | return $param['type']::findOrFail($param['value']);
27 | }
28 |
29 | return $param['value'];
30 | });
31 |
32 | if (! class_exists($notificationClass)) {
33 | return response('', 400);
34 | }
35 |
36 | try {
37 | $notification = $params ? new $notificationClass(...$params) : new $notificationClass();
38 | } catch (\Throwable $e) {
39 | return response(__('The notification could not be created with the provided information'), 422);
40 | }
41 |
42 | $notifiable = str_replace('.', '\\', $request->get('notifiable')['name']);
43 |
44 | Notification::send($notifiable::findOrFail($request->get('notifiable')['value']), $notification);
45 |
46 | $this->respondSuccess();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Controllers/NotificationClassesControllerTest.php:
--------------------------------------------------------------------------------
1 | disableExceptionHandling();
19 |
20 | $this->testNotificationClassName = 'Christophrumpel\NovaNotifications\Tests\Notifications\TestNotification';
21 |
22 | $this->classFinder = Mockery::mock(ClassFinder::class);
23 | $this->app->instance(ClassFinder::class, $this->classFinder);
24 | }
25 |
26 | /**
27 | * @test
28 | **/
29 | public function it_returns_given_notification_classes()
30 | {
31 | $this->classFinder->shouldReceive('findByExtending')
32 | ->withArgs(['Illuminate\Notifications\Notification', config('nova-notifications.notificationNamespaces')])
33 | ->andReturn(collect([$this->testNotificationClassName]));
34 |
35 | $this->get('nova-vendor/nova-notifications/notifications/classes')
36 | ->assertSuccessful()
37 | ->assertJson([
38 | [
39 | 'name' => $this->testNotificationClassName,
40 | 'parameters' => [
41 | [
42 | 'name' => 'testParam',
43 | 'type' => 'int',
44 | 'options' => '',
45 | ],
46 | ],
47 | ],
48 | ]);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Http/Controllers/NotifiableController.php:
--------------------------------------------------------------------------------
1 | classFinder = $classFinder;
23 | }
24 |
25 | public function index()
26 | {
27 | $modelClasses = $this->classFinder->findByExtending('Illuminate\Database\Eloquent\Model', config('nova-notifications.notifiableNamespaces'))
28 | ->filter(function ($className) {
29 | $classInfo = new ReflectionClass($className);
30 |
31 | return in_array('Illuminate\Notifications\Notifiable', $classInfo->getTraitNames());
32 | })
33 | ->map(function ($className) {
34 | return [
35 | 'name' => str_replace('\\', '.', $className),
36 | 'options' => $className::all(),
37 | ];
38 | });
39 |
40 | $options = $modelClasses->map(function ($notifiable) {
41 | return [
42 | 'name' => $notifiable['name'],
43 | ];
44 | })
45 | ->toArray();
46 |
47 | return [
48 | 'data' => $modelClasses->values(),
49 | 'filter' => [
50 | 'name' => __('Notifiables'),
51 | 'options' => $options,
52 | ],
53 | ];
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationParamForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{__('Send Notification To')}}
5 |
6 |
14 |
15 |
16 |
20 |
21 |
22 | Define Notification Parameters
23 |
24 |
32 |
33 |
34 |
35 |
36 |
37 |
54 |
55 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationParamInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
18 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
44 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationsOverview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{__('Nova Notifications')}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
45 |
46 |
49 |
--------------------------------------------------------------------------------
/src/ClassFinder.php:
--------------------------------------------------------------------------------
1 | getAppNamespace();
17 | }
18 |
19 | /**
20 | * @param string $nameSpace
21 | * @return Collection
22 | */
23 | public function find(string $nameSpace): Collection
24 | {
25 | $composer = require base_path('vendor/autoload.php');
26 |
27 | return collect($composer->getClassMap())->filter(function ($value, $key) use ($nameSpace) {
28 | return Str::startsWith($key, $nameSpace);
29 | });
30 | }
31 |
32 | /**
33 | * Find classes which are extending a specific class.
34 | *
35 | * @param string $classNameToFind
36 | * @param array $namespacesToSearch
37 | * @return Collection
38 | */
39 | public function findByExtending(string $classNameToFind, array $namespacesToSearch): Collection
40 | {
41 | $composer = require base_path('vendor/autoload.php');
42 |
43 | return collect($composer->getClassMap())
44 | ->keys()
45 | ->filter(function ($className) {
46 | return $className !== 'Illuminate\Filesystem\Cache';
47 | })
48 | ->filter(function ($className) use ($namespacesToSearch) {
49 | return collect($namespacesToSearch)
50 | ->filter(function ($namespace) use ($className) {
51 | return Str::startsWith($className, $namespace);
52 | })
53 | ->count();
54 | })
55 | ->filter(function ($className) use ($classNameToFind) {
56 | try {
57 | $classInfo = new ReflectionClass($className);
58 | } catch (\Exception $e) {
59 | return false;
60 | }
61 |
62 | return $classInfo->isSubclassOf($classNameToFind);
63 | });
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Controllers/NotifiableControllerTest.php:
--------------------------------------------------------------------------------
1 | app->instance(ClassFinder::class, $classFinder);
17 |
18 | $classFinder->shouldReceive('findByExtending')
19 | ->withArgs(['Illuminate\Database\Eloquent\Model', ['App']])
20 | ->andReturn(collect([]));
21 |
22 | $response = $this->get('nova-vendor/nova-notifications/notifiables')
23 | ->assertSuccessful();
24 |
25 | $output = json_decode($response->getContent());
26 | $this->assertEmpty($output->data);
27 | }
28 |
29 | /**
30 | * @test
31 | **/
32 | public function it_returns_given_notifiable()
33 | {
34 | $testModelClassName = 'Christophrumpel\NovaNotifications\Tests\Models\TestModel';
35 | $testModelClassNameDots = str_replace('\\', '.', $testModelClassName);
36 | $classFinder = Mockery::mock(ClassFinder::class);
37 | $this->app->instance(ClassFinder::class, $classFinder);
38 |
39 | $classFinder->shouldReceive('findByExtending')
40 | ->withArgs(['Illuminate\Database\Eloquent\Model', ['App']])
41 | ->andReturn(collect([$testModelClassName]));
42 |
43 | $this->get('nova-vendor/nova-notifications/notifiables')
44 | ->assertSuccessful()
45 | ->assertJson([
46 | 'data' => [
47 | [
48 | 'name' => $testModelClassNameDots,
49 | 'options' => [],
50 | ],
51 | ],
52 | 'filter' => [
53 | 'name' => 'Notifiables',
54 | 'options' => [
55 | [
56 | 'name' => $testModelClassNameDots,
57 | ],
58 | ],
59 | ],
60 | ]);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | loadMigrationsFrom(__DIR__.'/../database/migrations');
19 | $this->loadMigrationsFrom(__DIR__.'/../tests/');
20 | $this->withFactories(__DIR__.'/../database/factories');
21 | }
22 |
23 | protected function getPackageProviders($app)
24 | {
25 | return [
26 | ToolServiceProvider::class,
27 | ];
28 | }
29 |
30 | /**
31 | * Define environment setup.
32 | *
33 | * @param \Illuminate\Foundation\Application $app
34 | * @return void
35 | */
36 | protected function getEnvironmentSetUp($app)
37 | {
38 | // Setup default database to use sqlite :memory:
39 | $app['config']->set('database.default', 'testbench');
40 | $app['config']->set('database.connections.testbench', [
41 | 'driver' => 'sqlite',
42 | 'database' => ':memory:',
43 | 'prefix' => '',
44 | ]);
45 |
46 | $app['config']->set('nova-notifications.modelNamespace', 'Christophrumpel\NovaNotifications\Tests\Models');
47 |
48 | $app['config']->set('nova-notifications.notificationNamespace',
49 | 'Christophrumpel\NovaNotifications\Tests\Notifications');
50 | }
51 |
52 | // Framework-supplied test case methods snipped for brevity
53 | // Use this version if you're on PHP 7
54 | protected function disableExceptionHandling()
55 | {
56 | $this->app->instance(ExceptionHandler::class, new class extends Handler {
57 | public function __construct()
58 | {
59 | }
60 |
61 | public function report(Exception $e)
62 | {
63 | // no-op
64 | }
65 |
66 | public function render($request, Exception $e)
67 | {
68 | throw $e;
69 | }
70 | });
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationsTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | |
8 | ID
9 | |
10 |
11 | {{__('Notification')}}
12 | |
13 |
14 | {{__('Notifiable Type')}}
15 | |
16 |
17 | {{__('Notifiable ID')}}
18 | |
19 |
20 | {{__('Channel')}}
21 | |
22 |
23 | {{__('Sent')}}
24 | |
25 |
26 | {{__('Date')}}
27 | |
28 |
29 |
30 |
31 |
32 | | {{notification.id}} |
33 | {{notification.notification}} |
34 | {{notification.notifiable_type}} |
35 | {{notification.notifiable_id}} |
36 | {{notification.channel}} |
37 |
38 |
39 | |
40 | {{notification.created_at}} |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/src/Http/Controllers/NotificationClassesController.php:
--------------------------------------------------------------------------------
1 | classFinder = $classFinder;
26 | }
27 |
28 | public function index()
29 | {
30 | return $this->classFinder->findByExtending('Illuminate\Notifications\Notification', config('nova-notifications.notificationNamespaces'))
31 | ->map(function ($className) {
32 | try {
33 | $classInfo = new ReflectionMethod($className, '__construct');
34 | } catch (\ReflectionException $e) {
35 | return [
36 | 'name' => $className,
37 | 'parameters' => [],
38 | ];
39 | }
40 |
41 | $notificationClassInfo = new stdClass();
42 | $notificationClassInfo->name = $classInfo->class;
43 |
44 | $params = collect($classInfo->getParameters())->map(function (ReflectionParameter $param) {
45 | $paramTypeName = is_null($param->getType()) ? 'unknown' : $param->getType()
46 | ->getName();
47 |
48 | if (class_exists($paramTypeName)) {
49 | $class = new ReflectionClass($paramTypeName);
50 | $fullyClassName = $class->getName();
51 |
52 | if ($this->isEloquentModelClass($fullyClassName)) {
53 | $options = collect($fullyClassName::all())->map(function ($item) {
54 | return [
55 | 'id' => $item->id,
56 | 'name' => $item->name ?? $item->id,
57 | ];
58 | });
59 | }
60 | }
61 |
62 | return [
63 | 'name' => $param->getName(),
64 | 'type' => $paramTypeName,
65 | 'options' => $options ?? '',
66 | ];
67 | });
68 |
69 | $notificationClassInfo->parameters = $params;
70 |
71 | return $notificationClassInfo;
72 | })->values();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ToolServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadViewsFrom(__DIR__.'/../resources/views', 'nova-notifications');
20 |
21 | $this->publishes([
22 | __DIR__.'/../config/nova-notifications.php' => config_path('nova-notifications.php'),
23 | ], 'config');
24 |
25 | $this->publishes([
26 | __DIR__.'/../resources/lang' => resource_path('lang/vendor/nova-notifications'),
27 | ], 'nova-notifications-lang');
28 |
29 | $this->loadJsonTranslationsFrom(resource_path('lang/vendor/nova-notifications'));
30 |
31 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
32 |
33 | $this->app->booted(function () {
34 | $this->routes();
35 | });
36 |
37 | Event::listen('Illuminate\Notifications\Events\NotificationSent', function ($event) {
38 | NovaNotification::create([
39 | 'notification' => get_class($event->notification),
40 | 'notifiable_type' => get_class($event->notifiable),
41 | 'notifiable_id' => $event->notifiable->id ?? '?',
42 | 'channel' => $event->channel,
43 | 'failed' => false,
44 | ]);
45 | });
46 |
47 | Event::listen('Illuminate\Notifications\Events\NotificationFailed', function ($event) {
48 | NovaNotification::create([
49 | 'notification' => get_class($event->notification),
50 | 'notifiable_type' => get_class($event->notifiable),
51 | 'notifiable_id' => $event->notifiable->id ?? '',
52 | 'channel' => $event->channel,
53 | 'failed' => true,
54 | ]);
55 | });
56 | }
57 |
58 | /**
59 | * Register the tool's routes.
60 | *
61 | * @return void
62 | */
63 | protected function routes()
64 | {
65 | if ($this->app->routesAreCached()) {
66 | return;
67 | }
68 |
69 | Route::middleware(['nova', Authorize::class])
70 | ->prefix('nova-vendor/nova-notifications')
71 | ->group(__DIR__.'/../routes/api.php');
72 | }
73 |
74 | /**
75 | * Register any application services.
76 | *
77 | * @return void
78 | */
79 | public function register()
80 | {
81 | $this->mergeConfigFrom(__DIR__.'/../config/nova-notifications.php', 'nova-notifications');
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > :warning: **Note**: This repo is not maintained right now.
2 |
3 | # A Laravel Nova Tool for Handling Laravel Notifications
4 |
5 | With this [Nova](https://nova.laravel.com) tool:
6 | - You can overview all sent and failed notifications.
7 | - You can send out notifications. (depening on the notification's dependencies)
8 | - All sent or failed notifications will be stored in the database.
9 |
10 | [](https://packagist.org/packages/christophrumpel/nova-notifications)
11 |
12 | 
13 | 
14 |
15 | ## Requirements
16 |
17 | In order to use this package, you need to have an installation of Laravel with Laravel nova setup.
18 |
19 | ## Installation
20 |
21 | First install the [Nova](https://nova.laravel.com) package via composer:
22 |
23 | ```bash
24 | composer require christophrumpel/nova-notifications
25 | ```
26 | Then publish the config file(optional):
27 | ``` bash
28 | php artisan vendor:publish --provider="Christophrumpel\NovaNotifications\ToolServiceProvider"
29 | ```
30 | In there, you can define where to look for the Notification classes, as well as the notifiable models.
31 | ```php
32 | [
38 | 'App',
39 | ],
40 | /*
41 | * The namespaces you want to check for Notifiable classes.
42 | */
43 | 'notificationNamespaces' => [
44 | 'App\Notifications',
45 | 'Illuminate',
46 | ],
47 | ];
48 | ```
49 |
50 |
51 | Next up, you must register the tool via the `tools` method of the `NovaServiceProvider`.
52 |
53 | ```php
54 | // inside app/Providers/NovaServiceProvder.php
55 |
56 | // ...
57 |
58 | public function tools()
59 | {
60 | return [
61 | // ...
62 | new \Christophrumpel\NovaNotifications\NovaNotifications()
63 | ];
64 | }
65 | ```
66 |
67 | You also need to run `php artisan migrate` on your Laravel application so that the new notifications table will be created.
68 |
69 | # Usage
70 |
71 | After installing the tool, you should see the new sidebar navigation item for `Notifications`.
72 |
73 | ## Overview
74 |
75 | On the `Overview` page you can see all of the sent and failed notifications. This only works for notifications sent after installing this package.
76 |
77 | Usually, only the notifications sent through the `database` channel will be stored in the database. This package comes with a new `nova_notifications` table, where `all` of them get stored.
78 |
79 | ## Send
80 |
81 | On the `Send` page you can see all of your created notification classes.
82 |
83 | If you don't see a newly created notification class, try running `composer dump-autoload`.
84 |
85 | ### Parameters
86 |
87 | Since you notifications often depend on parameters, this package tries to help you with that. All found constructor parameters will be shown when you try to send a notification. If one of the dependencies is an Eloquent Model, you will get a list with all of the items to choose from.
88 |
89 | 
90 |
91 | If you want to create a new notification with custom objects, then this approach will not work for now. If you have a custom use-case, let me know about it, and we can think of a solution.
92 |
93 | ### Testing
94 |
95 | ``` bash
96 | phpunit tests
97 | ```
98 |
99 | ### Changelog
100 |
101 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
102 |
103 | ### Security
104 |
105 | If you discover any security-related issues, please email me at c.rumpel@kabsi.at instead of using the issue tracker.
106 |
107 | ## License
108 |
109 | The MIT License (MIT).
110 |
--------------------------------------------------------------------------------
/tests/Controllers/NotificationControllerTest.php:
--------------------------------------------------------------------------------
1 | notificationClassName = 'Christophrumpel\NovaNotifications\Tests\Notifications\TestNotification';
19 | factory(TestModel::class)
20 | ->times(4)
21 | ->create();
22 | }
23 |
24 | /** @test * */
25 | public function it_returns_no_nova_notifications()
26 | {
27 | $this->get('nova-vendor/nova-notifications/notifications')
28 | ->assertSuccessful()
29 | ->assertJsonCount(0);
30 | }
31 |
32 | /** @test * */
33 | public function it_returns_given_nova_notifications()
34 | {
35 | $notifications = factory(NovaNotification::class)
36 | ->times(2)
37 | ->create();
38 |
39 | $this->get('nova-vendor/nova-notifications/notifications')
40 | ->assertSuccessful()
41 | ->assertJsonCount(2)
42 | ->assertJson([
43 | $notifications[0]->toArray(),
44 | $notifications[1]->toArray(),
45 | ]);
46 | }
47 |
48 | /** @test * */
49 | public function it_does_not_find_the_provided_notification_class()
50 | {
51 | $notificationData = [
52 | 'notification' => ['name' => 'NotGivenNotification'],
53 | 'notificationParameters' => [],
54 | 'notifiable' => [
55 | 'name' => 'App\User',
56 | 'value' => '2',
57 | ],
58 | ];
59 |
60 | $this->post('nova-vendor/nova-notifications/notifications/send', $notificationData)
61 | ->assertStatus(400);
62 | }
63 |
64 | /** @test * */
65 | public function it_sends_a_notification()
66 | {
67 | $this->disableExceptionHandling();
68 |
69 | $notificationData = [
70 | 'notification' => ['name' => $this->notificationClassName],
71 | 'notificationParameters' => [],
72 | 'notifiable' => [
73 | 'name' => 'Christophrumpel\NovaNotifications\Tests\Models\TestModel',
74 | 'value' => '2',
75 | ],
76 | ];
77 |
78 | $this->post('nova-vendor/nova-notifications/notifications/send', $notificationData)
79 | ->assertSuccessful();
80 |
81 | Notification::assertSentTo(TestModel::find(2), $this->notificationClassName);
82 | }
83 |
84 | /** @test * */
85 | public function it_sends_a_notification_with_wrong_notifiable_id()
86 | {
87 | $notificationData = [
88 | 'notification' => ['name' => $this->notificationClassName],
89 | 'notificationParameters' => [],
90 | 'notifiable' => [
91 | 'name' => 'Christophrumpel\NovaNotifications\Tests\Models\TestModel',
92 | 'value' => '99',
93 | ],
94 | ];
95 |
96 | $this->post('nova-vendor/nova-notifications/notifications/send', $notificationData)
97 | ->assertStatus(404);
98 | }
99 |
100 | /** @test * */
101 | public function it_sends_a_notification_with_wrong_params()
102 | {
103 | $this->disableExceptionHandling();
104 | $notificationData = [
105 | 'notification' => ['name' => $this->notificationClassName],
106 | 'notificationParameters' => [
107 | [
108 | 'type' => 'int',
109 | 'value' => 'wrongInputType',
110 | ],
111 | ],
112 | 'notifiable' => [
113 | 'name' => 'Christophrumpel\NovaNotifications\Tests\TestModel',
114 | 'value' => '99',
115 | ],
116 | ];
117 |
118 | $this->post('nova-vendor/nova-notifications/notifications/send', $notificationData)
119 | ->assertStatus(422);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationsSend.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{__('Send Notification')}}
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | | {{__('Name')}} |
19 | {{__('Parameters')}} |
20 | |
21 |
22 |
23 |
24 |
25 |
26 | | {{ notificationClass.name }} |
27 |
28 |
29 | {{param.name}} ({{param.type}})
30 |
31 | |
32 |
33 |
36 | |
37 |
38 |
39 |
40 |
41 |
42 |
{{__("You don't have any notification classes yet.")}}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
129 |
130 |
133 |
--------------------------------------------------------------------------------
/resources/js/components/NotificationsParamModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{ selectedNotification.name}}
8 |
9 |
10 |
{{__('Send Notification To')}}
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {{__('Define Notification Parameters')}}
73 |
74 |
75 |
77 |
78 |
82 |
83 |
84 |
85 |
86 |
92 |
98 |
99 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
114 |
115 |
116 |
117 |
118 |
152 |
153 |
156 |
--------------------------------------------------------------------------------
/coverage.clover:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/tailwind.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Tailwind - The Utility-First CSS Framework
4 |
5 | A project by Adam Wathan (@adamwathan), Jonathan Reinink (@reinink),
6 | David Hemphill (@davidhemphill) and Steve Schoger (@steveschoger).
7 |
8 | Welcome to the Tailwind config file. This is where you can customize
9 | Tailwind specifically for your project. Don't be intimidated by the
10 | length of this file. It's really just a big JavaScript object and
11 | we've done our very best to explain each section.
12 |
13 | View the full documentation at https://tailwindcss.com.
14 |
15 |
16 | |-------------------------------------------------------------------------------
17 | | The default config
18 | |-------------------------------------------------------------------------------
19 | |
20 | | This variable contains the default Tailwind config. You don't have
21 | | to use it, but it can sometimes be helpful to have available. For
22 | | example, you may choose to merge your custom configuration
23 | | values with some of the Tailwind defaults.
24 | |
25 | */
26 |
27 | // let defaultConfig = require('tailwindcss/defaultConfig')()
28 |
29 |
30 | /*
31 | |-------------------------------------------------------------------------------
32 | | Colors https://tailwindcss.com/docs/colors
33 | |-------------------------------------------------------------------------------
34 | |
35 | | Here you can specify the colors used in your project. To get you started,
36 | | we've provided a generous palette of great looking colors that are perfect
37 | | for prototyping, but don't hesitate to change them for your project. You
38 | | own these colors, nothing will break if you change everything about them.
39 | |
40 | | We've used literal color names ("red", "blue", etc.) for the default
41 | | palette, but if you'd rather use functional names like "primary" and
42 | | "secondary", or even a numeric scale like "100" and "200", go for it.
43 | |
44 | */
45 |
46 | let colors = {
47 | 'transparent': 'transparent',
48 |
49 | 'black': '#22292f',
50 | 'grey-darkest': '#3d4852',
51 | 'grey-darker': '#606f7b',
52 | 'grey-dark': '#8795a1',
53 | 'grey': '#b8c2cc',
54 | 'grey-light': '#dae1e7',
55 | 'grey-lighter': '#f1f5f8',
56 | 'grey-lightest': '#f8fafc',
57 | 'white': '#ffffff',
58 |
59 | 'red-darkest': '#3b0d0c',
60 | 'red-darker': '#621b18',
61 | 'red-dark': '#cc1f1a',
62 | 'red': '#e3342f',
63 | 'red-light': '#ef5753',
64 | 'red-lighter': '#f9acaa',
65 | 'red-lightest': '#fcebea',
66 |
67 | 'orange-darkest': '#462a16',
68 | 'orange-darker': '#613b1f',
69 | 'orange-dark': '#de751f',
70 | 'orange': '#f6993f',
71 | 'orange-light': '#faad63',
72 | 'orange-lighter': '#fcd9b6',
73 | 'orange-lightest': '#fff5eb',
74 |
75 | 'yellow-darkest': '#453411',
76 | 'yellow-darker': '#684f1d',
77 | 'yellow-dark': '#f2d024',
78 | 'yellow': '#ffed4a',
79 | 'yellow-light': '#fff382',
80 | 'yellow-lighter': '#fff9c2',
81 | 'yellow-lightest': '#fcfbeb',
82 |
83 | 'green-darkest': '#0f2f21',
84 | 'green-darker': '#1a4731',
85 | 'green-dark': '#1f9d55',
86 | 'green': '#38c172',
87 | 'green-light': '#51d88a',
88 | 'green-lighter': '#a2f5bf',
89 | 'green-lightest': '#e3fcec',
90 |
91 | 'teal-darkest': '#0d3331',
92 | 'teal-darker': '#20504f',
93 | 'teal-dark': '#38a89d',
94 | 'teal': '#4dc0b5',
95 | 'teal-light': '#64d5ca',
96 | 'teal-lighter': '#a0f0ed',
97 | 'teal-lightest': '#e8fffe',
98 |
99 | 'blue-darkest': '#12283a',
100 | 'blue-darker': '#1c3d5a',
101 | 'blue-dark': '#2779bd',
102 | 'blue': '#3490dc',
103 | 'blue-light': '#6cb2eb',
104 | 'blue-lighter': '#bcdefa',
105 | 'blue-lightest': '#eff8ff',
106 |
107 | 'indigo-darkest': '#191e38',
108 | 'indigo-darker': '#2f365f',
109 | 'indigo-dark': '#5661b3',
110 | 'indigo': '#6574cd',
111 | 'indigo-light': '#7886d7',
112 | 'indigo-lighter': '#b2b7ff',
113 | 'indigo-lightest': '#e6e8ff',
114 |
115 | 'purple-darkest': '#21183c',
116 | 'purple-darker': '#382b5f',
117 | 'purple-dark': '#794acf',
118 | 'purple': '#9561e2',
119 | 'purple-light': '#a779e9',
120 | 'purple-lighter': '#d6bbfc',
121 | 'purple-lightest': '#f3ebff',
122 |
123 | 'pink-darkest': '#451225',
124 | 'pink-darker': '#6f213f',
125 | 'pink-dark': '#eb5286',
126 | 'pink': '#f66d9b',
127 | 'pink-light': '#fa7ea8',
128 | 'pink-lighter': '#ffbbca',
129 | 'pink-lightest': '#ffebef',
130 | }
131 |
132 | module.exports = {
133 |
134 | /*
135 | |-----------------------------------------------------------------------------
136 | | Colors https://tailwindcss.com/docs/colors
137 | |-----------------------------------------------------------------------------
138 | |
139 | | The color palette defined above is also assigned to the "colors" key of
140 | | your Tailwind config. This makes it easy to access them in your CSS
141 | | using Tailwind's config helper. For example:
142 | |
143 | | .error { color: config('colors.red') }
144 | |
145 | */
146 |
147 | colors: colors,
148 |
149 |
150 | /*
151 | |-----------------------------------------------------------------------------
152 | | Screens https://tailwindcss.com/docs/responsive-design
153 | |-----------------------------------------------------------------------------
154 | |
155 | | Screens in Tailwind are translated to CSS media queries. They define the
156 | | responsive breakpoints for your project. By default Tailwind takes a
157 | | "mobile first" approach, where each screen size represents a minimum
158 | | viewport width. Feel free to have as few or as many screens as you
159 | | want, naming them in whatever way you'd prefer for your project.
160 | |
161 | | Tailwind also allows for more complex screen definitions, which can be
162 | | useful in certain situations. Be sure to see the full responsive
163 | | documentation for a complete list of options.
164 | |
165 | | Class name: .{screen}:{utility}
166 | |
167 | */
168 |
169 | screens: {
170 | 'sm': '576px',
171 | 'md': '768px',
172 | 'lg': '992px',
173 | 'xl': '1200px',
174 | },
175 |
176 |
177 | /*
178 | |-----------------------------------------------------------------------------
179 | | Fonts https://tailwindcss.com/docs/fonts
180 | |-----------------------------------------------------------------------------
181 | |
182 | | Here is where you define your project's font stack, or font families.
183 | | Keep in mind that Tailwind doesn't actually load any fonts for you.
184 | | If you're using custom fonts you'll need to import them prior to
185 | | defining them here.
186 | |
187 | | By default we provide a native font stack that works remarkably well on
188 | | any device or OS you're using, since it just uses the default fonts
189 | | provided by the platform.
190 | |
191 | | Class name: .font-{name}
192 | |
193 | */
194 |
195 | fonts: {
196 | 'sans': [
197 | 'system-ui',
198 | 'BlinkMacSystemFont',
199 | '-apple-system',
200 | 'Segoe UI',
201 | 'Roboto',
202 | 'Oxygen',
203 | 'Ubuntu',
204 | 'Cantarell',
205 | 'Fira Sans',
206 | 'Droid Sans',
207 | 'Helvetica Neue',
208 | 'sans-serif',
209 | ],
210 | 'serif': [
211 | 'Constantia',
212 | 'Lucida Bright',
213 | 'Lucidabright',
214 | 'Lucida Serif',
215 | 'Lucida',
216 | 'DejaVu Serif',
217 | 'Bitstream Vera Serif',
218 | 'Liberation Serif',
219 | 'Georgia',
220 | 'serif',
221 | ],
222 | 'mono': [
223 | 'Menlo',
224 | 'Monaco',
225 | 'Consolas',
226 | 'Liberation Mono',
227 | 'Courier New',
228 | 'monospace',
229 | ]
230 | },
231 |
232 |
233 | /*
234 | |-----------------------------------------------------------------------------
235 | | Text sizes https://tailwindcss.com/docs/text-sizing
236 | |-----------------------------------------------------------------------------
237 | |
238 | | Here is where you define your text sizes. Name these in whatever way
239 | | makes the most sense to you. We use size names by default, but
240 | | you're welcome to use a numeric scale or even something else
241 | | entirely.
242 | |
243 | | By default Tailwind uses the "rem" unit type for most measurements.
244 | | This allows you to set a root font size which all other sizes are
245 | | then based on. That said, you are free to use whatever units you
246 | | prefer, be it rems, ems, pixels or other.
247 | |
248 | | Class name: .text-{size}
249 | |
250 | */
251 |
252 | textSizes: {
253 | 'xs': '.75rem', // 12px
254 | 'sm': '.875rem', // 14px
255 | 'base': '1rem', // 16px
256 | 'lg': '1.125rem', // 18px
257 | 'xl': '1.25rem', // 20px
258 | '2xl': '1.5rem', // 24px
259 | '3xl': '1.875rem', // 30px
260 | '4xl': '2.25rem', // 36px
261 | '5xl': '3rem', // 48px
262 | },
263 |
264 |
265 | /*
266 | |-----------------------------------------------------------------------------
267 | | Font weights https://tailwindcss.com/docs/font-weight
268 | |-----------------------------------------------------------------------------
269 | |
270 | | Here is where you define your font weights. We've provided a list of
271 | | common font weight names with their respective numeric scale values
272 | | to get you started. It's unlikely that your project will require
273 | | all of these, so we recommend removing those you don't need.
274 | |
275 | | Class name: .font-{weight}
276 | |
277 | */
278 |
279 | fontWeights: {
280 | 'hairline': 100,
281 | 'thin': 200,
282 | 'light': 300,
283 | 'normal': 400,
284 | 'medium': 500,
285 | 'semibold': 600,
286 | 'bold': 700,
287 | 'extrabold': 800,
288 | 'black': 900,
289 | },
290 |
291 |
292 | /*
293 | |-----------------------------------------------------------------------------
294 | | Leading (line height) https://tailwindcss.com/docs/line-height
295 | |-----------------------------------------------------------------------------
296 | |
297 | | Here is where you define your line height values, or as we call
298 | | them in Tailwind, leadings.
299 | |
300 | | Class name: .leading-{size}
301 | |
302 | */
303 |
304 | leading: {
305 | 'none': 1,
306 | 'tight': 1.25,
307 | 'normal': 1.5,
308 | 'loose': 2,
309 | },
310 |
311 |
312 | /*
313 | |-----------------------------------------------------------------------------
314 | | Tracking (letter spacing) https://tailwindcss.com/docs/letter-spacing
315 | |-----------------------------------------------------------------------------
316 | |
317 | | Here is where you define your letter spacing values, or as we call
318 | | them in Tailwind, tracking.
319 | |
320 | | Class name: .tracking-{size}
321 | |
322 | */
323 |
324 | tracking: {
325 | 'tight': '-0.05em',
326 | 'normal': '0',
327 | 'wide': '0.05em',
328 | },
329 |
330 |
331 | /*
332 | |-----------------------------------------------------------------------------
333 | | Text colors https://tailwindcss.com/docs/text-color
334 | |-----------------------------------------------------------------------------
335 | |
336 | | Here is where you define your text colors. By default these use the
337 | | color palette we defined above, however you're welcome to set these
338 | | independently if that makes sense for your project.
339 | |
340 | | Class name: .text-{color}
341 | |
342 | */
343 |
344 | textColors: colors,
345 |
346 |
347 | /*
348 | |-----------------------------------------------------------------------------
349 | | Background colors https://tailwindcss.com/docs/background-color
350 | |-----------------------------------------------------------------------------
351 | |
352 | | Here is where you define your background colors. By default these use
353 | | the color palette we defined above, however you're welcome to set
354 | | these independently if that makes sense for your project.
355 | |
356 | | Class name: .bg-{color}
357 | |
358 | */
359 |
360 | backgroundColors: colors,
361 |
362 |
363 | /*
364 | |-----------------------------------------------------------------------------
365 | | Background sizes https://tailwindcss.com/docs/background-size
366 | |-----------------------------------------------------------------------------
367 | |
368 | | Here is where you define your background sizes. We provide some common
369 | | values that are useful in most projects, but feel free to add other sizes
370 | | that are specific to your project here as well.
371 | |
372 | | Class name: .bg-{size}
373 | |
374 | */
375 |
376 | backgroundSize: {
377 | 'auto': 'auto',
378 | 'cover': 'cover',
379 | 'contain': 'contain',
380 | },
381 |
382 |
383 | /*
384 | |-----------------------------------------------------------------------------
385 | | Border widths https://tailwindcss.com/docs/border-width
386 | |-----------------------------------------------------------------------------
387 | |
388 | | Here is where you define your border widths. Take note that border
389 | | widths require a special "default" value set as well. This is the
390 | | width that will be used when you do not specify a border width.
391 | |
392 | | Class name: .border{-side?}{-width?}
393 | |
394 | */
395 |
396 | borderWidths: {
397 | default: '1px',
398 | '0': '0',
399 | '2': '2px',
400 | '4': '4px',
401 | '8': '8px',
402 | },
403 |
404 |
405 | /*
406 | |-----------------------------------------------------------------------------
407 | | Border colors https://tailwindcss.com/docs/border-color
408 | |-----------------------------------------------------------------------------
409 | |
410 | | Here is where you define your border colors. By default these use the
411 | | color palette we defined above, however you're welcome to set these
412 | | independently if that makes sense for your project.
413 | |
414 | | Take note that border colors require a special "default" value set
415 | | as well. This is the color that will be used when you do not
416 | | specify a border color.
417 | |
418 | | Class name: .border-{color}
419 | |
420 | */
421 |
422 | borderColors: global.Object.assign({ default: colors['grey-light'] }, colors),
423 |
424 |
425 | /*
426 | |-----------------------------------------------------------------------------
427 | | Border radius https://tailwindcss.com/docs/border-radius
428 | |-----------------------------------------------------------------------------
429 | |
430 | | Here is where you define your border radius values. If a `default` radius
431 | | is provided, it will be made available as the non-suffixed `.rounded`
432 | | utility.
433 | |
434 | | If your scale includes a `0` value to reset already rounded corners, it's
435 | | a good idea to put it first so other values are able to override it.
436 | |
437 | | Class name: .rounded{-side?}{-size?}
438 | |
439 | */
440 |
441 | borderRadius: {
442 | 'none': '0',
443 | 'sm': '.125rem',
444 | default: '.25rem',
445 | 'lg': '.5rem',
446 | 'full': '9999px',
447 | },
448 |
449 |
450 | /*
451 | |-----------------------------------------------------------------------------
452 | | Width https://tailwindcss.com/docs/width
453 | |-----------------------------------------------------------------------------
454 | |
455 | | Here is where you define your width utility sizes. These can be
456 | | percentage based, pixels, rems, or any other units. By default
457 | | we provide a sensible rem based numeric scale, a percentage
458 | | based fraction scale, plus some other common use-cases. You
459 | | can, of course, modify these values as needed.
460 | |
461 | |
462 | | It's also worth mentioning that Tailwind automatically escapes
463 | | invalid CSS class name characters, which allows you to have
464 | | awesome classes like .w-2/3.
465 | |
466 | | Class name: .w-{size}
467 | |
468 | */
469 |
470 | width: {
471 | 'auto': 'auto',
472 | 'px': '1px',
473 | '1': '0.25rem',
474 | '2': '0.5rem',
475 | '3': '0.75rem',
476 | '4': '1rem',
477 | '5': '1.25rem',
478 | '6': '1.5rem',
479 | '8': '2rem',
480 | '10': '2.5rem',
481 | '12': '3rem',
482 | '16': '4rem',
483 | '24': '6rem',
484 | '32': '8rem',
485 | '48': '12rem',
486 | '64': '16rem',
487 | '1/2': '50%',
488 | '1/3': '33.33333%',
489 | '2/3': '66.66667%',
490 | '1/4': '25%',
491 | '3/4': '75%',
492 | '1/5': '20%',
493 | '2/5': '40%',
494 | '3/5': '60%',
495 | '4/5': '80%',
496 | '1/6': '16.66667%',
497 | '5/6': '83.33333%',
498 | 'full': '100%',
499 | 'screen': '100vw'
500 | },
501 |
502 |
503 | /*
504 | |-----------------------------------------------------------------------------
505 | | Height https://tailwindcss.com/docs/height
506 | |-----------------------------------------------------------------------------
507 | |
508 | | Here is where you define your height utility sizes. These can be
509 | | percentage based, pixels, rems, or any other units. By default
510 | | we provide a sensible rem based numeric scale plus some other
511 | | common use-cases. You can, of course, modify these values as
512 | | needed.
513 | |
514 | | Class name: .h-{size}
515 | |
516 | */
517 |
518 | height: {
519 | 'auto': 'auto',
520 | 'px': '1px',
521 | '1': '0.25rem',
522 | '2': '0.5rem',
523 | '3': '0.75rem',
524 | '4': '1rem',
525 | '5': '1.25rem',
526 | '6': '1.5rem',
527 | '8': '2rem',
528 | '10': '2.5rem',
529 | '12': '3rem',
530 | '16': '4rem',
531 | '24': '6rem',
532 | '32': '8rem',
533 | '48': '12rem',
534 | '64': '16rem',
535 | 'full': '100%',
536 | 'screen': '100vh'
537 | },
538 |
539 |
540 | /*
541 | |-----------------------------------------------------------------------------
542 | | Minimum width https://tailwindcss.com/docs/min-width
543 | |-----------------------------------------------------------------------------
544 | |
545 | | Here is where you define your minimum width utility sizes. These can
546 | | be percentage based, pixels, rems, or any other units. We provide a
547 | | couple common use-cases by default. You can, of course, modify
548 | | these values as needed.
549 | |
550 | | Class name: .min-w-{size}
551 | |
552 | */
553 |
554 | minWidth: {
555 | '0': '0',
556 | 'full': '100%',
557 | },
558 |
559 |
560 | /*
561 | |-----------------------------------------------------------------------------
562 | | Minimum height https://tailwindcss.com/docs/min-height
563 | |-----------------------------------------------------------------------------
564 | |
565 | | Here is where you define your minimum height utility sizes. These can
566 | | be percentage based, pixels, rems, or any other units. We provide a
567 | | few common use-cases by default. You can, of course, modify these
568 | | values as needed.
569 | |
570 | | Class name: .min-h-{size}
571 | |
572 | */
573 |
574 | minHeight: {
575 | '0': '0',
576 | 'full': '100%',
577 | 'screen': '100vh'
578 | },
579 |
580 |
581 | /*
582 | |-----------------------------------------------------------------------------
583 | | Maximum width https://tailwindcss.com/docs/max-width
584 | |-----------------------------------------------------------------------------
585 | |
586 | | Here is where you define your maximum width utility sizes. These can
587 | | be percentage based, pixels, rems, or any other units. By default
588 | | we provide a sensible rem based scale and a "full width" size,
589 | | which is basically a reset utility. You can, of course,
590 | | modify these values as needed.
591 | |
592 | | Class name: .max-w-{size}
593 | |
594 | */
595 |
596 | maxWidth: {
597 | 'xs': '20rem',
598 | 'sm': '30rem',
599 | 'md': '40rem',
600 | 'lg': '50rem',
601 | 'xl': '60rem',
602 | '2xl': '70rem',
603 | '3xl': '80rem',
604 | '4xl': '90rem',
605 | '5xl': '100rem',
606 | 'full': '100%',
607 | },
608 |
609 |
610 | /*
611 | |-----------------------------------------------------------------------------
612 | | Maximum height https://tailwindcss.com/docs/max-height
613 | |-----------------------------------------------------------------------------
614 | |
615 | | Here is where you define your maximum height utility sizes. These can
616 | | be percentage based, pixels, rems, or any other units. We provide a
617 | | couple common use-cases by default. You can, of course, modify
618 | | these values as needed.
619 | |
620 | | Class name: .max-h-{size}
621 | |
622 | */
623 |
624 | maxHeight: {
625 | 'full': '100%',
626 | 'screen': '100vh',
627 | },
628 |
629 |
630 | /*
631 | |-----------------------------------------------------------------------------
632 | | Padding https://tailwindcss.com/docs/padding
633 | |-----------------------------------------------------------------------------
634 | |
635 | | Here is where you define your padding utility sizes. These can be
636 | | percentage based, pixels, rems, or any other units. By default we
637 | | provide a sensible rem based numeric scale plus a couple other
638 | | common use-cases like "1px". You can, of course, modify these
639 | | values as needed.
640 | |
641 | | Class name: .p{side?}-{size}
642 | |
643 | */
644 |
645 | padding: {
646 | 'px': '1px',
647 | '0': '0',
648 | '1': '0.25rem',
649 | '2': '0.5rem',
650 | '3': '0.75rem',
651 | '4': '1rem',
652 | '5': '1.25rem',
653 | '6': '1.5rem',
654 | '8': '2rem',
655 | '10': '2.5rem',
656 | '12': '3rem',
657 | '16': '4rem',
658 | '20': '5rem',
659 | '24': '6rem',
660 | '32': '8rem',
661 | },
662 |
663 |
664 | /*
665 | |-----------------------------------------------------------------------------
666 | | Margin https://tailwindcss.com/docs/margin
667 | |-----------------------------------------------------------------------------
668 | |
669 | | Here is where you define your margin utility sizes. These can be
670 | | percentage based, pixels, rems, or any other units. By default we
671 | | provide a sensible rem based numeric scale plus a couple other
672 | | common use-cases like "1px". You can, of course, modify these
673 | | values as needed.
674 | |
675 | | Class name: .m{side?}-{size}
676 | |
677 | */
678 |
679 | margin: {
680 | 'auto': 'auto',
681 | 'px': '1px',
682 | '0': '0',
683 | '1': '0.25rem',
684 | '2': '0.5rem',
685 | '3': '0.75rem',
686 | '4': '1rem',
687 | '5': '1.25rem',
688 | '6': '1.5rem',
689 | '8': '2rem',
690 | '10': '2.5rem',
691 | '12': '3rem',
692 | '16': '4rem',
693 | '20': '5rem',
694 | '24': '6rem',
695 | '32': '8rem',
696 | },
697 |
698 |
699 | /*
700 | |-----------------------------------------------------------------------------
701 | | Negative margin https://tailwindcss.com/docs/negative-margin
702 | |-----------------------------------------------------------------------------
703 | |
704 | | Here is where you define your negative margin utility sizes. These can
705 | | be percentage based, pixels, rems, or any other units. By default we
706 | | provide matching values to the padding scale since these utilities
707 | | generally get used together. You can, of course, modify these
708 | | values as needed.
709 | |
710 | | Class name: .-m{side?}-{size}
711 | |
712 | */
713 |
714 | negativeMargin: {
715 | 'px': '1px',
716 | '0': '0',
717 | '1': '0.25rem',
718 | '2': '0.5rem',
719 | '3': '0.75rem',
720 | '4': '1rem',
721 | '5': '1.25rem',
722 | '6': '1.5rem',
723 | '8': '2rem',
724 | '10': '2.5rem',
725 | '12': '3rem',
726 | '16': '4rem',
727 | '20': '5rem',
728 | '24': '6rem',
729 | '32': '8rem',
730 | },
731 |
732 |
733 | /*
734 | |-----------------------------------------------------------------------------
735 | | Shadows https://tailwindcss.com/docs/shadows
736 | |-----------------------------------------------------------------------------
737 | |
738 | | Here is where you define your shadow utilities. As you can see from
739 | | the defaults we provide, it's possible to apply multiple shadows
740 | | per utility using comma separation.
741 | |
742 | | If a `default` shadow is provided, it will be made available as the non-
743 | | suffixed `.shadow` utility.
744 | |
745 | | Class name: .shadow-{size?}
746 | |
747 | */
748 |
749 | shadows: {
750 | default: '0 2px 4px 0 rgba(0,0,0,0.10)',
751 | 'md': '0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)',
752 | 'lg': '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)',
753 | 'inner': 'inset 0 2px 4px 0 rgba(0,0,0,0.06)',
754 | 'outline': '0 0 0 3px rgba(52,144,220,0.5)',
755 | 'none': 'none',
756 | },
757 |
758 |
759 | /*
760 | |-----------------------------------------------------------------------------
761 | | Z-index https://tailwindcss.com/docs/z-index
762 | |-----------------------------------------------------------------------------
763 | |
764 | | Here is where you define your z-index utility values. By default we
765 | | provide a sensible numeric scale. You can, of course, modify these
766 | | values as needed.
767 | |
768 | | Class name: .z-{index}
769 | |
770 | */
771 |
772 | zIndex: {
773 | 'auto': 'auto',
774 | '0': 0,
775 | '10': 10,
776 | '20': 20,
777 | '30': 30,
778 | '40': 40,
779 | '50': 50,
780 | },
781 |
782 |
783 | /*
784 | |-----------------------------------------------------------------------------
785 | | Opacity https://tailwindcss.com/docs/opacity
786 | |-----------------------------------------------------------------------------
787 | |
788 | | Here is where you define your opacity utility values. By default we
789 | | provide a sensible numeric scale. You can, of course, modify these
790 | | values as needed.
791 | |
792 | | Class name: .opacity-{name}
793 | |
794 | */
795 |
796 | opacity: {
797 | '0': '0',
798 | '25': '.25',
799 | '50': '.5',
800 | '75': '.75',
801 | '100': '1',
802 | },
803 |
804 |
805 | /*
806 | |-----------------------------------------------------------------------------
807 | | SVG fill https://tailwindcss.com/docs/svg
808 | |-----------------------------------------------------------------------------
809 | |
810 | | Here is where you define your SVG fill colors. By default we just provide
811 | | `fill-current` which sets the fill to the current text color. This lets you
812 | | specify a fill color using existing text color utilities and helps keep the
813 | | generated CSS file size down.
814 | |
815 | | Class name: .fill-{name}
816 | |
817 | */
818 |
819 | svgFill: {
820 | 'current': 'currentColor',
821 | },
822 |
823 |
824 | /*
825 | |-----------------------------------------------------------------------------
826 | | SVG stroke https://tailwindcss.com/docs/svg
827 | |-----------------------------------------------------------------------------
828 | |
829 | | Here is where you define your SVG stroke colors. By default we just provide
830 | | `stroke-current` which sets the stroke to the current text color. This lets
831 | | you specify a stroke color using existing text color utilities and helps
832 | | keep the generated CSS file size down.
833 | |
834 | | Class name: .stroke-{name}
835 | |
836 | */
837 |
838 | svgStroke: {
839 | 'current': 'currentColor',
840 | },
841 |
842 |
843 | /*
844 | |-----------------------------------------------------------------------------
845 | | Modules https://tailwindcss.com/docs/configuration#modules
846 | |-----------------------------------------------------------------------------
847 | |
848 | | Here is where you control which modules are generated and what variants are
849 | | generated for each of those modules.
850 | |
851 | | Currently supported variants:
852 | | - responsive
853 | | - hover
854 | | - focus
855 | | - active
856 | | - group-hover
857 | |
858 | | To disable a module completely, use `false` instead of an array.
859 | |
860 | */
861 |
862 | modules: {
863 | appearance: ['responsive'],
864 | backgroundAttachment: ['responsive'],
865 | backgroundColors: ['responsive', 'hover', 'focus'],
866 | backgroundPosition: ['responsive'],
867 | backgroundRepeat: ['responsive'],
868 | backgroundSize: ['responsive'],
869 | borderCollapse: [],
870 | borderColors: ['responsive', 'hover', 'focus'],
871 | borderRadius: ['responsive'],
872 | borderStyle: ['responsive'],
873 | borderWidths: ['responsive'],
874 | cursor: ['responsive'],
875 | display: ['responsive'],
876 | flexbox: ['responsive'],
877 | float: ['responsive'],
878 | fonts: ['responsive'],
879 | fontWeights: ['responsive', 'hover', 'focus'],
880 | height: ['responsive'],
881 | leading: ['responsive'],
882 | lists: ['responsive'],
883 | margin: ['responsive'],
884 | maxHeight: ['responsive'],
885 | maxWidth: ['responsive'],
886 | minHeight: ['responsive'],
887 | minWidth: ['responsive'],
888 | negativeMargin: ['responsive'],
889 | opacity: ['responsive'],
890 | outline: ['focus'],
891 | overflow: ['responsive'],
892 | padding: ['responsive'],
893 | pointerEvents: ['responsive'],
894 | position: ['responsive'],
895 | resize: ['responsive'],
896 | shadows: ['responsive', 'hover', 'focus'],
897 | svgFill: [],
898 | svgStroke: [],
899 | tableLayout: ['responsive'],
900 | textAlign: ['responsive'],
901 | textColors: ['responsive', 'hover', 'focus'],
902 | textSizes: ['responsive'],
903 | textStyle: ['responsive', 'hover', 'focus'],
904 | tracking: ['responsive'],
905 | userSelect: ['responsive'],
906 | verticalAlign: ['responsive'],
907 | visibility: ['responsive'],
908 | whitespace: ['responsive'],
909 | width: ['responsive'],
910 | zIndex: ['responsive'],
911 | },
912 |
913 |
914 | /*
915 | |-----------------------------------------------------------------------------
916 | | Plugins https://tailwindcss.com/docs/plugins
917 | |-----------------------------------------------------------------------------
918 | |
919 | | Here is where you can register any plugins you'd like to use in your
920 | | project. Tailwind's built-in `container` plugin is enabled by default to
921 | | give you a Bootstrap-style responsive container component out of the box.
922 | |
923 | | Be sure to view the complete plugin documentation to learn more about how
924 | | the plugin system works.
925 | |
926 | */
927 |
928 | plugins: [
929 | require('tailwindcss/plugins/container')({
930 | // center: true,
931 | // padding: '1rem',
932 | }),
933 | ],
934 |
935 |
936 | /*
937 | |-----------------------------------------------------------------------------
938 | | Advanced Options https://tailwindcss.com/docs/configuration#options
939 | |-----------------------------------------------------------------------------
940 | |
941 | | Here is where you can tweak advanced configuration options. We recommend
942 | | leaving these options alone unless you absolutely need to change them.
943 | |
944 | */
945 |
946 | options: {
947 | prefix: '',
948 | important: false,
949 | separator: ':',
950 | },
951 |
952 | }
953 |
--------------------------------------------------------------------------------
/dist/js/tool.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 3);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports) {
69 |
70 | /* globals __VUE_SSR_CONTEXT__ */
71 |
72 | // IMPORTANT: Do NOT use ES2015 features in this file.
73 | // This module is a runtime utility for cleaner component module output and will
74 | // be included in the final webpack user bundle.
75 |
76 | module.exports = function normalizeComponent (
77 | rawScriptExports,
78 | compiledTemplate,
79 | functionalTemplate,
80 | injectStyles,
81 | scopeId,
82 | moduleIdentifier /* server only */
83 | ) {
84 | var esModule
85 | var scriptExports = rawScriptExports = rawScriptExports || {}
86 |
87 | // ES6 modules interop
88 | var type = typeof rawScriptExports.default
89 | if (type === 'object' || type === 'function') {
90 | esModule = rawScriptExports
91 | scriptExports = rawScriptExports.default
92 | }
93 |
94 | // Vue.extend constructor export interop
95 | var options = typeof scriptExports === 'function'
96 | ? scriptExports.options
97 | : scriptExports
98 |
99 | // render functions
100 | if (compiledTemplate) {
101 | options.render = compiledTemplate.render
102 | options.staticRenderFns = compiledTemplate.staticRenderFns
103 | options._compiled = true
104 | }
105 |
106 | // functional template
107 | if (functionalTemplate) {
108 | options.functional = true
109 | }
110 |
111 | // scopedId
112 | if (scopeId) {
113 | options._scopeId = scopeId
114 | }
115 |
116 | var hook
117 | if (moduleIdentifier) { // server build
118 | hook = function (context) {
119 | // 2.3 injection
120 | context =
121 | context || // cached call
122 | (this.$vnode && this.$vnode.ssrContext) || // stateful
123 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
124 | // 2.2 with runInNewContext: true
125 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
126 | context = __VUE_SSR_CONTEXT__
127 | }
128 | // inject component styles
129 | if (injectStyles) {
130 | injectStyles.call(this, context)
131 | }
132 | // register component module identifier for async chunk inferrence
133 | if (context && context._registeredComponents) {
134 | context._registeredComponents.add(moduleIdentifier)
135 | }
136 | }
137 | // used by ssr in case component is cached and beforeCreate
138 | // never gets called
139 | options._ssrRegister = hook
140 | } else if (injectStyles) {
141 | hook = injectStyles
142 | }
143 |
144 | if (hook) {
145 | var functional = options.functional
146 | var existing = functional
147 | ? options.render
148 | : options.beforeCreate
149 |
150 | if (!functional) {
151 | // inject component registration as beforeCreate hook
152 | options.beforeCreate = existing
153 | ? [].concat(existing, hook)
154 | : [hook]
155 | } else {
156 | // for template-only hot-reload because in that case the render fn doesn't
157 | // go through the normalizer
158 | options._injectStyles = hook
159 | // register for functioal component in vue file
160 | options.render = function renderWithStyleInjection (h, context) {
161 | hook.call(context)
162 | return existing(h, context)
163 | }
164 | }
165 | }
166 |
167 | return {
168 | esModule: esModule,
169 | exports: scriptExports,
170 | options: options
171 | }
172 | }
173 |
174 |
175 | /***/ }),
176 | /* 1 */
177 | /***/ (function(module, exports) {
178 |
179 | /*
180 | MIT License http://www.opensource.org/licenses/mit-license.php
181 | Author Tobias Koppers @sokra
182 | */
183 | // css base code, injected by the css-loader
184 | module.exports = function(useSourceMap) {
185 | var list = [];
186 |
187 | // return the list of modules as css string
188 | list.toString = function toString() {
189 | return this.map(function (item) {
190 | var content = cssWithMappingToString(item, useSourceMap);
191 | if(item[2]) {
192 | return "@media " + item[2] + "{" + content + "}";
193 | } else {
194 | return content;
195 | }
196 | }).join("");
197 | };
198 |
199 | // import a list of modules into the list
200 | list.i = function(modules, mediaQuery) {
201 | if(typeof modules === "string")
202 | modules = [[null, modules, ""]];
203 | var alreadyImportedModules = {};
204 | for(var i = 0; i < this.length; i++) {
205 | var id = this[i][0];
206 | if(typeof id === "number")
207 | alreadyImportedModules[id] = true;
208 | }
209 | for(i = 0; i < modules.length; i++) {
210 | var item = modules[i];
211 | // skip already imported module
212 | // this implementation is not 100% perfect for weird media query combinations
213 | // when a module is imported multiple times with different media queries.
214 | // I hope this will never occur (Hey this way we have smaller bundles)
215 | if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
216 | if(mediaQuery && !item[2]) {
217 | item[2] = mediaQuery;
218 | } else if(mediaQuery) {
219 | item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
220 | }
221 | list.push(item);
222 | }
223 | }
224 | };
225 | return list;
226 | };
227 |
228 | function cssWithMappingToString(item, useSourceMap) {
229 | var content = item[1] || '';
230 | var cssMapping = item[3];
231 | if (!cssMapping) {
232 | return content;
233 | }
234 |
235 | if (useSourceMap && typeof btoa === 'function') {
236 | var sourceMapping = toComment(cssMapping);
237 | var sourceURLs = cssMapping.sources.map(function (source) {
238 | return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'
239 | });
240 |
241 | return [content].concat(sourceURLs).concat([sourceMapping]).join('\n');
242 | }
243 |
244 | return [content].join('\n');
245 | }
246 |
247 | // Adapted from convert-source-map (MIT)
248 | function toComment(sourceMap) {
249 | // eslint-disable-next-line no-undef
250 | var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
251 | var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;
252 |
253 | return '/*# ' + data + ' */';
254 | }
255 |
256 |
257 | /***/ }),
258 | /* 2 */
259 | /***/ (function(module, exports, __webpack_require__) {
260 |
261 | /*
262 | MIT License http://www.opensource.org/licenses/mit-license.php
263 | Author Tobias Koppers @sokra
264 | Modified by Evan You @yyx990803
265 | */
266 |
267 | var hasDocument = typeof document !== 'undefined'
268 |
269 | if (typeof DEBUG !== 'undefined' && DEBUG) {
270 | if (!hasDocument) {
271 | throw new Error(
272 | 'vue-style-loader cannot be used in a non-browser environment. ' +
273 | "Use { target: 'node' } in your Webpack config to indicate a server-rendering environment."
274 | ) }
275 | }
276 |
277 | var listToStyles = __webpack_require__(8)
278 |
279 | /*
280 | type StyleObject = {
281 | id: number;
282 | parts: Array
283 | }
284 |
285 | type StyleObjectPart = {
286 | css: string;
287 | media: string;
288 | sourceMap: ?string
289 | }
290 | */
291 |
292 | var stylesInDom = {/*
293 | [id: number]: {
294 | id: number,
295 | refs: number,
296 | parts: Array<(obj?: StyleObjectPart) => void>
297 | }
298 | */}
299 |
300 | var head = hasDocument && (document.head || document.getElementsByTagName('head')[0])
301 | var singletonElement = null
302 | var singletonCounter = 0
303 | var isProduction = false
304 | var noop = function () {}
305 | var options = null
306 | var ssrIdKey = 'data-vue-ssr-id'
307 |
308 | // Force single-tag solution on IE6-9, which has a hard limit on the # of