├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src ├── Commands └── PackageSetup.php ├── Controllers ├── Ops.php └── TagosController.php ├── Observers └── TagObserver.php ├── Tagos.php ├── TagosRoutes.php ├── TagosServiceProvider.php ├── Traits └── HasTags.php ├── config └── tags.php ├── database └── seeds │ └── TagsTableSeeder.php └── resources ├── assets ├── js │ ├── add.vue │ ├── index.vue │ ├── manager.js │ ├── mixins │ │ └── search.js │ └── table │ │ ├── item.vue │ │ └── list.vue └── sass │ ├── _checkbox.scss │ ├── _vue.scss │ └── style.scss ├── lang └── en │ └── messages.php └── views ├── editor.blade.php ├── index.blade.php ├── partials ├── add.blade.php ├── display.blade.php └── shared.blade.php └── show.blade.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ctf0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Muah 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Tagos 3 |
4 | Latest Stable Version Total Downloads 5 |

6 | 7 | A Tag **Editor** and **Selector** based on [spatie/laravel-tags](https://github.com/spatie/laravel-tags). 8 | 9 | #### Editor 10 |

11 | 12 |

13 | 14 | #### Selector 15 |

16 | 17 |

18 | 19 | - package requires Laravel v5.5+ 20 | 21 |
22 | 23 | ## Installation 24 | 25 | - `composer require ctf0/tagos` 26 | 27 | - publish the package assets with 28 | 29 | `php artisan vendor:publish --provider="ctf0\Tagos\TagosServiceProvider"`
30 | `php artisan vendor:publish --provider="Spatie\Tags\TagsServiceProvider" --tag="migrations"` 31 | 32 | - after installation, run `php artisan tagos:setup` to add 33 | + package routes to `routes/web.php` 34 | + package assets compiling to `webpack.mix.js` 35 | 36 | - install dependencies 37 | 38 | ```bash 39 | yarn add vue vue-awesome@v2 vue-notif vue-tippy@v2 axios fuse.js 40 | ``` 41 | 42 | - add this one liner to your main js file and run `npm run watch` to compile your `js/css` files. 43 | - if you are having issues [Check](https://ctf0.wordpress.com/2017/09/12/laravel-mix-es6/) 44 | 45 | ```js 46 | // app.js 47 | 48 | window.Vue = require('vue') 49 | 50 | require('../vendor/Tagos/js/manager') 51 | 52 | new Vue({ 53 | el: '#app' 54 | }) 55 | ``` 56 | 57 |
58 | 59 | ## Features 60 | - tags editor & selector. 61 | - show tag suggestion as you type. 62 | - easily add new tag name & type. 63 | - show tagged items by tag & by type. 64 | - search for tags by name in tags index. 65 | - shortcuts 66 | 67 | | interactions | keyboard | mouse (click) | 68 | |----------------|----------|---------------| 69 | | show all tags | | *(input)* 2x | 70 | | add new tag | enter | * | 71 | | hide tags list | esc | anywhere | 72 | 73 |
74 | 75 | ## Usage 76 | 77 | - migrate the tags table with `php artisan migrate` 78 | 79 | - there is also a seeder to quickly get you going 80 | ```php 81 | // database/seeds/DatabaseSeeder.php 82 | 83 | class DatabaseSeeder extends Seeder 84 | { 85 | /** 86 | * Run the database seeds. 87 | */ 88 | public function run() 89 | { 90 | //... 91 | 92 | $this->call(TagsTableSeeder::class); 93 | } 94 | } 95 | ``` 96 | 97 | - add `HasTags` trait to your model ex.`post` 98 | 99 | ```php 100 | use ctf0\Tagos\Traits\HasTags; 101 | use Illuminate\Database\Eloquent\Model; 102 | 103 | class Post extends Model 104 | { 105 | use HasTags; 106 | } 107 | ``` 108 | 109 |
110 | 111 | > #### Get All tags 112 | ```php 113 | app('cache')->get('tagos'); 114 | ``` 115 | 116 | > #### Attaching Tags 117 | 118 | - show the tag selector 119 | + ex.`posts create view` 120 | 121 | ```blade 122 | @include('Tagos::partials.add') 123 | ``` 124 | 125 | + ex.`posts edit view` 126 | 127 | ```blade 128 | @include('Tagos::partials.add', ['old' => app('tagos')->getModelTags($post)]) 129 | ``` 130 | 131 | - save the tags 132 | + `store()` 133 | 134 | ```php 135 | $model = Post::create([...]); 136 | 137 | app('tagos')->saveTags($model, $request); 138 | ``` 139 | 140 | + `update()` 141 | 142 | ```php 143 | $model = Post::find($id)->update([...]); 144 | 145 | app('tagos')->saveTags($model, $request); 146 | ``` 147 | 148 |
149 | 150 | > #### Display Model Tags 151 | 152 | ```blade 153 | @include('Tagos::partials.display', [ 154 | 'tags' => $post->tags, 155 | 'showType' => true // whether to show the tag type or not 156 | ]) 157 | ``` 158 | 159 |
160 | 161 | #### Routes 162 | 163 | | Method | URL | Name | Action | 164 | |--------|-----------------------------|---------------------|------------------------------------------------------| 165 | | GET | tags/editor | tagos.editor | \ctf0\Tagos\Controllers\TagosController@editor | 166 | | GET | tags | tagos.index | \ctf0\Tagos\Controllers\TagosController@index | 167 | | GET | tags/type/{type} | tagos.index_type | \ctf0\Tagos\Controllers\TagosController@indexByType | 168 | | GET | tags/{slug} | tagos.show | \ctf0\Tagos\Controllers\TagosController@show | 169 | | GET | tags/type/{type}/tag/{slug} | tagos.show_type | \ctf0\Tagos\Controllers\TagosController@showByType | 170 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctf0/tagos", 3 | "description": "GUI to manage tags in laravel based on spatie/laravel-tags", 4 | "homepage": "https://github.com/ctf0/Tagos", 5 | "license": "MIT", 6 | "keywords": [ 7 | "ctf0", 8 | "tagos", 9 | "tagging", 10 | "gui", 11 | "laravel", 12 | "manager" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Muah", 17 | "email": "muah003@gmail.com" 18 | } 19 | ], 20 | "require": { 21 | "illuminate/support": ">=5.5 <9.0", 22 | "spatie/laravel-tags": "*", 23 | "ctf0/package-changelog": "*" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "ctf0\\Tagos\\": "src" 28 | } 29 | }, 30 | "extra": { 31 | "laravel": { 32 | "providers": [ 33 | "ctf0\\Tagos\\TagosServiceProvider" 34 | ] 35 | }, 36 | "changeLog": "logs" 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "scripts": { 42 | "post-package-install": [ 43 | "@php artisan vendor:publish --provider=\"ctf0\\Tagos\\TagosServiceProvider\"", 44 | "@php artisan vendor:publish --provider=\"Spatie\\Tags\\TagsServiceProvider\" --tag=\"migrations\"" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Commands/PackageSetup.php: -------------------------------------------------------------------------------- 1 | file = app('files'); 20 | 21 | parent::__construct(); 22 | } 23 | 24 | /** 25 | * Execute the console command. 26 | * 27 | * @return mixed 28 | */ 29 | public function handle() 30 | { 31 | // routes 32 | $route_file = base_path('routes/web.php'); 33 | $search = 'Tagos'; 34 | 35 | if ($this->checkExist($route_file, $search)) { 36 | $data = "\n// Tagos\nctf0\Tagos\TagosRoutes::routes();"; 37 | 38 | $this->file->append($route_file, $data); 39 | } 40 | 41 | // mix 42 | $mix_file = base_path('webpack.mix.js'); 43 | $search = 'Tagos'; 44 | 45 | if ($this->checkExist($mix_file, $search)) { 46 | $data = "\n// Tagos\nmix.sass('resources/assets/vendor/Tagos/sass/style.scss', 'public/assets/vendor/Tagos/style.css')"; 47 | 48 | $this->file->append($mix_file, $data); 49 | } 50 | 51 | $this->info('All Done'); 52 | } 53 | 54 | /** 55 | * [checkExist description]. 56 | * 57 | * @param [type] $file [description] 58 | * @param [type] $search [description] 59 | * 60 | * @return [type] [description] 61 | */ 62 | protected function checkExist($file, $search) 63 | { 64 | return $this->file->exists($file) && !Str::contains($this->file->get($file), $search); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Controllers/Ops.php: -------------------------------------------------------------------------------- 1 | locales as $one) { 10 | $item[$one] = $item[$one] ?? null; 11 | } 12 | 13 | return $item; 14 | } 15 | 16 | protected function getModelsByTag($slug) 17 | { 18 | $tag = $this->tagCache->where('slug', $slug)->first(); 19 | 20 | return $this->relation 21 | ->get() 22 | ->where('tag_id', $tag->id) 23 | ->groupBy(function ($item) { 24 | return $item->taggable_type; 25 | }) 26 | ->map(function ($val, $model) use ($tag) { 27 | return app($model)->with(['user', 'tags'])->withAnyTags([$tag->name])->get(); 28 | }); 29 | } 30 | 31 | protected function getModelsByType($type, $slug) 32 | { 33 | $tag = $this->tagCache 34 | ->where('slug', $slug) 35 | ->where('type', $type) 36 | ->first(); 37 | 38 | return $this->relation 39 | ->get() 40 | ->where('tag_id', $tag->id) 41 | ->groupBy(function ($item) { 42 | return $item->taggable_type; 43 | }) 44 | ->map(function ($val, $model) { 45 | return $val->map(function ($item) use ($model) { 46 | return app($model)->with(['user', 'tags'])->find($item->taggable_id); 47 | }); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Controllers/TagosController.php: -------------------------------------------------------------------------------- 1 | relation = app('db')->table('taggables'); 20 | $this->locales = [config('app.locale')]; 21 | $this->tagCache = app('cache')->get('tagos'); 22 | $this->tagModel = app(config('tags.model')); 23 | } 24 | 25 | /** 26 | * Display a listing of the resource. 27 | * 28 | * @return \Illuminate\Http\Response 29 | */ 30 | public function index() 31 | { 32 | $tags = $this->tagCache; 33 | $showType = true; 34 | 35 | return view('Tagos::index', compact('tags', 'showType')); 36 | } 37 | 38 | public function indexByType($type) 39 | { 40 | $tags = $this->tagCache->where('type', $type); 41 | $showType = false; 42 | 43 | return view('Tagos::index', compact('tags', 'showType')); 44 | } 45 | 46 | /** 47 | * Display tags editor. 48 | * 49 | * @return [type] [description] 50 | */ 51 | public function editor() 52 | { 53 | $locales = $this->locales; 54 | $relation = $this->relation->get(); 55 | $tags = $this->tagCache->map(function ($tag) use ($relation) { 56 | return [ 57 | 'count' => $relation->where('tag_id', $tag->id)->count(), 58 | 'order' => $tag->order_column, 59 | 'type' => $tag->type, 60 | 'name' => $this->populateLocales($tag->getTranslations('name')), 61 | 'slug' => $this->populateLocales($tag->getTranslations('slug')), 62 | 'id' => $tag->id, 63 | ]; 64 | }); 65 | 66 | return view('Tagos::editor', compact('tags', 'locales')); 67 | } 68 | 69 | /** 70 | * Display Taged Items. 71 | * 72 | * @param mixed $tag 73 | * 74 | * @return [type] [description] 75 | */ 76 | public function show($tag) 77 | { 78 | $models = $this->getModelsByTag($tag); 79 | 80 | return view('Tagos::show', compact('models')); 81 | } 82 | 83 | public function showByType($type, $slug) 84 | { 85 | $models = $this->getModelsByType($type, $slug); 86 | 87 | return view('Tagos::show', compact('models')); 88 | } 89 | 90 | /** 91 | * Store a newly created resource in storage. 92 | * 93 | * @param \Illuminate\Http\Request $request 94 | * 95 | * @return \Illuminate\Http\Response 96 | */ 97 | public function store(Request $request) 98 | { 99 | $tag = $this->tagModel->create($request->all()); 100 | 101 | return response()->json([ 102 | 'msg' => trans('Tagos::messages.model_created'), 103 | 'item' => [ 104 | 'count' => 0, 105 | 'order' => $tag->order_column, 106 | 'type' => $tag->type, 107 | 'name' => $this->populateLocales($tag->getTranslations('name')), 108 | 'slug' => $this->populateLocales($tag->getTranslations('slug')), 109 | 'id' => $tag->id, 110 | ], 111 | ]); 112 | } 113 | 114 | /** 115 | * Update the specified resource in storage. 116 | * 117 | * @param \Illuminate\Http\Request $request 118 | * @param int $id 119 | * 120 | * @return \Illuminate\Http\Response 121 | */ 122 | public function update(Request $request, $id) 123 | { 124 | $request->validate(['order' => 'required']); 125 | 126 | $reload = false; 127 | $tagModel = $this->tagModel; 128 | $new_order = $request->order; 129 | 130 | $tag = $tagModel->find($id); 131 | $tag->update([ 132 | 'name' => $request->name, 133 | 'type' => $request->type, 134 | ]); 135 | 136 | if ($new_order != $tag->order_column) { 137 | $reload = true; 138 | $tag->swapOrderWithModel($tagModel->where('order_column', $new_order)->first()); 139 | } 140 | 141 | return response()->json([ 142 | 'msg' => trans('Tagos::messages.model_updated'), 143 | 'tag' => [ 144 | 'count' => $this->relation->get()->where('tag_id', $tag->id)->count(), 145 | 'order' => $tag->order_column, 146 | 'type' => $tag->type, 147 | 'name' => $this->populateLocales($tag->getTranslations('name')), 148 | 'slug' => $this->populateLocales($tag->getTranslations('slug')), 149 | 'id' => $tag->id, 150 | ], 151 | 'reload' => $reload, 152 | ]); 153 | } 154 | 155 | public function updateMulti(Request $request) 156 | { 157 | foreach ($this->tagModel->whereIn('id', $request->ids)->get() as $model) { 158 | $model->update(['type' => $request->type]); 159 | } 160 | 161 | return response()->json([ 162 | 'msg' => trans('Tagos::messages.model_updated'), 163 | ]); 164 | } 165 | 166 | /** 167 | * Remove the specified resource from storage. 168 | * 169 | * @param int $id 170 | * 171 | * @return \Illuminate\Http\Response 172 | */ 173 | public function destroy($id) 174 | { 175 | $this->tagModel->destroy($id); 176 | 177 | return response()->json([ 178 | 'msg' => trans('Tagos::messages.model_deleted'), 179 | ]); 180 | } 181 | 182 | public function destroyMulti(Request $request) 183 | { 184 | $this->tagModel->destroy($request->ids); 185 | 186 | return response()->json([ 187 | 'msg' => trans('Tagos::messages.models_deleted'), 188 | ]); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Observers/TagObserver.php: -------------------------------------------------------------------------------- 1 | cleanData(); 13 | } 14 | 15 | /** 16 | * Listen to the Tag deleted event. 17 | */ 18 | public function deleted() 19 | { 20 | return $this->cleanData(); 21 | } 22 | 23 | /** 24 | * helpers. 25 | * 26 | * @return [type] [description] 27 | */ 28 | protected function cleanData() 29 | { 30 | return app('cache')->forget('tagos'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Tagos.php: -------------------------------------------------------------------------------- 1 | tagClass = app('cache')->get('tagos'); 12 | } 13 | 14 | /** 15 | * resolve all tags. 16 | * 17 | * @return [type] [description] 18 | */ 19 | public function getTags() 20 | { 21 | $res = $this->tagClass->map(function ($item) { 22 | return [ 23 | 'name'=> $item->name, 24 | 'type'=> $item->type, 25 | ]; 26 | }); 27 | 28 | return json_encode($res); 29 | } 30 | 31 | /** 32 | * resolve model tags. 33 | * 34 | * @param mixed $model 35 | * 36 | * @return [type] [description] 37 | */ 38 | public function getModelTags($model) 39 | { 40 | if (!$model->tags->count()) { 41 | return json_encode([]); 42 | } 43 | 44 | $res = $model->tags->map(function ($item) { 45 | return [ 46 | 'name'=> $item->name, 47 | 'type'=> $item->type, 48 | ]; 49 | }); 50 | 51 | return json_encode($res); 52 | } 53 | 54 | /** 55 | * save & sync tags correctly to model. 56 | * 57 | * @param [type] $model [description] 58 | * @param [type] $request [description] 59 | * 60 | * @return [type] [description] 61 | */ 62 | public function saveTags($model, $request) 63 | { 64 | $items = []; 65 | $tags = json_decode($request->tags, true); 66 | 67 | foreach ($tags as $one) { 68 | $items[] = app(config('tags.model'))->findOrCreate($one['name'], $one['type']); 69 | } 70 | 71 | return $model->syncTags($items); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/TagosRoutes.php: -------------------------------------------------------------------------------- 1 | group([ 12 | 'prefix' => 'tags', 13 | 'as' => 'tagos.', 14 | ], function () use ($controller) { 15 | // editor 16 | app('router')->get('editor', "$controller@editor")->name('editor'); 17 | app('router')->post('/', "$controller@store")->name('store'); 18 | // show all 19 | app('router')->get('/', "$controller@index")->name('index'); 20 | app('router')->get('type/{type}', "$controller@indexByType")->name('index_type'); 21 | // update 22 | app('router')->put('{id}/update', "$controller@update")->name('update'); 23 | app('router')->post('update-multi', "$controller@updateMulti")->name('update_multi'); 24 | // delete 25 | app('router')->delete('{id}/destroy', "$controller@destroy")->name('destroy'); 26 | app('router')->post('destroy-multi', "$controller@destroyMulti")->name('destroy_multi'); 27 | // show specific 28 | app('router')->get('{slug}', "$controller@show")->name('show'); 29 | app('router')->get('type/{type}/tag/{slug}', "$controller@showByType")->name('show_type'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/TagosServiceProvider.php: -------------------------------------------------------------------------------- 1 | packagePublish(); 18 | $this->cacheAndObserver(); 19 | $this->command(); 20 | } 21 | 22 | /** 23 | * [packagePublish description]. 24 | * 25 | * @return [type] [description] 26 | */ 27 | protected function packagePublish() 28 | { 29 | // config 30 | $this->publishes([ 31 | __DIR__ . '/config' => config_path(), 32 | ], 'config'); 33 | 34 | // seeds 35 | $this->publishes([ 36 | __DIR__ . '/database/seeds' => database_path('seeds'), 37 | ], 'seeds'); 38 | 39 | // resources 40 | $this->publishes([ 41 | __DIR__ . '/resources/assets' => resource_path('assets/vendor/Tagos'), 42 | ], 'assets'); 43 | 44 | // trans 45 | $this->loadTranslationsFrom(__DIR__ . '/resources/lang', 'Tagos'); 46 | $this->publishes([ 47 | __DIR__ . '/resources/lang' => resource_path('lang/vendor/Tagos'), 48 | ], 'trans'); 49 | 50 | // views 51 | $this->loadViewsFrom(__DIR__ . '/resources/views', 'Tagos'); 52 | $this->publishes([ 53 | __DIR__ . '/resources/views' => resource_path('views/vendor/Tagos'), 54 | ], 'views'); 55 | } 56 | 57 | /** 58 | * model events cacheAndObserver. 59 | * 60 | * @return [type] [description] 61 | */ 62 | protected function cacheAndObserver() 63 | { 64 | $model = $this->app['config']->get('tags.model'); 65 | 66 | if ($model && Schema::hasTable('tags')) { 67 | $this->app['cache']->rememberForever('tagos', function () use ($model) { 68 | return $this->app->make($model)->ordered()->get(); 69 | }); 70 | 71 | $this->app->make($model)->observe(TagObserver::class); 72 | } 73 | } 74 | 75 | /** 76 | * package commands. 77 | * 78 | * @return [type] [description] 79 | */ 80 | protected function command() 81 | { 82 | $this->commands([ 83 | PackageSetup::class, 84 | ]); 85 | } 86 | 87 | /** 88 | * Register any package services. 89 | * 90 | * @return [type] [description] 91 | */ 92 | public function register() 93 | { 94 | $this->app->singleton('tagos', function () { 95 | return new Tagos(); 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Traits/HasTags.php: -------------------------------------------------------------------------------- 1 | '\ctf0\Tagos\Controllers\TagosController', 12 | 13 | /* 14 | * package tag model 15 | * ex."https://github.com/ctf0/Tagos/wiki/Example-Model" 16 | * 17 | * so you can extend the functionality with ease 18 | * for example add revisions 19 | */ 20 | 'model' => Spatie\Tags\Tag::class, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/database/seeds/TagsTableSeeder.php: -------------------------------------------------------------------------------- 1 | create([ 18 | 'name' => $faker->unique()->safeColorName(), 19 | ]); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/resources/assets/js/add.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 71 | 72 | 93 | 94 | 259 | -------------------------------------------------------------------------------- /src/resources/assets/js/index.vue: -------------------------------------------------------------------------------- 1 | 47 | -------------------------------------------------------------------------------- /src/resources/assets/js/manager.js: -------------------------------------------------------------------------------- 1 | /* Libs */ 2 | window.EventHub = require('vuemit') 3 | window.Fuse = require('fuse.js') 4 | 5 | // axios 6 | window.axios = require('axios') 7 | axios.defaults.headers.common = { 8 | 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 9 | 'X-Requested-With': 'XMLHttpRequest' 10 | } 11 | axios.interceptors.response.use( 12 | (response) => response, 13 | (error) => Promise.reject(error.response) 14 | ) 15 | 16 | // vue-tippy 17 | Vue.use(require('vue-tippy'), { 18 | arrow: true, 19 | touchHold: true, 20 | inertia: true, 21 | performance: true, 22 | flipDuration: 0, 23 | popperOptions: { 24 | modifiers: { 25 | preventOverflow: {enabled: false}, 26 | hide: {enabled: false} 27 | } 28 | } 29 | }) 30 | 31 | // vue-awesome 32 | import 'vue-awesome/icons/search' 33 | import 'vue-awesome/icons/times' 34 | import 'vue-awesome/icons/trash' 35 | import 'vue-awesome/icons/anchor' 36 | Vue.component('icon', require('vue-awesome/components/Icon').default) 37 | 38 | /* Components */ 39 | Vue.component('Tagos', require('./add.vue').default) 40 | Vue.component('TagosList', require('./table/list.vue').default) 41 | Vue.component('TagosIndex', require('./index.vue').default) 42 | Vue.component('MyNotification', require('vue-notif').default) 43 | -------------------------------------------------------------------------------- /src/resources/assets/js/mixins/search.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | searchFor: null 5 | } 6 | }, 7 | methods: { 8 | resetSearch() { 9 | this.searchFor = null 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/resources/assets/js/table/item.vue: -------------------------------------------------------------------------------- 1 | 111 | 112 | 224 | -------------------------------------------------------------------------------- /src/resources/assets/js/table/list.vue: -------------------------------------------------------------------------------- 1 | 196 | -------------------------------------------------------------------------------- /src/resources/assets/sass/_checkbox.scss: -------------------------------------------------------------------------------- 1 | // https://codepen.io/andreasstorm/pen/ybPwre 2 | $grey-lighter: #dbdbdb; 3 | $primary: #23d160; 4 | 5 | .cbx-checkbox { 6 | &[type='checkbox'], 7 | &:checked { 8 | display: none; 9 | } 10 | } 11 | 12 | .cbx { 13 | position: relative; 14 | top: 1px; 15 | display: inline-block; 16 | width: 14px; 17 | height: 14px; 18 | margin-right: 6px; 19 | cursor: pointer; 20 | vertical-align: sub; 21 | border: 1px solid $grey-lighter; 22 | border-radius: 3px; 23 | 24 | &::before { 25 | position: absolute; 26 | top: 50%; 27 | left: 50%; 28 | width: 20px; 29 | height: 20px; 30 | margin: -10px 0 0 -10px; 31 | content: ''; 32 | transform: scale(0); 33 | border-radius: 100%; 34 | background: $primary; 35 | } 36 | 37 | &::after { 38 | position: absolute; 39 | top: 5px; 40 | left: 5px; 41 | width: 2px; 42 | height: 2px; 43 | content: ''; 44 | transform: scale(0); 45 | border-radius: 2px; 46 | box-shadow: 0 -18px 0 $primary, 12px -12px 0 $primary, 18px 0 0 $primary, 12px 12px 0 $primary, 0 18px 0 $primary, -12px 12px 0 $primary, -18px 0 0 $primary, -12px -12px 0 $primary; 47 | } 48 | 49 | svg { 50 | position: relative; 51 | top: -3px; 52 | transform: scale(0); 53 | fill: none; 54 | stroke-linecap: round; 55 | stroke-linejoin: round; 56 | 57 | polyline { 58 | stroke: $primary; 59 | stroke-width: 2; 60 | } 61 | } 62 | } 63 | 64 | input:checked + .cbx { 65 | top: 0; 66 | border-color: transparent; 67 | 68 | &::before { 69 | transition: all 0.3s ease; 70 | transform: scale(1); 71 | opacity: 0; 72 | } 73 | 74 | &::after { 75 | transition: all 0.6s ease; 76 | transform: scale(1); 77 | opacity: 0; 78 | } 79 | 80 | svg { 81 | transition: all 0.4s ease; 82 | transition-delay: 0.1s; 83 | transform: scale(1); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/resources/assets/sass/_vue.scss: -------------------------------------------------------------------------------- 1 | $list: (tag-slide-up); 2 | 3 | @each $class in $list { 4 | .#{$class}-move, 5 | .#{$class}-enter-active, 6 | .#{$class}-leave-active { 7 | transition: all 0.3s ease; 8 | } 9 | 10 | .#{$class}-enter, 11 | .#{$class}-leave-to { 12 | opacity: 0; 13 | } 14 | } 15 | 16 | .tag-slide-up-enter, 17 | .tag-slide-up-leave-to { 18 | transform: translateY(-30px); 19 | } 20 | -------------------------------------------------------------------------------- /src/resources/assets/sass/style.scss: -------------------------------------------------------------------------------- 1 | @import './checkbox'; 2 | @import './vue'; 3 | 4 | [v-cloak] { 5 | display: none; 6 | } 7 | 8 | .link { 9 | cursor: pointer; 10 | } 11 | 12 | .m-b-50 { 13 | margin-bottom: 50px; 14 | } 15 | 16 | .m-t-20 { 17 | margin-top: 20px; 18 | } 19 | 20 | // input multi-locale 21 | .input-box { 22 | position: relative; 23 | } 24 | 25 | .toggle-locale { 26 | position: absolute; 27 | z-index: 5; 28 | top: 0; 29 | right: 1px; 30 | opacity: 0.3; 31 | 32 | &:hover { 33 | opacity: 1; 34 | } 35 | 36 | &::after { 37 | display: none !important; 38 | } 39 | 40 | select { 41 | font-weight: bold; 42 | padding: 0.4rem 0.5rem 0.5rem !important; 43 | text-transform: capitalize; 44 | border: none; 45 | background-color: transparent; 46 | } 47 | } 48 | 49 | .toggle-pad { 50 | padding-right: 32px; 51 | } 52 | 53 | // editor 54 | .box { 55 | position: relative; 56 | overflow: hidden; 57 | cursor: pointer; 58 | transition: all 0.3s ease; 59 | box-shadow: 0 6px 20px rgba(black, 0.08); 60 | 61 | &:hover { 62 | transform: translateY(-0.7rem); 63 | box-shadow: 0 15px 30px 0 rgba(black, 0.11), 0 5px 15px 0 rgba(black, 0.08); 64 | } 65 | 66 | &::before { 67 | font-size: 8rem; 68 | position: absolute; 69 | top: 50%; 70 | right: -5%; 71 | content: attr(data-order); 72 | transform: translateY(-50%); 73 | color: rgba(black, 0.05); 74 | } 75 | } 76 | 77 | .tag-ops { 78 | width: 100%; 79 | text-align: right; 80 | } 81 | 82 | .tag-list { 83 | margin: 0; 84 | } 85 | 86 | .tag-item { 87 | position: relative; 88 | 89 | &:not(:first-of-type) { 90 | padding-top: 5px; 91 | } 92 | 93 | &::before { 94 | position: absolute; 95 | top: 0.5rem; 96 | right: 0.5rem; 97 | content: attr(data-lang); 98 | cursor: default; 99 | text-transform: capitalize; 100 | opacity: 0.5; 101 | } 102 | } 103 | 104 | .tag-input { 105 | font-size: 1rem; 106 | line-height: 1; 107 | width: calc(100% - 0.7rem); 108 | height: 100%; 109 | margin-left: 0.7rem; 110 | padding: 0.5rem; 111 | border: none; 112 | border-left: 5px solid #edf2f3; 113 | background-color: #f9fbfb; 114 | 115 | &:focus, 116 | &:active, 117 | &:hover { 118 | border-left-color: darken(#edf2f3, 20%); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | 'Add new', 5 | 'count' => 'Count', 6 | 'delete' => 'Delete', 7 | 'delete_selected' => 'Delete Selected', 8 | 'find' => 'Find', 9 | 'model_created' => 'Model Created !', 10 | 'model_deleted' => 'Model Deleted !', 11 | 'model_updated' => 'Model Updated !', 12 | 'models_deleted' => 'Models Deleted !', 13 | 'models_updated' => 'Models Updated !', 14 | 'name' => 'Name', 15 | 'no_entries' => 'No Data To Display', 16 | 'no_val' => 'Maybe You Should Add Something First ?!!', 17 | 'ops' => 'Operations', 18 | 'order' => 'Order', 19 | 'select_all' => 'Select All', 20 | 'select_non' => 'Select Non', 21 | 'slug' => 'Slug', 22 | 'tag_exist' => 'Tag Already Exist', 23 | 'tag_ph' => 'tag name', 24 | 'tags' => 'Tags', 25 | 'type' => 'Type', 26 | 'type_ph' => 'type name', 27 | 'update' => 'Update', 28 | 'clear_list' => 'Clear Tags List', 29 | ); -------------------------------------------------------------------------------- /src/resources/views/editor.blade.php: -------------------------------------------------------------------------------- 1 | @extends('Tagos::partials.shared') 2 | @section('title', trans('Tagos::messages.tags')) 3 | 4 | @section('content') 5 | 18 |
19 |
20 | {{-- count --}} 21 |
22 |

23 | {{ trans('Tagos::messages.tags') }} "@{{ itemsCount }}" 24 |

25 |
26 | 27 | {{-- search --}} 28 |
29 |
30 | {{-- type --}} 31 |

32 | 33 | 36 | 37 |

38 | {{-- input --}} 39 |

40 | 45 | 46 |

47 | {{-- clear --}} 48 |

49 | 54 |

55 |
56 |
57 |
58 | 59 | {{-- update multi --}} 60 |
61 |
62 |
63 | {{-- delete multi --}} 64 |
65 |
68 | 73 |
74 |
75 | 76 | {{-- select multi --}} 77 |
78 |
79 | 80 | 81 |
82 |
83 |
84 |
85 | 86 | {{-- update --}} 87 |
88 |
89 |
90 |
91 | 92 |
93 |
94 | 99 |
100 |
101 |
102 |
103 |
104 | 105 | {{-- add new --}} 106 |
107 |
108 |
109 | {{-- name --}} 110 |
111 |
112 | 117 |
118 | @foreach($locales as $code) 119 | 125 | @endforeach 126 | @if($errors->has('name')) 127 |

128 | {{ $errors->first('name') }} 129 |

130 | @endif 131 |
132 | {{-- type --}} 133 |
134 | 135 |
136 | {{-- submit --}} 137 |
138 | 139 |
140 |
141 |
142 |
143 |
144 |
145 | 146 | {{-- list --}} 147 |
148 | 157 | 158 |
159 |
160 | @endsection 161 | -------------------------------------------------------------------------------- /src/resources/views/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('Tagos::partials.shared') 2 | 3 | @section('content') 4 | 5 |
6 | {{-- search --}} 7 |
8 | {{-- input --}} 9 |

10 | 14 | 15 |

16 | {{-- clear --}} 17 |

18 | 23 |

24 |
25 | 26 | {{-- list --}} 27 |
28 | @foreach($tags as $tag) 29 | 30 |
31 | @if($tag->type) 32 | @if(isset($showType) && $showType == true) 33 | 34 | {{ $tag->name }} 35 | {{ $tag->type }} 36 | 37 | @else 38 | {{ $tag->name }} 39 | @endif 40 | @else 41 | {{ $tag->name }} 42 | @endif 43 |
44 |
45 | @endforeach 46 |
47 |
48 |
49 | @endsection 50 | -------------------------------------------------------------------------------- /src/resources/views/partials/add.blade.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/resources/views/partials/display.blade.php: -------------------------------------------------------------------------------- 1 | @if(count($tags)) 2 |
3 | @foreach($tags as $tag) 4 |
5 | @if($tag->type) 6 | @if(isset($showType) && $showType == true) 7 | 8 | {{ $tag->name }} 9 | {{ $tag->type }} 10 | 11 | @else 12 | {{ $tag->name }} 13 | @endif 14 | @else 15 | {{ $tag->name }} 16 | @endif 17 |
18 | @endforeach 19 |
20 | @endif 21 | -------------------------------------------------------------------------------- /src/resources/views/partials/shared.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @yield('title', '') 9 | 10 | {{-- styles --}} 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | {{-- notif --}} 19 |
20 | 21 |
22 | 23 | {{-- Body --}} 24 |
25 |
26 |
27 | @yield('content') 28 |
29 |
30 |
31 |
32 | 33 | {{-- app --}} 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/resources/views/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('Tagos::partials.shared') 2 | 3 | @section('content') 4 |
5 | @foreach($models as $model => $value) 6 | @foreach($value as $item) 7 |
8 |
9 | 14 |
15 | {{-- date --}} 16 |

{{ $item->created_at->format('F d, Y') }}

17 | 18 |
19 | {{-- title --}} 20 |

{{ $item->title }}

21 |
22 | {{-- tags --}} 23 | @include('Tagos::partials.display', ['tags' => $one->tags]) 24 |
25 |
26 |
27 |
28 | @endforeach 29 | @endforeach 30 |
31 | @endsection --------------------------------------------------------------------------------