├── .babelrc ├── .eslintrc ├── .prettierrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── composer.json ├── config └── froala-field.php ├── database └── migrations │ └── create_froala_attachment_tables.php.stub ├── dist ├── css │ ├── field.css │ ├── froala_styles.min.css │ └── plugins │ │ └── tui-image-editor │ │ ├── tui-color-picker.min.css │ │ └── tui-image-editor.min.css ├── js │ ├── field.js │ └── plugins │ │ └── tui-image-editor │ │ ├── fabric.js │ │ ├── fabric.min.js │ │ ├── tui-code-snippet.min.js │ │ └── tui-image-editor.min.js ├── mix-manifest.json └── vendor │ └── nova │ └── froala │ ├── embedly.min.js │ ├── image_tui.min.js │ └── spell_checker.min.js ├── package.json ├── resources ├── js │ ├── FroalaAttachmentsAdapter.js │ ├── MediaConfigurator.js │ ├── PluginsLoader.js │ ├── TrixAttachmentsAdapter.js │ ├── components │ │ ├── DetailField.vue │ │ ├── FormField.vue │ │ └── IndexField.vue │ └── field.js └── sass │ └── field.scss ├── routes └── api.php ├── src ├── Froala.php ├── FroalaFieldServiceProvider.php ├── FroalaPlugins.php ├── FroalaPluginsManager.php ├── Handlers │ ├── AttachedImagesList.php │ ├── DeleteAttachments.php │ ├── DetachAttachment.php │ ├── DiscardPendingAttachments.php │ └── StorePendingAttachment.php ├── Http │ └── Controllers │ │ ├── FroalaImageManagerController.php │ │ ├── FroalaToTrixAttachmentAdapterController.php │ │ └── FroalaUploadController.php ├── Jobs │ └── PruneStaleAttachments.php ├── Models │ ├── Attachment.php │ └── PendingAttachment.php └── helpers.php └── webpack.mix.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["babel-plugin-syntax-dynamic-import"] 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:vue/recommended", 5 | "@vue/prettier" 6 | ], 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "globals": { 11 | "Nova": true, 12 | "_": true 13 | }, 14 | "env": { 15 | "browser": true, 16 | "node": true 17 | }, 18 | "rules": {} 19 | } 20 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `froala/nova-froala-field` will be documented in this file. 4 | 5 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 6 | 7 | ## 3.4.0 - 2021-01-18 8 | 9 | - Froala updated to 3.2.5-1 version 10 | 11 | ## 3.3.1 - 2020-11-17 12 | 13 | - Improve image detection in images list 14 | 15 | ## 3.3.0 - 2020-03-18 16 | 17 | - Updated Froala version to 3.1.1 18 | - Fixed Tui Image Editor plugin import 19 | 20 | ## 3.2.3 - 2020-02-17 21 | 22 | - Fixed files storing consider to new `withFiles($disk, $path)` method signature and `StorableContract` 23 | 24 | ## 3.2.2 - 2019-12-12 25 | 26 | - Removed unnecessary JS bundle size 27 | 28 | ## 3.2.1 - 2019-11-16 29 | 30 | - Updated `Froala::withFiles` method signature according to latest Nova update 31 | 32 | ## 3.2.0 - 2019-09-07 33 | 34 | - Laravel 6.0 support 35 | 36 | ## 3.1.0 - 2019-07-26 37 | 38 | - Nova 2.1 compatibility 39 | - Froala updated to 3.0.5 version 40 | 41 | ## 3.0.1 - 2019-07-04 42 | 43 | - Fixed plugins usage detection 44 | 45 | ## 3.0.0 - 2019-07-03 46 | 47 | - Upgraded Froala to **3.0.1** version! 48 | - Fixed an error when no toolbarButtons provided 49 | - Font Awesome 5 support 50 | - TUI Advanced Image Editor support 51 | 52 | ## 2.2.1 - 2019-06-10 53 | 54 | - Fixed dynamic import of 3rd party plugins 55 | - Downgraded laravel-mix to `^1.0` for providing better support with Laravel Nova 56 | 57 | ## 2.2.0 - 2019-05-22 58 | 59 | - Fixed issue with image manager caused by latest Nova update 60 | - Updated node dependencies and retranspiled assets 61 | - Froala version updated to 2.9.5 62 | 63 | ## 2.1.0 - 2019-04-25 64 | 65 | Ability to use Nova local installation 66 | 67 | ## 2.0.0 - 2019-03-11 68 | 69 | - add support for Laravel 5.8 and Nova 2.0 70 | - _Froala_ version update 71 | 72 | ## 1.1.4 - 2019-02-01 73 | 74 | Added missing return when custom fill callback called 75 | 76 | ## 1.1.3 - 2019-01-31 77 | 78 | Improved creating attachments on resource creation, 79 | according to _Trix_ fix in **Nova v1.1.5**. Now you can use the `trix` driver without any issues. 80 | 81 | ## 1.1.2 - 2019-01-12 82 | 83 | - Updated Froala version to 2.9.1 84 | - Improved get images list 85 | - Readme updated 86 | 87 | ## 1.1.1 - 2018-12-04 88 | 89 | - Prevented jump to global search on pressing "/" 90 | 91 | ## 1.1.0 - 2018-11-02 92 | 93 | - Updated to latest **froala-editor@2.9.0** version. 94 | 95 | ## 1.0.1 - 2018-10-19 96 | 97 | - Added minimum required dependencies versions 98 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **Create feature branches** - Don't ask us to pull from your master branch. 52 | 53 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 54 | 55 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 56 | 57 | 58 | ## Running Tests 59 | 60 | ``` bash 61 | $ composer test 62 | ``` 63 | 64 | 65 | **Happy coding**! 66 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Froala 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Nova Froala Field

2 | 3 |

Froala WYSIWYG Editor field for Laravel Nova

4 | 5 |

6 | Latest Version on Packagist 7 | Build Status 8 | Code Style Status 9 | Total Downloads 10 |

11 | 12 | ## Introduction 13 | 14 | ### Froala WYSIWYG Editor Field 15 | 16 | Full support of attaching Images, Files and Videos 17 | 18 | ![Form Field](docs/form-field.png) 19 | 20 | Notifications for _Froala_ events are handled by [Toasted](https://nova.laravel.com/docs/1.0/customization/frontend.html#notifications) which is provided in _Nova_ by default. 21 | 22 | ## Upgrading 23 | 24 | For upgrading to **Froala 3**, check out – [Upgrading Instructions](UPGRADING.md). 25 | 26 | ## Installation 27 | 28 | You can install the package into a Laravel application that uses [Nova](https://nova.laravel.com) via composer: 29 | 30 | ```bash 31 | composer require froala/nova-froala-field 32 | ``` 33 | 34 | ## Usage 35 | 36 | Just use the `Froala\NovaFroalaField\Froala` field in your Nova resource: 37 | 38 | ```php 39 | namespace App\Nova; 40 | 41 | use Froala\NovaFroalaField\Froala; 42 | 43 | class Article extends Resource 44 | { 45 | // ... 46 | 47 | public function fields(Request $request) 48 | { 49 | return [ 50 | // ... 51 | 52 | Froala::make('Content'), 53 | 54 | // ... 55 | ]; 56 | } 57 | } 58 | ``` 59 | 60 | ## Override Config Values 61 | 62 | To change any of config values for _froala field_, publish a config file: 63 | 64 | ```bash 65 | php artisan vendor:publish --tag=config --provider=Froala\\NovaFroalaField\\FroalaFieldServiceProvider 66 | ``` 67 | 68 | ## Customize Editor Options 69 | 70 | For changing any [Available Froala Option](https://www.froala.com/wysiwyg-editor/docs/options) 71 | edit `nova.froala-field.options` value: 72 | 73 | ```php 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Default Editor Options 77 | |-------------------------------------------------------------------------- 78 | | 79 | | Setup default values for any Froala editor option. 80 | | 81 | | To view a list of all available options check out the Froala documentation 82 | | {@link https://www.froala.com/wysiwyg-editor/docs/options} 83 | | 84 | */ 85 | 86 | 'options' => [ 87 | 'toolbarButtons' => [ 88 | [ 89 | 'bold', 90 | 'italic', 91 | 'underline', 92 | ], 93 | [ 94 | 'formatOL', 95 | 'formatUL', 96 | ], 97 | [ 98 | 'insertImage', 99 | 'insertFile', 100 | 'insertLink', 101 | 'insertVideo', 102 | ], 103 | [ 104 | 'embedly', 105 | 'html', 106 | ], 107 | ], 108 | ], 109 | 110 | //... 111 | ``` 112 | 113 | If you want to set options only to specific field, just pass them to `options` method: 114 | 115 | ```php 116 | public function fields(Request $request) 117 | { 118 | return [ 119 | // ... 120 | 121 | Froala::make('Content')->options([ 122 | 'editorClass' => 'custom-class', 123 | 'height' => 300, 124 | ]), 125 | 126 | // ... 127 | ]; 128 | } 129 | ``` 130 | 131 | ## Attachments 132 | 133 | **Nova Froala Field** provides native attachments driver which works similar to [Trix File Uploads](https://nova.laravel.com/docs/1.0/resources/fields.html#file-uploads), but with ability to optimize images and preserve file names. 134 | Also you have an ability to switch to the `trix` driver to use its upload system. 135 | 136 | * It's Recommended to use `froala` driver (enabled by default) to be able to use current and future 137 | additional features for attachments, provided by *Froala*. 138 | 139 | ### Froala Driver 140 | 141 | To use `froala` driver, publish and run a migration: 142 | 143 | ```bash 144 | php artisan vendor:publish --tag=migrations --provider=Froala\\NovaFroalaField\\FroalaFieldServiceProvider 145 | php artisan migrate 146 | ``` 147 | 148 | ### Trix Driver 149 | 150 | If previously you have used *Trix* attachments and you want to preserve behavior with same tables and handlers 151 | you can use `trix` driver in config file: 152 | 153 | ```php 154 | /* 155 | |-------------------------------------------------------------------------- 156 | | Editor Attachments Driver 157 | |-------------------------------------------------------------------------- 158 | | 159 | | If you have used `Trix` previously and want to save the same flow with 160 | | `Trix` attachments handlers and database tables you can use 161 | | "trix" driver. 162 | | 163 | | *** Note that "trix" driver doesn't support image optimization 164 | | and file names preservation. 165 | | 166 | | It is recommended to use "froala" driver to be able to automatically 167 | | optimize uploaded images and preserve attachments file names. 168 | | 169 | | Supported: "froala", "trix" 170 | | 171 | */ 172 | 173 | 'attachments_driver' => 'trix' 174 | 175 | //... 176 | ``` 177 | 178 | ### Attachments Usage 179 | 180 | To allow users to upload images, files and videos, just like with _Trix_ field, chain the `withFiles` method onto the field's definition. When calling the `withFiles` method, you should pass the name of the filesystem disk that photos should be stored on: 181 | 182 | ```php 183 | use Froala\NovaFroalaField\Froala; 184 | 185 | Froala::make('Content')->withFiles('public'); 186 | ``` 187 | 188 | And also, in your `app/Console/Kernel.php` file, you should register a [daily job](https://laravel.com/docs/5.7/scheduling) to prune any stale attachments from the pending attachments table and storage: 189 | 190 | ```php 191 | use Froala\NovaFroalaField\Jobs\PruneStaleAttachments; 192 | 193 | 194 | /** 195 | * Define the application's command schedule. 196 | * 197 | * @param \Illuminate\Console\Scheduling\Schedule $schedule 198 | * @return void 199 | */ 200 | protected function schedule(Schedule $schedule) 201 | { 202 | $schedule->call(function () { 203 | (new PruneStaleAttachments)(); 204 | })->daily(); 205 | } 206 | ``` 207 | 208 | #### Filenames Preservation 209 | 210 | A unique ID is generated by default to serve as the file name according to `store` [method specification](https://laravel.com/docs/master/filesystem#file-uploads). 211 | If you want to preserve original client filenames for uploaded attachments, change `preserve_file_names` option in config file to `true`. 212 | 213 | ```php 214 | /* 215 | |-------------------------------------------------------------------------- 216 | | Preserve Attachments File Name 217 | |-------------------------------------------------------------------------- 218 | | 219 | | Ability to preserve client original file name for uploaded 220 | | image, file or video. 221 | | 222 | */ 223 | 224 | 'preserve_file_names' => true, 225 | 226 | //... 227 | ``` 228 | 229 | #### Images Optimization 230 | 231 | All uploaded images will be optimized by default by [spatie/image-optimizer](https://github.com/spatie/image-optimizer). 232 | 233 | You can disable image optimization in config file: 234 | 235 | ```php 236 | /* 237 | |-------------------------------------------------------------------------- 238 | | Automatically Images Optimization 239 | |-------------------------------------------------------------------------- 240 | | 241 | | Optimize all uploaded images by default. 242 | | 243 | */ 244 | 245 | 'optimize_images' => false, 246 | 247 | //... 248 | ``` 249 | 250 | Or set custom optimization options for any optimizer: 251 | 252 | ```php 253 | /* 254 | |-------------------------------------------------------------------------- 255 | | Image Optimizers Setup 256 | |-------------------------------------------------------------------------- 257 | | 258 | | These are the optimizers that will be used by default. 259 | | You can setup custom parameters for each optimizer. 260 | | 261 | */ 262 | 263 | 'image_optimizers' => [ 264 | Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ 265 | '-m85', // this will store the image with 85% quality. This setting seems to satisfy Google's Pagespeed compression rules 266 | '--strip-all', // this strips out all text information such as comments and EXIF data 267 | '--all-progressive', // this will make sure the resulting image is a progressive one 268 | ], 269 | Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ 270 | '--force', // required parameter for this package 271 | ], 272 | Spatie\ImageOptimizer\Optimizers\Optipng::class => [ 273 | '-i0', // this will result in a non-interlaced, progressive scanned image 274 | '-o2', // this set the optimization level to two (multiple IDAT compression trials) 275 | '-quiet', // required parameter for this package 276 | ], 277 | Spatie\ImageOptimizer\Optimizers\Svgo::class => [ 278 | '--disable=cleanupIDs', // disabling because it is known to cause troubles 279 | ], 280 | Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ 281 | '-b', // required parameter for this package 282 | '-O3', // this produces the slowest but best results 283 | ], 284 | ], 285 | ``` 286 | 287 | > Image optimization currently supported only for local filesystems 288 | 289 | ### Upload Max Filesize 290 | 291 | You can set max upload filesize for attachments. If set to `null`, max upload filesize equals to _php.ini_ `upload_max_filesize` directive value. 292 | 293 | ```php 294 | /* 295 | |-------------------------------------------------------------------------- 296 | | Maximum Possible Size for Uploaded Files 297 | |-------------------------------------------------------------------------- 298 | | 299 | | Customize max upload filesize for uploaded attachments. 300 | | By default it is set to "null", it means that default value is 301 | | retrieved from `upload_max_size` directive of php.ini file. 302 | | 303 | | Format is the same as for `uploaded_max_size` directive. 304 | | Check out FAQ page, to get more detail description. 305 | | {@link http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes} 306 | | 307 | */ 308 | 309 | 'upload_max_filesize' => null, 310 | 311 | //... 312 | ``` 313 | 314 | ## Display Edited Content 315 | 316 | According to _Froala_ [Display Edited Content](https://www.froala.com/wysiwyg-editor/docs/overview#frontend) documentation you should publish _Froala_ styles: 317 | 318 | ```bash 319 | php artisan vendor:publish --tag=froala-styles --provider=Froala\\NovaFroalaField\\FroalaFieldServiceProvider 320 | ``` 321 | 322 | include into view where an edited content is shown: 323 | 324 | ```blade 325 | 326 | 327 | ``` 328 | 329 | Also, you should make sure that you put the edited content inside an element that has the `.fr-view` class: 330 | 331 | ```html 332 |
333 | {!! $article->content !!} 334 |
335 | ``` 336 | 337 | ## Show on Index Page 338 | 339 | You have an ability to show field content on resource index page in popup window: 340 | 341 | ```php 342 | use Froala/NovaFroalaField/Froala; 343 | 344 | Froala::make('Content')->showOnIndex(); 345 | ``` 346 | 347 | Just click **Show Content** 348 | 349 | ![Index Field](docs/index-field.png) 350 | 351 | ## License Key 352 | 353 | To setup your license key, uncomment `key` option in the config file and set `FROALA_KEY` environment variable 354 | 355 | ```php 356 | // ... 357 | 'options' => [ 358 | 'key' => env('FROALA_KEY'), 359 | // ... 360 | ], 361 | ``` 362 | 363 | ## 3rd Party Integrations 364 | 365 | To enable a button that uses some a 3rd party service and needs additional script including, like: *Embed.ly*, *TUI Advanced Image Editor* or *SCAYT Web SpellChecker*, you should publish 3rd party scripts: 366 | 367 | ```bash 368 | php artisan vendor:publish --tag=nova-froala-field-plugins --provider=Froala\\NovaFroalaField\\FroalaFieldServiceProvider 369 | ``` 370 | 371 | Script will be dynamically imported when you enable `embedly` or `spellChecker` buttons. 372 | 373 | ### TUI Advanced Image Editor 374 | 375 | If you want to use _TUI Image Editor_ to add advanced image editing options, switch `tuiEnable` option to `true`: 376 | 377 | ```php 378 | 'options' => [ 379 | // 'key' => env('FROALA_KEY'), 380 | 381 | // 'tuiEnable' => true, 382 | 383 | //... 384 | ], 385 | ``` 386 | 387 | ### Font Awesome 5 388 | 389 | If you have a [Font Awesome Pro license](https://fontawesome.com), you can enable using the regular icons instead of 390 | the solid ones by using the iconsTemplate option. 391 | 392 | Add `iconsTemplate` config value into `froala-field.php` config: 393 | 394 | ```php 395 | 'options' => [ 396 | // 'key' => env('FROALA_KEY'), 397 | 398 | 'iconsTemplate' => 'font_awesome_5', 399 | // If you want to use the regular/light icons, change the template to the following. 400 | // iconsTemplate: 'font_awesome_5r' 401 | // iconsTemplate: 'font_awesome_5l' 402 | 403 | //... 404 | ], 405 | ``` 406 | 407 | **Note**: 408 | 409 | > If you have any problems with loading 3rd party plugins, try to republish it 410 | 411 | ```bash 412 | php artisan vendor:publish --tag=nova-froala-field-plugins --force 413 | ``` 414 | 415 | ## Advanced 416 | 417 | ### Custom Event Handlers 418 | 419 | If you want to setup custom event handlers for froala editor instance, create js file and assign `events` property to `window.froala`: 420 | 421 | ```javascript 422 | window.froala = { 423 | events: { 424 | 'image.error': (error, response) => {}, 425 | 'imageManager.error': (error, response) => {}, 426 | 'file.error': (error, response) => {}, 427 | } 428 | }; 429 | ``` 430 | 431 | to all callbacks provided in `window.froala.events`, the context of _VueJS_ form field component is automatically applied, you can work with `this` inside callbacks like with _Vue_ instance component. 432 | 433 | After that, load the js file into _Nova_ scripts in `NovaServiceProvider::boot` method: 434 | 435 | ```php 436 | public function boot() 437 | { 438 | parent::boot(); 439 | 440 | Nova::serving(function (ServingNova $event) { 441 | Nova::script('froala-event-handlers', public_path('path/to/js/file.js')); 442 | }); 443 | } 444 | ``` 445 | 446 | ### Customize Attachment Handlers 447 | 448 | You can change any of attachment handlers by passing a `callable`: 449 | 450 | ```php 451 | use App\Nova\Handlers\{ 452 | StorePendingAttachment, 453 | DetachAttachment, 454 | DeleteAttachments, 455 | DiscardPendingAttachments, 456 | AttachedImagesList 457 | }; 458 | 459 | Froala::make('Content') 460 | ->attach(new StorePendingAttachment) 461 | ->detach(new DetachAttachment) 462 | ->delete(new DeleteAttachments) 463 | ->discard(new DiscardPendingAttachments) 464 | ->images(new AttachedImagesList) 465 | ``` 466 | 467 | ## Testing 468 | 469 | ``` bash 470 | composer test 471 | ``` 472 | 473 | ## Changelog 474 | 475 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 476 | 477 | ## Contributing 478 | 479 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 480 | 481 | ## Security 482 | 483 | If you discover any security related issues, please email support@froala.com instead of using the issue tracker. 484 | 485 | ## Credits 486 | 487 | - [Slava Razum](https://github.com/slavarazum) 488 | - [All Contributors][link-contributors] 489 | 490 | ## License 491 | 492 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 493 | 494 | [link-contributors]: ../../contributors 495 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading 2 | 3 | Upgrade guide for major versions which have breaking changes. 4 | 5 | ## 3.0 6 | 7 | ### Required 8 | 9 | Only one step required for upgrading to _Froala 3_: 10 | 11 | ```bash 12 | composer require froala/nova-froala-field 13 | ``` 14 | 15 | Done! 16 | 17 | Font Awesome has been removed in 3.0 as required dependency. 18 | So, you can delete your `public/vendor/nova/fonts/font-awesome` directory. 19 | If you want to use _Font Awesome 5_, please read the [instruction](README.md#font-awesome-5). 20 | 21 | ### Additional 22 | 23 | 1. Toolbar Buttons configuration format has been changed in 3.0 version. 24 | You can check new format in the lateset config file version: [config file](https://github.com/froala/nova-froala-field/blob/master/config/froala-field.php) 25 | 2. If you use any 3rd party plugins such as: Embed.ly, SCAYT Web SpellChecker... make force republish: 26 | ```bash 27 | php artisan vendor:publish --tag=nova-froala-field-plugins --force 28 | ``` 29 | 3. If you use _Aviary_ plugin, it's currently not supported in 3.0 version, instead of _Aviary_, 30 | you can use _TUI Advanced Image Editor_, check out the [instruction](README.md#tui-advanced-image-editor) 31 | 4. If you previously setup _Custom Event Handlers_, in 3.0 version api has been changed, 32 | check out the new version [here](README.md#custom-event-handlers) 33 | 34 | **Note**: 35 | If you have made any advanced customizations, please check official upgrade guide - [https://www.froala.com/wysiwyg-editor/docs/migrate-from-v2](https://www.froala.com/wysiwyg-editor/docs/migrate-from-v2). 36 | 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "froala/nova-froala-field", 3 | "description": "A Laravel Nova Froala WYSIWYG Editor Field.", 4 | "keywords": [ 5 | "laravel", 6 | "nova", 7 | "field", 8 | "wysiwyg", 9 | "froala", 10 | "editor" 11 | ], 12 | "homepage": "https://github.com/froala/nova-froala-field", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Slava Razum", 17 | "email": "modern.web.artisan@gmail.com", 18 | "role": "Developer" 19 | } 20 | ], 21 | "repositories": [ 22 | { 23 | "type": "composer", 24 | "url": "https://nova.laravel.com" 25 | } 26 | ], 27 | "require": { 28 | "php": ">=7.1.3", 29 | "laravel/nova": "*", 30 | "league/flysystem": "^1.0.8", 31 | "spatie/image-optimizer": "^1.1" 32 | }, 33 | "require-dev": { 34 | "mockery/mockery": "^1.1", 35 | "orchestra/testbench": "^3.8|^4.0", 36 | "phpunit/phpunit": "^7.0|^8.0" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Froala\\NovaFroalaField\\": "src/" 41 | }, 42 | "files": [ 43 | "src/helpers.php" 44 | ] 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Froala\\NovaFroalaField\\Tests\\": "tests" 49 | } 50 | }, 51 | "scripts": { 52 | "test": "phpunit --colors=always", 53 | "test-coverage": "phpunit --colors=always --coverage-html coverage" 54 | }, 55 | "extra": { 56 | "laravel": { 57 | "providers": [ 58 | "Froala\\NovaFroalaField\\FroalaFieldServiceProvider" 59 | ] 60 | } 61 | }, 62 | "config": { 63 | "sort-packages": true 64 | }, 65 | "minimum-stability": "dev", 66 | "prefer-stable": true 67 | } 68 | -------------------------------------------------------------------------------- /config/froala-field.php: -------------------------------------------------------------------------------- 1 | [ 17 | //'key' => env('FROALA_KEY'), 18 | //'iconsTemplate' => 'font_awesome_5', 19 | //'tuiEnable' => true, 20 | 21 | 'toolbarButtons' => [ 22 | [ 23 | 'bold', 24 | 'italic', 25 | 'underline', 26 | ], 27 | [ 28 | 'formatOL', 29 | 'formatUL', 30 | ], 31 | [ 32 | 'insertImage', 33 | 'insertFile', 34 | 'insertLink', 35 | 'insertVideo', 36 | ], 37 | [ 38 | 'html', 39 | ], 40 | ], 41 | ], 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Editor Attachments Driver 46 | |-------------------------------------------------------------------------- 47 | | 48 | | If you have used `Trix` previously and want to save the same flow with 49 | | `Trix` attachments handlers and database tables you can use 50 | | "trix" driver. 51 | | 52 | | *** Note that "trix" driver doesn't support image optimization 53 | | and file names preservation. 54 | | 55 | | It is recommended to use "froala" driver to be able to automatically 56 | | optimize uploaded images and preserve attachments file names. 57 | | 58 | | Supported: "froala", "trix" 59 | | 60 | */ 61 | 62 | 'attachments_driver' => 'froala', 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Preserve Attachments File Name 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Ability to preserve client original file name for uploaded 70 | | image, file or video. 71 | | 72 | */ 73 | 74 | 'preserve_file_names' => false, 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Maximum Possible Size for Uploaded Files 79 | |-------------------------------------------------------------------------- 80 | | 81 | | Customize max upload filesize for uploaded attachments. 82 | | By default it is set to "null", it means that default value is 83 | | retrieved from `upload_max_size` directive of php.ini file. 84 | | 85 | | Format is the same as for `uploaded_max_size` directive. 86 | | Check out FAQ page, to get more detail description. 87 | | {@link http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes} 88 | | 89 | */ 90 | 91 | 'upload_max_filesize' => null, 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Automatically Images Optimization 96 | |-------------------------------------------------------------------------- 97 | | 98 | | Optimize all uploaded images by default. 99 | | 100 | | * Currently not supported for cloud filesystems 101 | | 102 | */ 103 | 104 | 'optimize_images' => true, 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Image Optimizers Setup 109 | |-------------------------------------------------------------------------- 110 | | 111 | | These are the optimizers that will be used by default. 112 | | You could setup custom parameters for each optimizer. 113 | | 114 | */ 115 | 116 | 'image_optimizers' => [ 117 | Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ 118 | '-m85', // this will store the image with 85% quality. This setting seems to satisfy Google's Pagespeed compression rules 119 | '--strip-all', // this strips out all text information such as comments and EXIF data 120 | '--all-progressive', // this will make sure the resulting image is a progressive one 121 | ], 122 | Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ 123 | '--force', // required parameter for this package 124 | ], 125 | Spatie\ImageOptimizer\Optimizers\Optipng::class => [ 126 | '-i0', // this will result in a non-interlaced, progressive scanned image 127 | '-o2', // this set the optimization level to two (multiple IDAT compression trials) 128 | '-quiet', // required parameter for this package 129 | ], 130 | Spatie\ImageOptimizer\Optimizers\Svgo::class => [ 131 | '--disable=cleanupIDs', // disabling because it is known to cause troubles 132 | ], 133 | Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ 134 | '-b', // required parameter for this package 135 | '-O3', // this produces the slowest but best results 136 | ], 137 | ], 138 | ]; 139 | -------------------------------------------------------------------------------- /database/migrations/create_froala_attachment_tables.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('draft_id')->index(); 19 | $table->string('attachment'); 20 | $table->string('disk'); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::create('nova_froala_attachments', function (Blueprint $table) { 25 | $table->increments('id'); 26 | $table->morphs('attachable'); 27 | $table->string('attachment'); 28 | $table->string('disk'); 29 | $table->string('url')->index(); 30 | $table->timestamps(); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::drop('nova_pending_froala_attachments'); 42 | Schema::drop('nova_froala_attachments'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dist/css/froala_styles.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * froala_editor v3.2.5-1 (https://www.froala.com/wysiwyg-editor) 3 | * License https://froala.com/wysiwyg-editor/terms/ 4 | * Copyright 2014-2021 Froala Labs 5 | */ 6 | 7 | .fr-clearfix::after{clear:both;display:block;content:"";height:0}.fr-hide-by-clipping{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.fr-view img.fr-rounded,.fr-view .fr-img-caption.fr-rounded img{border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box}.fr-view img.fr-shadow,.fr-view .fr-img-caption.fr-shadow img{-webkit-box-shadow:10px 10px 5px 0px #cccccc;-moz-box-shadow:10px 10px 5px 0px #cccccc;box-shadow:10px 10px 5px 0px #cccccc}.fr-view img.fr-bordered,.fr-view .fr-img-caption.fr-bordered img{border:solid 5px #CCC}.fr-view img.fr-bordered{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fr-view .fr-img-caption.fr-bordered img{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.fr-view{word-wrap:break-word}.fr-view span[style~="color:"] a{color:inherit}.fr-view strong{font-weight:700}.fr-view table{border:none;border-collapse:collapse;empty-cells:show;max-width:100%}.fr-view table td{min-width:5px}.fr-view table.fr-dashed-borders td,.fr-view table.fr-dashed-borders th{border-style:dashed}.fr-view table.fr-alternate-rows tbody tr:nth-child(2n){background:whitesmoke}.fr-view table td,.fr-view table th{border:1px solid #DDD}.fr-view table td:empty,.fr-view table th:empty{height:20px}.fr-view table td.fr-highlighted,.fr-view table th.fr-highlighted{border:1px double red}.fr-view table td.fr-thick,.fr-view table th.fr-thick{border-width:2px}.fr-view table th{background:#ececec}.fr-view hr{clear:both;user-select:none;-o-user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;break-after:always;page-break-after:always}.fr-view .fr-file{position:relative}.fr-view .fr-file::after{position:relative;content:"\1F4CE";font-weight:normal}.fr-view pre{white-space:pre-wrap;word-wrap:break-word;overflow:visible}.fr-view[dir="rtl"] blockquote{border-left:none;border-right:solid 2px #5E35B1;margin-right:0;padding-right:5px;padding-left:0}.fr-view[dir="rtl"] blockquote blockquote{border-color:#00BCD4}.fr-view[dir="rtl"] blockquote blockquote blockquote{border-color:#43A047}.fr-view blockquote{border-left:solid 2px #5E35B1;margin-left:0;padding-left:5px;color:#5E35B1}.fr-view blockquote blockquote{border-color:#00BCD4;color:#00BCD4}.fr-view blockquote blockquote blockquote{border-color:#43A047;color:#43A047}.fr-view span.fr-emoticon{font-weight:normal;font-family:"Apple Color Emoji","Segoe UI Emoji","NotoColorEmoji","Segoe UI Symbol","Android Emoji","EmojiSymbols";display:inline;line-height:0}.fr-view span.fr-emoticon.fr-emoticon-img{background-repeat:no-repeat !important;font-size:inherit;height:1em;width:1em;min-height:20px;min-width:20px;display:inline-block;margin:-.1em .1em .1em;line-height:1;vertical-align:middle}.fr-view .fr-text-gray{color:#AAA !important}.fr-view .fr-text-bordered{border-top:solid 1px #222;border-bottom:solid 1px #222;padding:10px 0}.fr-view .fr-text-spaced{letter-spacing:1px}.fr-view .fr-text-uppercase{text-transform:uppercase}.fr-view .fr-class-highlighted{background-color:#ffff00}.fr-view .fr-class-code{border-color:#cccccc;border-radius:2px;-moz-border-radius:2px;-webkit-border-radius:2px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background:#f5f5f5;padding:10px;font-family:"Courier New", Courier, monospace}.fr-view .fr-class-transparency{opacity:0.5}.fr-view img{position:relative;max-width:100%}.fr-view img.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}.fr-view img.fr-dib.fr-fil{margin-left:0;text-align:left}.fr-view img.fr-dib.fr-fir{margin-right:0;text-align:right}.fr-view img.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}.fr-view img.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}.fr-view img.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)}.fr-view span.fr-img-caption{position:relative;max-width:100%}.fr-view span.fr-img-caption.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}.fr-view span.fr-img-caption.fr-dib.fr-fil{margin-left:0;text-align:left}.fr-view span.fr-img-caption.fr-dib.fr-fir{margin-right:0;text-align:right}.fr-view span.fr-img-caption.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}.fr-view span.fr-img-caption.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}.fr-view span.fr-img-caption.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)}.fr-view .fr-video{text-align:center;position:relative}.fr-view .fr-video.fr-rv{padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.fr-view .fr-video.fr-rv>iframe,.fr-view .fr-video.fr-rv object,.fr-view .fr-video.fr-rv embed{position:absolute !important;top:0;left:0;width:100%;height:100%}.fr-view .fr-video>*{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;max-width:100%;border:none}.fr-view .fr-video.fr-dvb{display:block;clear:both}.fr-view .fr-video.fr-dvb.fr-fvl{text-align:left}.fr-view .fr-video.fr-dvb.fr-fvr{text-align:right}.fr-view .fr-video.fr-dvi{display:inline-block}.fr-view .fr-video.fr-dvi.fr-fvl{float:left}.fr-view .fr-video.fr-dvi.fr-fvr{float:right}.fr-view a.fr-strong{font-weight:700}.fr-view a.fr-green{color:green}.fr-view .fr-img-caption{text-align:center}.fr-view .fr-img-caption .fr-img-wrap{padding:0;margin:auto;text-align:center;width:100%}.fr-view .fr-img-caption .fr-img-wrap a{display:block}.fr-view .fr-img-caption .fr-img-wrap img{display:block;margin:auto;width:100%}.fr-view .fr-img-caption .fr-img-wrap>span{margin:auto;display:block;padding:5px 5px 10px;font-size:14px;font-weight:initial;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-opacity:0.9;-moz-opacity:0.9;opacity:0.9;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";width:100%;text-align:center}.fr-view button.fr-rounded,.fr-view input.fr-rounded,.fr-view textarea.fr-rounded{border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box}.fr-view button.fr-large,.fr-view input.fr-large,.fr-view textarea.fr-large{font-size:24px}.fr-view ul,.fr-view ol{list-style-position:inside}a.fr-view.fr-strong{font-weight:700}a.fr-view.fr-green{color:green}img.fr-view{position:relative;max-width:100%}img.fr-view.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}img.fr-view.fr-dib.fr-fil{margin-left:0;text-align:left}img.fr-view.fr-dib.fr-fir{margin-right:0;text-align:right}img.fr-view.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}img.fr-view.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}img.fr-view.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)}span.fr-img-caption.fr-view{position:relative;max-width:100%}span.fr-img-caption.fr-view.fr-dib{margin:5px auto;display:block;float:none;vertical-align:top}span.fr-img-caption.fr-view.fr-dib.fr-fil{margin-left:0;text-align:left}span.fr-img-caption.fr-view.fr-dib.fr-fir{margin-right:0;text-align:right}span.fr-img-caption.fr-view.fr-dii{display:inline-block;float:none;vertical-align:bottom;margin-left:5px;margin-right:5px;max-width:calc(100% - (2 * 5px))}span.fr-img-caption.fr-view.fr-dii.fr-fil{float:left;margin:5px 5px 5px 0;max-width:calc(100% - 5px)}span.fr-img-caption.fr-view.fr-dii.fr-fir{float:right;margin:5px 0 5px 5px;max-width:calc(100% - 5px)} 8 | -------------------------------------------------------------------------------- /dist/css/plugins/tui-image-editor/tui-color-picker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * TOAST UI Color Picker 3 | * @version 2.2.6 4 | * @author NHN FE Development Team 5 | * @license MIT 6 | */.tui-colorpicker-clearfix{zoom:1}.tui-colorpicker-clearfix:after{content:"";display:block;clear:both}.tui-colorpicker-vml{behavior:url(#default#VML);display:block}.tui-colorpicker-container,.tui-colorpicker-palette-container{width:152px}.tui-colorpicker-palette-container ul{width:152px;margin:0;padding:0}.tui-colorpicker-palette-container li{float:left;margin:0;padding:0 3px 3px 0;list-style:none}.tui-colorpicker-palette-button{display:block;overflow:hidden;outline:none;margin:0;padding:0;width:16px;height:16px;border:1px solid #ccc;cursor:pointer}.tui-colorpicker-palette-button.tui-colorpicker-selected{border:2px solid #000}.tui-colorpicker-palette-button.tui-colorpicker-color-transparent{barckground-repeat:repeat;background-repeat:no-repeat;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAABfGlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGAqSSwoyGFhYGDIzSspCnJ3UoiIjFJgv8PAzcDDIMRgxSCemFxc4BgQ4MOAE3y7xsAIoi/rgsxK8/x506a1fP4WNq+ZclYlOrj1gQF3SmpxMgMDIweQnZxSnJwLZOcA2TrJBUUlQPYMIFu3vKQAxD4BZIsUAR0IZN8BsdMh7A8gdhKYzcQCVhMS5AxkSwDZAkkQtgaInQ5hW4DYyRmJKUC2B8guiBvAgNPDRcHcwFLXkYC7SQa5OaUwO0ChxZOaFxoMcgcQyzB4MLgwKDCYMxgwWDLoMjiWpFaUgBQ65xdUFmWmZ5QoOAJDNlXBOT+3oLQktUhHwTMvWU9HwcjA0ACkDhRnEKM/B4FNZxQ7jxDLX8jAYKnMwMDcgxBLmsbAsH0PA4PEKYSYyjwGBn5rBoZt5woSixLhDmf8xkKIX5xmbARh8zgxMLDe+///sxoDA/skBoa/E////73o//+/i4H2A+PsQA4AJHdp4IxrEg8AAAGbaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjEzPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjE0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CghrN1AAAABzSURBVCgVldKxEYAgDAXQD5VOpLuwgi4jlrTMqF00oOd5Aia/CcV/F4oYOgNlrLjvVyCEVJchBjEC25538PeaWTzRMBLxvIL7UZwFwL06qoA6aoAy+gFfJABvJAQPUoCMlICRRd8BzgHzJL4ok9aJ67l4AK9AxVKhHryUAAAAAElFTkSuQmCC")}.tui-colorpicker-palette-hex{font-family:monospace;width:60px}.tui-colorpicker-palette-hex,.tui-colorpicker-palette-preview{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.tui-colorpicker-palette-preview{width:12px;height:12px;border:1px solid #ccc;overflow:hidden}.tui-colorpicker-palette-toggle-slider{display:inline-block;*display:inline;zoom:1;vertical-align:middle;float:right}.tui-colorpicker-slider-container{margin:5px 0 0;height:122px;zoom:1}.tui-colorpicker-slider-container:after{content:"";display:block;clear:both}.tui-colorpicker-slider-left{float:left;width:120px;height:120px}.tui-colorpicker-slider-right{float:right;width:32px;height:120px}.tui-colorpicker-svg{display:block}.tui-colorpicker-slider-handle{position:absolute;overflow:visible;top:0;left:0;width:1px;height:1px;z-index:2;opacity:.9}.tui-colorpicker-svg-slider,.tui-colorpicker-vml-slider{width:120px;height:120px;border:1px solid #ccc;overflow:hidden}.tui-colorpicker-vml-slider{position:relative}.tui-colorpicker-vml-slider-bg{position:absolute;margin:-1px 0 0 -1px;top:0;left:0;width:122px;height:122px}.tui-colorpicker-svg-huebar{float:right;width:18px;height:120px;border:1px solid #ccc;overflow:visible}.tui-colorpicker-vml-huebar{width:32px;position:relative}.tui-colorpicker-vml-huebar-bg{position:absolute;top:0;right:0;width:18px;height:121px} -------------------------------------------------------------------------------- /dist/css/plugins/tui-image-editor/tui-image-editor.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * tui-image-editor.min.js 3 | * @version 3.2.2 4 | * @author NHNEnt FE Development Lab 5 | * @license MIT 6 | */ 7 | body>textarea{position:fixed!important}.tui-image-editor-container{marign:0;padding:0;box-sizing:border-box;min-height:300px;height:100%;position:relative;background-color:#282828;overflow:hidden;letter-spacing:.3px}.tui-image-editor-container div,.tui-image-editor-container input,.tui-image-editor-container label,.tui-image-editor-container li,.tui-image-editor-container ul{box-sizing:border-box;margin:0;padding:0;-ms-user-select:none;-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;user-select:none}.tui-image-editor-container .tui-image-editor-header{min-width:533px;position:absolute;background-color:#151515;top:0;width:100%}.tui-image-editor-container .tui-image-editor-controls-buttons,.tui-image-editor-container .tui-image-editor-header-buttons{float:right;margin:8px}.tui-image-editor-container .tui-image-editor-controls-logo,.tui-image-editor-container .tui-image-editor-header-logo{float:left;width:30%;padding:17px}.tui-image-editor-container .tui-image-editor-controls-buttons,.tui-image-editor-container .tui-image-editor-controls-logo{width:270px;height:100%;display:none}.tui-image-editor-container .tui-image-editor-controls-buttons button,.tui-image-editor-container .tui-image-editor-header-buttons button{position:relative;width:120px;height:40px;padding:0;line-height:40px;outline:none;border-radius:20px;border:1px solid #ddd;font-family:Noto Sans,sans-serif;font-size:12px;font-weight:700;cursor:pointer;vertical-align:middle;letter-spacing:.3px}.tui-image-editor-container .tui-image-editor-download-btn{background-color:#fdba3b;border-color:#fdba3b;color:#fff}.tui-image-editor-container .tui-image-editor-load-btn{position:absolute;left:0;right:0;display:inline-block;top:0;bottom:0;width:100%;cursor:pointer;opacity:0}.tui-image-editor-container .tui-image-editor-main-container{position:absolute;width:100%;top:0;bottom:64px}.tui-image-editor-container .tui-image-editor-main{position:absolute;text-align:center;top:64px;bottom:0;right:0;left:0}.tui-image-editor-container .tui-image-editor-wrap{bottom:0;width:100%;overflow:auto}.tui-image-editor-container .tui-image-editor-wrap .tui-image-editor-size-wrap{display:table;width:100%;height:100%}.tui-image-editor-container .tui-image-editor-wrap .tui-image-editor-size-wrap .tui-image-editor-align-wrap{display:table-cell;vertical-align:middle}.tui-image-editor-container .tui-image-editor{position:relative;display:inline-block}.tui-image-editor-container .tui-image-editor-menu{width:auto;list-style:none;padding:0;margin:0 auto;display:table-cell;text-align:center;vertical-align:middle;white-space:nowrap}.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item{position:relative;display:inline-block;border-radius:2px;padding:7px 8px 3px;cursor:pointer;margin:0 4px}.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item[title]:hover:before{content:"";display:inline-block;margin:0 auto;width:0;height:0;border-right:7px solid transparent;border-top:7px solid #2f2f2f;border-left:7px solid transparent;position:absolute;left:13px;top:-2px}.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item[title]:hover:after{content:attr(title);position:absolute;display:inline-block;background-color:#2f2f2f;color:#fff;padding:5px 8px;font-size:11px;font-weight:lighter;border-radius:3px;max-height:23px;top:-22px;left:0;min-width:24px}.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item.active{background-color:#fff;transition:all .3s ease}.tui-image-editor-container .tui-image-editor-wrap{position:absolute}.tui-image-editor-container .tui-image-editor-grid-visual{display:none;position:absolute;width:100%;height:100%;border:1px solid hsla(0,0%,100%,.7)}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor{transition:none}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor-grid-visual,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-grid-visual{display:block}.tui-image-editor-container .tui-image-editor-grid-visual table{width:100%;height:100%;border-collapse:collapse}.tui-image-editor-container .tui-image-editor-grid-visual table td{border:1px solid hsla(0,0%,100%,.3)}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot:before{content:"";position:absolute;box-sizing:border-box;width:10px;height:10px;border:0;box-shadow:0 0 1px 0 rgba(0,0,0,.3);border-radius:100%;background-color:#fff}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.left-top:before{top:-5px;left:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.right-top:before{top:-5px;right:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.left-bottom:before{bottom:-5px;left:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.right-bottom:before{bottom:-5px;right:-5px}.tui-image-editor-container .tui-image-editor-submenu{display:none;position:absolute;bottom:0;width:100%;height:150px;white-space:nowrap;z-index:2}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button:hover svg>use.active{display:block}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item li{display:inline-block;vertical-align:top}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-newline{display:block;margin-top:0}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button{position:relative;cursor:pointer;display:inline-block;font-weight:400;font-size:11px;margin:0 9px}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item label{display:inline-block;cursor:pointer;padding-top:5px;font-family:Noto Sans,sans-serif;font-size:11px}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button.apply label,.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button.cancel label{vertical-align:7px}.tui-image-editor-container .tui-image-editor-submenu>div{display:none;vertical-align:bottom}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-style{opacity:.95;z-index:-1;position:absolute;top:0;bottom:0;left:0;right:0;display:block}.tui-image-editor-container .tui-image-editor-partition>div{width:1px;height:52px;border-left:1px solid #3c3c3c;margin:0 8px}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-filter .tui-image-editor-partition>div{height:108px;margin:0 29px 0 0}.tui-image-editor-container .tui-image-editor-submenu-align{text-align:left;margin-right:30px}.tui-image-editor-container .tui-image-editor-submenu-align label{width:55px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-submenu-align:first-child{margin-right:0}.tui-image-editor-container .tui-image-editor-submenu-align:first-child label{width:70px}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-crop .tui-image-editor-submenu>div.tui-image-editor-menu-crop,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-draw .tui-image-editor-submenu>div.tui-image-editor-menu-draw,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-filter .tui-image-editor-submenu>div.tui-image-editor-menu-filter,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor-submenu>div.tui-image-editor-menu-flip,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-icon .tui-image-editor-submenu>div.tui-image-editor-menu-icon,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-mask .tui-image-editor-submenu>div.tui-image-editor-menu-mask,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-submenu>div.tui-image-editor-menu-rotate,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-shape .tui-image-editor-submenu>div.tui-image-editor-menu-shape,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-text .tui-image-editor-submenu>div.tui-image-editor-menu-text{display:table-cell}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-crop .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-draw .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-filter .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-icon .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-mask .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-shape .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-text .tui-image-editor-submenu{display:table}.tui-image-editor-container .filter-color-item{display:inline-block}.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox{display:block}.tui-image-editor-container .tui-image-editor-checkbox-wrap{display:inline-block!important;text-align:left}.tui-image-editor-container .tui-image-editor-checkbox-wrap.fixed-width{width:187px;white-space:normal}.tui-image-editor-container .tui-image-editor-checkbox{display:inline-block;margin:1px 0}.tui-image-editor-container .tui-image-editor-checkbox input{width:14px;height:14px;opacity:0}.tui-image-editor-container .tui-image-editor-checkbox input+label{color:#fff;height:14px;position:relative}.tui-image-editor-container .tui-image-editor-checkbox input+label:before{content:"";position:absolute;width:14px;height:14px;background-color:#fff;top:6px;left:-19px;display:inline-block;margin:0;text-align:center;font-size:11px;border:0;border-radius:2px;padding-top:1px;box-sizing:border-box}.tui-image-editor-container .tui-image-editor-checkbox input[type=checkbox]:checked+label:before{background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAMBJREFUKBWVkjEOwjAMRe2WgZW7IIHEDdhghhuwcQ42rlJugAQS54Cxa5cq1QM5TUpByZfS2j9+dlJVt/tX5ZxbS4ZU9VLkQvSHKTIGRaVJYFmKrBbTCJxE2UgCdDzMZDkHrOV6b95V0US6UmgKodujEZbJg0B0ZgEModO5lrY1TMQf1TpyJGBEjD+E2NPN7ukIUDiF/BfEXgRiGEw8NgkffYGYwCi808fpn/6OvfUfsDr/Vc1IfRf8sKnFVqeiVQfDu0tf/nWH9gAAAABJRU5ErkJggg==")}.tui-image-editor-container .tui-image-editor-selectlist-wrap{position:relative}.tui-image-editor-container .tui-image-editor-selectlist-wrap select{width:100%;height:28px;margin-top:4px;border:0;outline:0;border-radius:0;border:1px solid #cbdbdb;background-color:#fff;-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0 7px 0 10px}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist{display:none;position:relative;top:-1px;border:1px solid #ccc;background-color:#fff;border-top:0;padding:4px 0}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist li{display:block;text-align:left;padding:7px 10px;font-family:Noto Sans,sans-serif}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist li:hover{background-color:rgba(81,92,230,.05)}.tui-image-editor-container .tui-image-editor-selectlist-wrap:before{content:"";position:absolute;display:inline-block;width:14px;height:14px;right:5px;top:10px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAHlJREFUKBVjYBgFOEOAEVkmPDxc89+/f6eAYjzI4kD2FyYmJrOVK1deh4kzwRggGiQBVJCELAZig8SQNYHEmEEEMrh69eo1HR0dfqCYJUickZGxf9WqVf3IakBsFBthklpaWmVA9mEQhrJhUoTp0NBQCRAmrHL4qgAAuu4cWZOZIGsAAAAASUVORK5CYII=");background-size:cover}.tui-image-editor-container .tui-image-editor-selectlist-wrap select::-ms-expand{display:none}.tui-image-editor-container .tui-image-editor-virtual-range-bar .tui-image-editor-disabled,.tui-image-editor-container .tui-image-editor-virtual-range-pointer .tui-image-editor-disabled,.tui-image-editor-container .tui-image-editor-virtual-range-subbar .tui-image-editor-disabled{backbround-color:red}.tui-image-editor-container .tui-image-editor-range{position:relative;top:5px;width:166px;height:17px;display:inline-block}.tui-image-editor-container .tui-image-editor-virtual-range-bar{top:7px;position:absolute;width:100%;height:2px;background-color:#666}.tui-image-editor-container .tui-image-editor-virtual-range-subbar{position:absolute;height:100%;left:0;right:0;background-color:#d1d1d1}.tui-image-editor-container .tui-image-editor-virtual-range-pointer{position:absolute;cursor:pointer;top:-5px;left:0;width:12px;height:12px;background-color:#fff;border-radius:100%}.tui-image-editor-container .tui-image-editor-range-wrap{display:inline-block;margin-left:4px}.tui-image-editor-container .tui-image-editor-range-wrap.short .tui-image-editor-range{width:100px}.tui-image-editor-container .color-picker-control .tui-image-editor-range{width:108px;margin-left:10px}.tui-image-editor-container .color-picker-control .tui-image-editor-virtual-range-pointer{background-color:#333}.tui-image-editor-container .color-picker-control .tui-image-editor-virtual-range-bar{background-color:#ccc}.tui-image-editor-container .color-picker-control .tui-image-editor-virtual-range-subbar{background-color:#606060}.tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short{margin-top:-2px;margin-left:19px}.tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label{color:#8e8e8e;font-weight:400}.tui-image-editor-container .tui-image-editor-range-wrap label{vertical-align:baseline;font-size:11px;margin-right:7px;color:#fff}.tui-image-editor-container .tui-image-editor-range-value{cursor:default;width:40px;height:24px;outline:none;border-radius:2px;box-shadow:none;border:1px solid #d5d5d5;text-align:center;background-color:#1c1c1c;color:#fff;font-weight:lighter;vertical-align:baseline;font-family:Noto Sans,sans-serif;margin-top:21px;margin-left:4px}.tui-image-editor-container .tui-image-editor-controls{position:absolute;background-color:#151515;width:100%;height:64px;display:table;bottom:0;z-index:2}.tui-image-editor-container .tui-image-editor-icpartition{display:inline-block;background-color:#282828;width:1px;height:24px}.tui-image-editor-container.left .tui-image-editor-menu>.tui-image-editor-item[title]:before{left:28px;top:11px;border-right:7px solid #2f2f2f;border-top:7px solid transparent;border-bottom:7px solid transparent}.tui-image-editor-container.left .tui-image-editor-menu>.tui-image-editor-item[title]:after{top:7px;left:39px;width:27px}.tui-image-editor-container.left .tui-image-editor-submenu{left:0;height:100%;width:248px}.tui-image-editor-container.left .tui-image-editor-main-container{left:64px;width:calc(100% - 64px);height:100%}.tui-image-editor-container.left .tui-image-editor-controls{width:64px;height:100%;display:table}.tui-image-editor-container.left .tui-image-editor-menu,.tui-image-editor-container.right .tui-image-editor-menu{white-space:inherit}.tui-image-editor-container.left .tui-image-editor-submenu,.tui-image-editor-container.right .tui-image-editor-submenu{white-space:normal}.tui-image-editor-container.left .tui-image-editor-submenu>div,.tui-image-editor-container.right .tui-image-editor-submenu>div{vertical-align:middle}.tui-image-editor-container.left .tui-image-editor-controls li,.tui-image-editor-container.right .tui-image-editor-controls li{display:inline-block;margin:4px auto}.tui-image-editor-container.left .tui-image-editor-icpartition,.tui-image-editor-container.right .tui-image-editor-icpartition{position:relative;top:-7px;width:24px;height:1px}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition{display:block;width:75%;margin:auto}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition>div,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition>div{border-left:0;height:10px;border-bottom:1px solid #3c3c3c;width:100%;margin:0}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-submenu-align,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-submenu-align{margin-right:0}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-submenu-item li,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-submenu-item li{margin-top:15px}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-colorpicker-clearfix li,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-colorpicker-clearfix li{margin-top:0}.tui-image-editor-container.left .tui-image-editor-checkbox-wrap.fixed-width,.tui-image-editor-container.right .tui-image-editor-checkbox-wrap.fixed-width{width:182px;white-space:normal}.tui-image-editor-container.left .tui-image-editor-range-wrap.tui-image-editor-newline label.range,.tui-image-editor-container.right .tui-image-editor-range-wrap.tui-image-editor-newline label.range{display:block;text-align:left;width:75%;margin:auto}.tui-image-editor-container.left .tui-image-editor-range,.tui-image-editor-container.right .tui-image-editor-range{width:136px}.tui-image-editor-container.right .tui-image-editor-menu>.tui-image-editor-item[title]:before{left:-3px;top:11px;border-left:7px solid #2f2f2f;border-top:7px solid transparent;border-bottom:7px solid transparent}.tui-image-editor-container.right .tui-image-editor-menu>.tui-image-editor-item[title]:after{top:7px;left:-44px;width:27px}.tui-image-editor-container.right .tui-image-editor-submenu{right:0;height:100%;width:248px}.tui-image-editor-container.right .tui-image-editor-main-container{right:64px;width:calc(100% - 64px);height:100%}.tui-image-editor-container.right .tui-image-editor-controls{right:0;width:64px;height:100%;display:table}.tui-image-editor-container.bottom .tui-image-editor-submenu .tui-image-editor-partition.only-left-right,.tui-image-editor-container.top .tui-image-editor-submenu .tui-image-editor-partition.only-left-right{display:none}.tui-image-editor-container.bottom .tui-image-editor-submenu>div{padding-bottom:24px}.tui-image-editor-container.top .color-picker-control .triangle{top:-8px;border-right:7px solid transparent;border-top:0;border-left:7px solid transparent;border-bottom:8px solid #fff}.tui-image-editor-container.top .tui-image-editor-size-wrap{height:100%}.tui-image-editor-container.top .tui-image-editor-main-container{bottom:0}.tui-image-editor-container.top .tui-image-editor-menu>.tui-image-editor-item[title]:before{left:13px;border-top:0;border-bottom:7px solid #2f2f2f;top:33px}.tui-image-editor-container.top .tui-image-editor-menu>.tui-image-editor-item[title]:after{top:38px}.tui-image-editor-container.top .tui-image-editor-submenu{top:0;bottom:inherit}.tui-image-editor-container.top .tui-image-editor-submenu>div{padding-top:24px;vertical-align:top}.tui-image-editor-container.top .tui-image-editor-controls-buttons,.tui-image-editor-container.top .tui-image-editor-controls-logo{display:table-cell}.tui-image-editor-container.top .tui-image-editor-main{top:64px;height:calc(100% - 64px)}.tui-image-editor-container.top .tui-image-editor-controls{top:0;bottom:inherit}.tui-image-editor-container #tie-icon-add-button .tui-image-editor-button{min-width:42px}.tui-image-editor-container .svg_ic-menu{width:24px;height:24px}.tui-image-editor-container .svg_ic-submenu{width:32px;height:32px}.tui-image-editor-container .svg_img-bi{width:257px;height:26px}.tui-image-editor-container .tui-image-editor-controls svg>use{display:none}.tui-image-editor-container .tui-image-editor-controls .enabled svg:hover>use.hover,.tui-image-editor-container .tui-image-editor-controls .normal svg:hover>use.hover{display:block}.tui-image-editor-container .tui-image-editor-controls .active svg:hover>use.hover{display:none}.tui-image-editor-container .tui-image-editor-controls .active svg>use.active,.tui-image-editor-container .tui-image-editor-controls .enabled svg>use.enabled,.tui-image-editor-container .tui-image-editor-controls svg>use.normal{display:block}.tui-image-editor-container .tui-image-editor-controls .active svg>use.normal,.tui-image-editor-container .tui-image-editor-controls .enabled svg>use.normal{display:none}.tui-image-editor-container .tui-image-editor-controls:hover{z-index:3}.tui-image-editor-container div.tui-colorpicker-clearfix{width:159px;height:28px;border:1px solid #d5d5d5;border-radius:2px;background-color:#f5f5f5;margin-top:6px;padding:4px 7px}.tui-image-editor-container .tui-colorpicker-palette-hex{width:114px;background-color:#f5f5f5;border:0;font-size:11px;margin-top:2px;font-family:Noto Sans,sans-serif}.tui-image-editor-container .tui-colorpicker-palette-hex[value=""]+.tui-colorpicker-palette-preview,.tui-image-editor-container .tui-colorpicker-palette-hex[value="#ffffff"]+.tui-colorpicker-palette-preview{border:1px solid #ccc}.tui-image-editor-container .tui-colorpicker-palette-hex[value=""]+.tui-colorpicker-palette-preview{background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC")}.tui-image-editor-container .tui-colorpicker-palette-preview{border-radius:100%;float:left;width:17px;height:17px;border:0}.tui-image-editor-container .color-picker-control{position:absolute;display:none;z-index:99;width:192px;background-color:#fff;box-shadow:0 3px 22px 6px rgba(0,0,0,.15);padding:16px;border-radius:2px}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-toggle-slider{display:none}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button{border:0;border-radius:100%;margin:2px;background-size:cover;font-size:1px}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button[title=""],.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button[title="#ffffff"]{border:1px solid #ccc}.tui-image-editor-container .color-picker-control .triangle{width:0;height:0;border-right:7px solid transparent;border-top:8px solid #fff;border-left:7px solid transparent;position:absolute;bottom:-8px;left:84px}.tui-image-editor-container .color-picker-control .tui-colorpicker-container,.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-container,.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-container ul{width:100%;height:auto}.tui-image-editor-container .filter-color-item .color-picker-control label{font-color:#333;font-weight:400;margin-right:7pxleft}.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox{margin-top:0}.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox input+label:before{left:-16px}.tui-image-editor-container .color-picker{width:100%;height:auto}.tui-image-editor-container .color-picker-value{width:32px;height:32px;border:0;border-radius:100%;margin:auto;margin-bottom:1px}.tui-image-editor-container .color-picker-value.transparent{border:1px solid #cbcbcb;background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC")}.tui-image-editor-container .color-picker-value+label{color:#fff}.tui-image-editor-container .tui-image-editor-submenu svg>use{display:none}#tie-icon-add-button.icon-arrow-2 .tui-image-editor-button[data-icontype=icon-arrow-2] svg>use.active,#tie-icon-add-button.icon-arrow-3 .tui-image-editor-button[data-icontype=icon-arrow-3] svg>use.active,#tie-icon-add-button.icon-arrow .tui-image-editor-button[data-icontype=icon-arrow] svg>use.active,#tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype=icon-bubble] svg>use.active,#tie-icon-add-button.icon-heart .tui-image-editor-button[data-icontype=icon-heart] svg>use.active,#tie-icon-add-button.icon-location .tui-image-editor-button[data-icontype=icon-location] svg>use.active,#tie-icon-add-button.icon-polygon .tui-image-editor-button[data-icontype=icon-polygon] svg>use.active,#tie-icon-add-button.icon-star .tui-image-editor-button[data-icontype=icon-star] svg>use.active,.tui-image-editor-container .tui-image-editor-submenu svg>use.normal{display:block}#tie-draw-line-select-button.free .tui-image-editor-button.free svg>use.normal,#tie-draw-line-select-button.line .tui-image-editor-button.line svg>use.normal{display:none}#tie-draw-line-select-button.free .tui-image-editor-button.free svg>use.active,#tie-draw-line-select-button.line .tui-image-editor-button.line svg>use.active{display:block}#tie-flip-button.flipX .tui-image-editor-button.flipX svg>use.normal,#tie-flip-button.flipY .tui-image-editor-button.flipY svg>use.normal,#tie-flip-button.resetFlip .tui-image-editor-button.resetFlip svg>use.normal{display:none}#tie-flip-button.flipX .tui-image-editor-button.flipX svg>use.active,#tie-flip-button.flipY .tui-image-editor-button.flipY svg>use.active,#tie-flip-button.resetFlip .tui-image-editor-button.resetFlip svg>use.active{display:block}#tie-mask-apply.apply.active .tui-image-editor-button.apply label{color:#fff}#tie-mask-apply.apply.active .tui-image-editor-button.apply svg>use.active{display:block}#tie-crop-button .tui-image-editor-button.apply{margin-right:24px}#tie-crop-button .tui-image-editor-button.apply.active svg>use.active{display:block}#tie-shape-button.circle .tui-image-editor-button.circle svg>use.normal,#tie-shape-button.rect .tui-image-editor-button.rect svg>use.normal,#tie-shape-button.triangle .tui-image-editor-button.triangle svg>use.normal{display:none}#tie-shape-button.circle .tui-image-editor-button.circle svg>use.active,#tie-shape-button.rect .tui-image-editor-button.rect svg>use.active,#tie-shape-button.triangle .tui-image-editor-button.triangle svg>use.active,#tie-text-align-button.center .tui-image-editor-button.center svg>use.active,#tie-text-align-button.left .tui-image-editor-button.left svg>use.active,#tie-text-align-button.right .tui-image-editor-button.right svg>use.active,#tie-text-effect-button .tui-image-editor-button.active svg>use.active{display:block}#tie-icon-image-file,#tie-mask-image-file{opacity:0;position:absolute;width:100%;height:100%;border:1px solid green;cursor:inherit;left:0;top:0}.tui-image-editor-container.top.tui-image-editor-top-optimization .tui-image-editor-controls ul{text-align:right}.tui-image-editor-container.top.tui-image-editor-top-optimization .tui-image-editor-controls-logo{display:none} -------------------------------------------------------------------------------- /dist/js/plugins/tui-image-editor/tui-code-snippet.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * tui-code-snippet.min.js 3 | * @version 1.4.0 4 | * @author NHNEnt FE Development Lab 5 | * @license MIT 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.util=e():(t.tui=t.tui||{},t.tui.util=e())}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="dist",e(0)}([function(t,e,n){"use strict";var r={},o=n(1),i=o.extend;i(r,o),i(r,n(3)),i(r,n(2)),i(r,n(4)),i(r,n(5)),i(r,n(6)),i(r,n(7)),i(r,n(8)),i(r,n(9)),r.browser=n(10),r.popup=n(11),r.formatDate=n(12),r.defineClass=n(13),r.defineModule=n(14),r.defineNamespace=n(15),r.CustomEvents=n(16),r.Enum=n(17),r.ExMap=n(18),r.HashMap=n(20),r.Map=n(19),t.exports=r},function(t,e,n){"use strict";function r(t,e){var n,r,o,i,u=Object.prototype.hasOwnProperty;for(o=1,i=arguments.length;o-1||h.inArray(e,o)>-1)return!1;for(n in e){if(e.hasOwnProperty(n)!==t.hasOwnProperty(n))return!1;if(typeof e[n]!=typeof t[n])return!1}for(n in t){if(e.hasOwnProperty(n)!==t.hasOwnProperty(n))return!1;if(typeof e[n]!=typeof t[n])return!1;if("object"==typeof t[n]||"function"==typeof t[n]){if(r.push(t),o.push(e),!a(t[n],e[n]))return!1;r.pop(),o.pop()}else if(t[n]!==e[n])return!1}return!0}function p(t,e){for(var n=arguments,r=n[0],o=1,i=n.length;o=0&&r","'":"'"," ":" "};return t.replace(/&|<|>|"|'| /g,function(t){return e[t]?e[t]:t})}function o(t){var e={'"':"quot","&":"amp","<":"lt",">":"gt","'":"#39"};return t.replace(/[<>&"']/g,function(t){return e[t]?"&"+e[t]+";":t})}function i(t){return/[<>&"']/.test(t)}function u(t,e){for(var n,r,o=0,i=t.length,u={};o1}),u=c.keys(u).sort(),n=u.join("")}var s=n(4),c=n(1);t.exports={decodeHTMLEntity:r,encodeHTMLEntity:o,hasEncodableString:i,getDuplicatedChar:u}},function(t,e){"use strict";function n(t,e){function n(){o=u.call(arguments),window.clearTimeout(r),r=window.setTimeout(function(){t.apply(null,o)},e)}var r,o;return e=e||0,n}function r(){return Number(new Date)}function o(t,e){function n(){return a=u.call(arguments),p?(f(a),void(p=!1)):(c=i.timestamp(),o=o||c,s(a),void(c-o>=e&&f(a)))}function r(){p=!0,o=null}var o,s,c,a,p=!0,f=function(e){t.apply(null,e),o=null};return e=e||0,s=i.debounce(f,e),n.reset=r,n}var i={},u=Array.prototype.slice;i.timestamp=r,i.debounce=n,i.throttle=o,t.exports=i},function(t,e,n){"use strict";function r(t){var e="https://www.google-analytics.com/collect",n=location.hostname,r="event",i="UA-115377265-9";(s.isUndefined(window.tui)||window.tui.usageStatistics!==!1)&&setTimeout(function(){"interactive"!==document.readyState&&"complete"!==document.readyState||o(e,{v:1,t:r,tid:i,cid:n,dp:n,dh:t})},1e3)}function o(t,e){var n=u.map(i.keys(e),function(t,n){var r=0===n?"":"&";return r+t+"="+e[t]}).join(""),r=document.createElement("img");return r.src=t+"?"+n,r.style.display="none",document.body.appendChild(r),document.body.removeChild(r),r}var i=n(1),u=n(4),s=n(2);t.exports={imagePing:o,sendHostname:r}},function(t,e){"use strict";var n,r,o={chrome:!1,firefox:!1,safari:!1,msie:!1,edge:!1,others:!1,version:0},i=window.navigator,u=i.appName.replace(/\s/g,"_"),s=i.userAgent,c=/MSIE\s([0-9]+[.0-9]*)/,a=/Trident.*rv:11\./,p=/Edge\/(\d+)\./,f={firefox:/Firefox\/(\d+)\./,chrome:/Chrome\/(\d+)\./,safari:/Version\/([\d.]+).*Safari\/(\d+)/},h={Microsoft_Internet_Explorer:function(){var t=s.match(c);t?(o.msie=!0,o.version=parseFloat(t[1])):o.others=!0},Netscape:function(){var t=!1;if(a.exec(s))o.msie=!0,o.version=11,t=!0;else if(p.exec(s))o.edge=!0,o.version=s.match(p)[1],t=!0;else for(n in f)if(f.hasOwnProperty(n)&&(r=s.match(f[n]),r&&r.length>1)){o[n]=t=!0,o.version=parseFloat(r[1]||0);break}t||(o.others=!0)}},l=h[u];l&&h[u](),t.exports=o},function(t,e,n){"use strict";function r(){this.openedPopup={},this.closeWithParentPopup={},this.postBridgeUrl=""}var o=n(4),i=n(2),u=n(5),s=n(10),c=n(1),a=0;r.prototype.getPopupList=function(t){var e;return e=i.isExisty(t)?this.openedPopup[t]:this.openedPopup},r.prototype.openPopup=function(t,e){var n,r,o;if(e=c.extend({popupName:"popup_"+a+"_"+Number(new Date),popupOptionStr:"",useReload:!0,closeWithParent:!0,method:"get",param:{}},e||{}),e.method=e.method.toUpperCase(),this.postBridgeUrl=e.postBridgeUrl||this.postBridgeUrl,o="POST"===e.method&&e.param&&s.msie&&11===s.version,!i.isExisty(t))throw new Error("Popup#open() need popup url.");a+=1,e.param&&("GET"===e.method?t=t+(/\?/.test(t)?"&":"?")+this._parameterize(e.param):"POST"===e.method&&(o||(r=this.createForm(t,e.param,e.method,e.popupName),t="about:blank"))),n=this.openedPopup[e.popupName],i.isExisty(n)?n.closed?this.openedPopup[e.popupName]=n=this._open(o,e.param,t,e.popupName,e.popupOptionStr):(e.useReload&&n.location.replace(t),n.focus()):this.openedPopup[e.popupName]=n=this._open(o,e.param,t,e.popupName,e.popupOptionStr),this.closeWithParentPopup[e.popupName]=e.closeWithParent,(!n||n.closed||i.isUndefined(n.closed))&&alert("please enable popup windows for this website"),e.param&&"POST"===e.method&&!o&&(n&&r.submit(),r.parentNode&&r.parentNode.removeChild(r)),window.onunload=u.bind(this.closeAllPopup,this)},r.prototype.close=function(t,e){var n=e||window;t=!!i.isExisty(t)&&t,t&&(window.onunload=null),n.closed||(n.opener=window.location.href,n.close())},r.prototype.closeAllPopup=function(t){var e=i.isExisty(t);o.forEachOwnProperties(this.openedPopup,function(t,n){(e&&this.closeWithParentPopup[n]||!e)&&this.close(!1,t)},this)},r.prototype.focus=function(t){this.getPopupList(t).focus()},r.prototype.parseQuery=function(){var t,e,n={};return t=window.location.search.substr(1),o.forEachArray(t.split("&"),function(t){e=t.split("="),n[decodeURIComponent(e[0])]=decodeURIComponent(e[1])}),n},r.prototype.createForm=function(t,e,n,r,i){var u,s=document.createElement("form");return i=i||document.body,s.method=n||"POST",s.action=t||"",s.target=r||"",s.style.display="none",o.forEachOwnProperties(e,function(t,e){u=document.createElement("input"),u.name=e,u.type="hidden",u.value=t,s.appendChild(u)}),i.appendChild(s),s},r.prototype._parameterize=function(t){var e=[];return o.forEachOwnProperties(t,function(t,n){e.push(encodeURIComponent(n)+"="+encodeURIComponent(t))}),e.join("&")},r.prototype._open=function(t,e,n,r,o){var i;return t?(i=window.open(this.postBridgeUrl,r,o),setTimeout(function(){i.redirect(n,e)},100)):i=window.open(n,r,o),i},t.exports=new r},function(t,e,n){"use strict";function r(t,e,n){var r,o,i,u;return t=Number(t),e=Number(e),n=Number(n),r=t>-1&&t<100||t>1969&&t<2070,o=e>0&&e<13,!(!r||!o)&&(u=a[e],2===e&&t%4===0&&(t%100===0&&t%400!==0||(u=29)),i=n>0&&n<=u)}function o(t,e,n){var o,c,a,f=u.pick(n,"meridiemSet","AM")||"AM",h=u.pick(n,"meridiemSet","PM")||"PM";return c=i.isDate(e)?{year:e.getFullYear(),month:e.getMonth()+1,date:e.getDate(),hour:e.getHours(),minute:e.getMinutes()}:{year:e.year,month:e.month,date:e.date,hour:e.hour,minute:e.minute},!!r(c.year,c.month,c.date)&&(c.meridiem="",/([^\\]|^)[aA]\b/.test(t)&&(o=c.hour>11?h:f,c.hour>12&&(c.hour%=12),0===c.hour&&(c.hour=12),c.meridiem=o),a=t.replace(s,function(t){return t.indexOf("\\")>-1?t.replace(/\\/,""):p[t](c)||""}))}var i=n(2),u=n(1),s=/[\\]*YYYY|[\\]*YY|[\\]*MMMM|[\\]*MMM|[\\]*MM|[\\]*M|[\\]*DD|[\\]*D|[\\]*HH|[\\]*H|[\\]*A/gi,c=["Invalid month","January","February","March","April","May","June","July","August","September","October","November","December"],a=[0,31,28,31,30,31,30,31,31,30,31,30,31],p={M:function(t){return Number(t.month)},MM:function(t){var e=t.month;return Number(e)<10?"0"+e:e},MMM:function(t){return c[Number(t.month)].substr(0,3)},MMMM:function(t){return c[Number(t.month)]},D:function(t){return Number(t.date)},d:function(t){return p.D(t)},DD:function(t){var e=t.date;return Number(e)<10?"0"+e:e},dd:function(t){return p.DD(t)},YY:function(t){return Number(t.year)%100},yy:function(t){return p.YY(t)},YYYY:function(t){var e="20",n=t.year;return n>69&&n<100&&(e="19"),Number(n)<100?e+String(n):n},yyyy:function(t){return p.YYYY(t)},A:function(t){return t.meridiem},a:function(t){return t.meridiem},hh:function(t){var e=t.hour;return Number(e)<10?"0"+e:e},HH:function(t){return p.hh(t)},h:function(t){return String(Number(t.hour))},H:function(t){return p.h(t)},m:function(t){return String(Number(t.minute))},mm:function(t){var e=t.minute;return Number(e)<10?"0"+e:e}};t.exports=o},function(t,e,n){"use strict";function r(t,e){var n;return e||(e=t,t=null),n=e.init||function(){},t&&o(n,t),e.hasOwnProperty("static")&&(i(n,e["static"]),delete e["static"]),i(n.prototype,e),n}var o=n(6).inherit,i=n(1).extend;t.exports=r},function(t,e,n){"use strict";function r(t,e){var n=e||{};return i.isFunction(n[u])&&n[u](),o(t,n)}var o=n(15),i=n(2),u="initialize";t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r,u,s,c;return r=t.split("."),r.unshift(window),u=o.reduce(r,function(t,e){return t[e]=t[e]||{},t[e]}),n?(c=r.pop(),s=i.pick.apply(null,r),u=s[c]=e):i.extend(u,e),u}var o=n(4),i=n(1);t.exports=r},function(t,e,n){"use strict";function r(){this.events=null,this.contexts=null}var o=n(4),i=n(2),u=n(1),s=/\s+/g;r.mixin=function(t){u.extend(t.prototype,r.prototype)},r.prototype._getHandlerItem=function(t,e){var n={handler:t};return e&&(n.context=e),n},r.prototype._safeEvent=function(t){var e,n=this.events;return n||(n=this.events={}),t&&(e=n[t],e||(e=[],n[t]=e),n=e),n},r.prototype._safeContext=function(){var t=this.contexts;return t||(t=this.contexts=[]),t},r.prototype._indexOfContext=function(t){for(var e=this._safeContext(),n=0;e[n];){if(t===e[n][0])return n;n+=1}return-1},r.prototype._memorizeContext=function(t){var e,n;i.isExisty(t)&&(e=this._safeContext(),n=this._indexOfContext(t),n>-1?e[n][1]+=1:e.push([t,1]))},r.prototype._forgetContext=function(t){var e,n;i.isExisty(t)&&(e=this._safeContext(),n=this._indexOfContext(t),n>-1&&(e[n][1]-=1,e[n][1]<=0&&e.splice(n,1)))},r.prototype._bindEvent=function(t,e,n){var r=this._safeEvent(t);this._memorizeContext(n),r.push(this._getHandlerItem(e,n))},r.prototype.on=function(t,e,n){var r=this;i.isString(t)?(t=t.split(s),o.forEach(t,function(t){r._bindEvent(t,e,n)})):i.isObject(t)&&(n=e,o.forEach(t,function(t,e){r.on(e,t,n)}))},r.prototype.once=function(t,e,n){function r(){e.apply(n,arguments),u.off(t,r,n)}var u=this;return i.isObject(t)?(n=e,void o.forEach(t,function(t,e){u.once(e,t,n)})):void this.on(t,r,n)},r.prototype._spliceMatches=function(t,e){var n,r=0;if(i.isArray(t))for(n=t.length;r0},r.prototype.getListenerLength=function(t){var e=this._safeEvent(t);return e.length},t.exports=r},function(t,e,n){"use strict";function r(t){t&&this.set.apply(this,arguments)}var o=n(4),i=n(2),u=function(){try{return Object.defineProperty({},"x",{}),!0}catch(t){return!1}}(),s=0;r.prototype.set=function(t){var e=this;i.isArray(t)||(t=o.toArray(arguments)),o.forEach(t,function(t){e._addItem(t)})},r.prototype.getName=function(t){var e,n=this;return o.forEach(this,function(r,o){if(n._isEnumItem(o)&&t===r)return e=o,!1}),e},r.prototype._addItem=function(t){var e;this.hasOwnProperty(t)||(e=this._makeEnumValue(),u?Object.defineProperty(this,t,{enumerable:!0,configurable:!1,writable:!1,value:e}):this[t]=e)},r.prototype._makeEnumValue=function(){var t;return t=s,s+=1,t},r.prototype._isEnumItem=function(t){return i.isNumber(this[t])},t.exports=r},function(t,e,n){"use strict";function r(t){this._map=new i(t),this.size=this._map.size}var o=n(4),i=n(19),u=["get","has","forEach","keys","values","entries"],s=["delete","clear"];o.forEachArray(u,function(t){r.prototype[t]=function(){return this._map[t].apply(this._map,arguments)}}),o.forEachArray(s,function(t){r.prototype[t]=function(){var e=this._map[t].apply(this._map,arguments);return this.size=this._map.size,e}}),r.prototype.set=function(){return this._map.set.apply(this._map,arguments),this.size=this._map.size,this},r.prototype.setObject=function(t){o.forEachOwnProperties(t,function(t,e){this.set(e,t)},this)},r.prototype.deleteByKeys=function(t){o.forEachArray(t,function(t){this["delete"](t)},this)},r.prototype.merge=function(t){t.forEach(function(t,e){this.set(e,t)},this)},r.prototype.filter=function(t){var e=new r;return this.forEach(function(n,r){t(n,r)&&e.set(r,n)}),e},t.exports=r},function(t,e,n){"use strict";function r(t,e){this._keys=t,this._valueGetter=e,this._length=this._keys.length,this._index=-1,this._done=!1}function o(t){this._valuesForString={},this._valuesForIndex={},this._keys=[],t&&this._setInitData(t),this.size=0}var i=n(4),u=n(2),s=n(3),c=n(10),a=n(5),p={},f={};r.prototype.next=function(){var t={};do this._index+=1;while(u.isUndefined(this._keys[this._index])&&this._index=this._length?t.done=!0:(t.done=!1,t.value=this._valueGetter(this._keys[this._index],this._index)),t},o.prototype._setInitData=function(t){if(!u.isArray(t))throw new Error("Only Array is supported.");i.forEachArray(t,function(t){this.set(t[0],t[1])},this)},o.prototype._isNaN=function(t){return"number"==typeof t&&t!==t},o.prototype._getKeyIndex=function(t){var e,n=-1;return u.isString(t)?(e=this._valuesForString[t],e&&(n=e.keyIndex)):n=s.inArray(t,this._keys),n},o.prototype._getOriginKey=function(t){var e=t;return t===p?e=void 0:t===f&&(e=NaN),e},o.prototype._getUniqueKey=function(t){var e=t;return u.isUndefined(t)?e=p:this._isNaN(t)&&(e=f),e},o.prototype._getValueObject=function(t,e){return u.isString(t)?this._valuesForString[t]:(u.isUndefined(e)&&(e=this._getKeyIndex(t)),e>=0?this._valuesForIndex[e]:void 0)},o.prototype._getOriginValue=function(t,e){return this._getValueObject(t,e).origin},o.prototype._getKeyValuePair=function(t,e){return[this._getOriginKey(t),this._getOriginValue(t,e)]},o.prototype._createValueObject=function(t,e){return{keyIndex:e,origin:t}},o.prototype.set=function(t,e){var n,r=this._getUniqueKey(t),o=this._getKeyIndex(r);return o<0&&(o=this._keys.push(r)-1,this.size+=1),n=this._createValueObject(e,o),u.isString(t)?this._valuesForString[t]=n:this._valuesForIndex[o]=n,this},o.prototype.get=function(t){var e=this._getUniqueKey(t),n=this._getValueObject(e);return n&&n.origin},o.prototype.keys=function(){return new r(this._keys,a.bind(this._getOriginKey,this))},o.prototype.values=function(){return new r(this._keys,a.bind(this._getOriginValue,this))},o.prototype.entries=function(){return new r(this._keys,a.bind(this._getKeyValuePair,this))},o.prototype.has=function(t){return!!this._getValueObject(t)},o.prototype["delete"]=function(t){var e;u.isString(t)?this._valuesForString[t]&&(e=this._valuesForString[t].keyIndex,delete this._valuesForString[t]):(e=this._getKeyIndex(t),e>=0&&delete this._valuesForIndex[e]),e>=0&&(delete this._keys[e],this.size-=1)},o.prototype.forEach=function(t,e){e=e||this,i.forEachArray(this._keys,function(n){u.isUndefined(n)||t.call(e,this._getValueObject(n).origin,n,this)},this)},o.prototype.clear=function(){o.call(this)},function(){window.Map&&(c.firefox&&c.version>=37||c.chrome&&c.version>=42)&&(o=window.Map)}(),t.exports=o},function(t,e,n){"use strict";function r(t){this.length=0,t&&this.setObject(t)}var o=n(4),i=n(2),u="å";r.prototype.set=function(t,e){2===arguments.length?this.setKeyValue(t,e):this.setObject(t)},r.prototype.setKeyValue=function(t,e){this.has(t)||(this.length+=1),this[this.encodeKey(t)]=e},r.prototype.setObject=function(t){var e=this;o.forEachOwnProperties(t,function(t,n){e.setKeyValue(n,t)})},r.prototype.merge=function(t){var e=this;t.each(function(t,n){e.setKeyValue(n,t)})},r.prototype.encodeKey=function(t){return u+t},r.prototype.decodeKey=function(t){var e=t.split(u);return e[e.length-1]},r.prototype.get=function(t){return this[this.encodeKey(t)]},r.prototype.has=function(t){return this.hasOwnProperty(this.encodeKey(t))},r.prototype.remove=function(t){return arguments.length>1&&(t=o.toArray(arguments)),i.isArray(t)?this.removeByKeyArray(t):this.removeByKey(t)},r.prototype.removeByKey=function(t){var e=this.has(t)?this.get(t):null;return null!==e&&(delete this[this.encodeKey(t)],this.length-=1),e},r.prototype.removeByKeyArray=function(t){var e=[],n=this;return o.forEach(t,function(t){e.push(n.removeByKey(t))}),e},r.prototype.removeAll=function(){var t=this;this.each(function(e,n){t.remove(n)})},r.prototype.each=function(t){var e,n=this;o.forEachOwnProperties(this,function(r,o){if(o.charAt(0)===u&&(e=t(r,n.decodeKey(o))),e===!1)return e})},r.prototype.keys=function(){var t=[],e=this;return this.each(function(n,r){t.push(e.decodeKey(r))}),t},r.prototype.find=function(t){var e=[];return this.each(function(n,r){t(n,r)&&e.push(n)}),e},r.prototype.toArray=function(){var t=[];return this.each(function(e){t.push(e)}),t},t.exports=r}])}); -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/vendor/nova/froala/spell_checker.min.js": "/vendor/nova/froala/spell_checker.min.js", 3 | "/vendor/nova/froala/image_tui.min.js": "/vendor/nova/froala/image_tui.min.js", 4 | "/vendor/nova/froala/embedly.min.js": "/vendor/nova/froala/embedly.min.js", 5 | "/js/field.js": "/js/field.js", 6 | "/css/field.css": "/css/field.css", 7 | "/css/froala_styles.min.css": "/css/froala_styles.min.css", 8 | "/js/plugins/tui-image-editor/tui-image-editor.min.js": "/js/plugins/tui-image-editor/tui-image-editor.min.js", 9 | "/js/plugins/tui-image-editor/tui-code-snippet.min.js": "/js/plugins/tui-image-editor/tui-code-snippet.min.js", 10 | "/js/plugins/tui-image-editor/fabric.js": "/js/plugins/tui-image-editor/fabric.js", 11 | "/css/plugins/tui-image-editor/tui-image-editor.min.css": "/css/plugins/tui-image-editor/tui-image-editor.min.css", 12 | "/css/plugins/tui-image-editor/tui-color-picker.min.css": "/css/plugins/tui-image-editor/tui-color-picker.min.css" 13 | } -------------------------------------------------------------------------------- /dist/vendor/nova/froala/embedly.min.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2],{ 2 | 3 | /***/ 315: 4 | /***/ (function(module, exports, __webpack_require__) { 5 | 6 | /*! 7 | * froala_editor v3.2.5-1 (https://www.froala.com/wysiwyg-editor) 8 | * License https://froala.com/wysiwyg-editor/terms/ 9 | * Copyright 2014-2021 Froala Labs 10 | */ 11 | 12 | !function(e,t){ true?t(__webpack_require__(84)):"function"==typeof define&&define.amd?define(["froala-editor"],t):t(e.FroalaEditor)}(this,function(E){"use strict";E=E&&E.hasOwnProperty("default")?E["default"]:E,Object.assign(E.POPUP_TEMPLATES,{"embedly.insert":"[_BUTTONS_][_URL_LAYER_]","embedly.edit":"[_BUTTONS_]"}),Object.assign(E.DEFAULTS,{embedlyKey:null,embedlyInsertButtons:["embedlyBack","|"],embedlyEditButtons:["embedlyRemove"],embedlyScriptPath:"https://cdn.embedly.com/widgets/platform.js"}),E.PLUGINS.embedly=function(o){var i,d,l=o.$;function t(e){o.events.on("html.processGet",s),e&&o.html._setHtml(o.$el,o.html.get()),o.events.$on(o.$el,"click touchend","div.fr-embedly",a),o.events.on("mousedown window.mousedown",b),o.events.on("window.touchmove",c),o.events.on("mouseup window.mouseup",u),o.events.on("commands.mousedown",function(e){0\n '.concat(o.button.buildList(o.opts.embedlyEditButtons),"\n ")},n=o.popups.create("embedly.edit",t);return o.events.$on(o.$wp,"scroll.emebdly-edit",function(){i&&o.popups.isVisible("embedly.edit")&&(o.events.disableBlur(),function t(e){a.call(e.get(0))}(i))}),n}return!1}());if(e){o.popups.setContainer("embedly.edit",o.$sc),o.popups.refresh("embedly.edit");var t=i.offset().left+i.outerWidth()/2,n=i.offset().top+i.outerHeight();o.popups.show("embedly.edit",t,n,i.outerHeight())}}()}function s(e){if(e&&o.node.hasClass(e,"fr-embedly"))e.innerHTML=e.getAttribute("data-original-embed"),e.removeAttribute("draggable"),e.removeAttribute("contenteditable"),e.setAttribute("class",(e.getAttribute("class")||"").replace("fr-draggable",""));else if(e&&e.nodeType==Node.ELEMENT_NODE)for(var t=e.querySelectorAll(".fr-embedly"),n=0;n");var n={buttons:t,url_layer:'
"};return o.popups.create("embedly.insert",n)}function r(){o.popups.get("embedly.insert").find(".fr-embedly-layer input").val("").trigger("change")}function e(e){if(e.length){var t="";o.html.insert('
'+t+"
"),o.popups.hideAll()}}function m(){if(i&&!1!==o.events.trigger("embedly.beforeRemove",[i])){var e=i;o.popups.hideAll(),u(!0),o.selection.setBefore(e.get(0))||o.selection.setAfter(e.get(0)),e.remove(),o.selection.restore(),o.html.fillEmptyBlocks(),o.undo.saveStep(),o.events.trigger("video.removed",[e])}}function u(e){i&&(function t(){return o.shared.embedly_exit_flag}()||!0===e)&&(d.removeClass("fr-active"),o.toolbar.enable(),i.removeClass("fr-active"),i=null,c())}function b(){o.shared.embedly_exit_flag=!0}function c(){o.shared.embedly_exit_flag=!1}return o.shared.embedly_exit_flag=!1,{_init:function p(){if(!o.$wp)return!1;if("undefined"!=typeof embedly)t(!0);else if(o.shared.embedlyLoaded)o.shared.embedlyCallbacks.push(t);else{o.shared.embedlyLoaded=!0,o.shared.embedlyCallbacks=[],o.shared.embedlyCallbacks.push(t);var e=o.doc.createElement("script");e.type="text/javascript",e.src=o.opts.embedlyScriptPath,e.innerText="",e.onload=function(){if(o.shared.embedlyCallbacks)for(var e=0;eCancel '),l(".tui-editor-cancel-btn")[0].addEventListener("click",function(e){g(d,t)}),l(".tui-editor-save-btn")[0].addEventListener("click",function(e){null!=n?p(s,t,o,i,n):p(s,t,o,i),g(d,t)})}}}},e.DefineIcon("imageTUI",{NAME:"sliders",FA5NAME:"sliders-h",SVG_KEY:"advancedImageEditor"}),e.RegisterCommand("imageTUI",{title:"Advanced Edit",undo:!1,focus:!1,callback:function(e,t){this.imageTUI.launch(this,!0)},plugin:"imageTUI"}),!e.PLUGINS.image)throw new Error("TUI image editor plugin requires image plugin.");e.DEFAULTS.imageEditButtons.push("imageTUI")}); 13 | 14 | /***/ }) 15 | 16 | }); -------------------------------------------------------------------------------- /dist/vendor/nova/froala/spell_checker.min.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([0],{ 2 | 3 | /***/ 316: 4 | /***/ (function(module, exports, __webpack_require__) { 5 | 6 | /*! 7 | * froala_editor v3.2.5-1 (https://www.froala.com/wysiwyg-editor) 8 | * License https://froala.com/wysiwyg-editor/terms/ 9 | * Copyright 2014-2021 Froala Labs 10 | */ 11 | 12 | !function(e,t){ true?t(__webpack_require__(84)):"function"==typeof define&&define.amd?define(["froala-editor"],t):t(e.FroalaEditor)}(this,function(p){"use strict";(p=p&&p.hasOwnProperty("default")?p["default"]:p).DEFAULT_SCAYT_OPTIONS={enableOnTouchDevices:!1,disableOptionsStorage:["all"],localization:"en",extraModules:"ui",DefaultSelection:"American English",spellcheckLang:"en_US",contextMenuSections:"suggest|moresuggest",serviceProtocol:"https",servicePort:"80",serviceHost:"svc.webspellchecker.net",servicePath:"spellcheck/script/ssrv.cgi",contextMenuForMisspelledOnly:!0,scriptPath:"https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js"},Object.assign(p.DEFAULTS,{scaytAutoload:!1,scaytCustomerId:"1:ldogw1-MSDuT3-slyfO-0YJgB1-Wx7262-HIT741-MAMDv4-10qfb3-A4LDP-c60m3-hSQgd2-az2",scaytOptions:{}}),p.PLUGINS.spellChecker=function(s){var l;function e(e){if(l&&l.isDisabled){var t=!l.isDisabled();e.toggleClass("fr-active",t).attr("aria-pressed",t),s.$el.attr("spellcheck",s.opts.spellcheck&&!t)}}function t(e){l&&l.isDisabled&&!l.isDisabled()&&0<=["bold","italic","underline","strikeThrough","subscript","superscript","fontFamily","fontSize","html"].indexOf(e)&&l.removeMarkupInSelectionNode({removeInside:!0})}function o(e){l&&l.isDisabled&&!l.isDisabled()&&0<=["bold","italic","underline","strikeThrough","subscript","superscript","fontFamily","fontSize","html"].indexOf(e)&&l.reloadMarkup()}function a(e){l&&l.isDisabled&&!l.isDisabled()&&(e.which==p.KEYCODE.ENTER&&setTimeout(l.reloadMarkup,0))}function i(e){if(e&&e.getAttribute&&e.getAttribute("data-scayt-word"))e.outerHTML=e.innerHTML;else if(e&&e.nodeType==Node.ELEMENT_NODE)for(var t=e.querySelectorAll("[data-scayt-word], [data-spelling-word]"),s=0;s {}) 45 | .catch(error => { 46 | this.notificator.show(error.message, { type: 'error' }); 47 | }); 48 | } 49 | } 50 | 51 | get uploadConfig() { 52 | return { 53 | // Set the image upload parameter. 54 | imageUploadParam: 'attachment', 55 | 56 | // Set the image upload URL. 57 | imageUploadURL: this.adapter.imageUploadUrl, 58 | 59 | // Additional upload params. 60 | imageUploadParams: { 61 | _token: this._token, 62 | draftId: this.field.draftId, 63 | }, 64 | 65 | // Set request type. 66 | imageUploadMethod: 'POST', 67 | }; 68 | } 69 | 70 | get eventsConfig() { 71 | return { 72 | events: { 73 | 'froalaEditor.image.removed': (e, editor, $img) => { 74 | Nova.request() 75 | .delete(this.adapter.imageRemoveUrl, { 76 | params: { attachmentUrl: $img.attr('src') }, 77 | }) 78 | .then(response => {}) 79 | .catch(error => { 80 | this.notificator.show(error.message, { type: 'error' }); 81 | }); 82 | }, 83 | 'froalaEditor.image.error': (e, editor, error, response) => { 84 | try { 85 | response = JSON.parse(response); 86 | 87 | if (typeof response.status !== 'undefined' && response.status === 409) { 88 | this.notificator.show('A file with this name already exists.', { 89 | type: 'error', 90 | }); 91 | 92 | return; 93 | } 94 | } catch (e) {} 95 | 96 | this.notificator.show(error.message, { type: 'error' }); 97 | }, 98 | 'froalaEditor.imageManager.error': (e, editor, error, response) => { 99 | this.notificator.show(error.message, { type: 'error' }); 100 | }, 101 | 'froalaEditor.file.error': (e, editor, error, response) => { 102 | this.notificator.show(error.message, { type: 'error' }); 103 | }, 104 | }, 105 | }; 106 | } 107 | 108 | get imageManagerLoadConfig() { 109 | return { 110 | imageManagerLoadURL: `/nova-vendor/froala-field/${this.resource}/image-manager`, 111 | 112 | imageManagerLoadParams: { 113 | field: this.field.attribute, 114 | }, 115 | }; 116 | } 117 | 118 | get imageManagerDeleteConfig() { 119 | return { 120 | imageManagerDeleteURL: `/nova-vendor/froala-field/${this.resource}/image-manager`, 121 | 122 | imageManagerDeleteMethod: 'DELETE', 123 | 124 | imageManagerDeleteParams: { 125 | _token: this._token, 126 | field: this.field.attribute, 127 | }, 128 | }; 129 | } 130 | 131 | get videoUploadConfig() { 132 | return { 133 | videoUploadURL: this.adapter.videoUploadUrl, 134 | 135 | videoUploadParam: 'attachment', 136 | 137 | videoUploadParams: { 138 | _token: this._token, 139 | draftId: this.field.draftId, 140 | }, 141 | }; 142 | } 143 | 144 | get fileUploadConfig() { 145 | return { 146 | // Set the file upload parameter. 147 | fileUploadParam: 'attachment', 148 | 149 | // Set the file upload URL. 150 | fileUploadURL: this.adapter.fileUploadUrl, 151 | 152 | // Additional upload params. 153 | fileUploadParams: { 154 | _token: this._token, 155 | draftId: this.field.draftId, 156 | }, 157 | 158 | // Set request type. 159 | fileUploadMethod: 'POST', 160 | }; 161 | } 162 | } 163 | 164 | export default MediaConfigurator; 165 | -------------------------------------------------------------------------------- /resources/js/PluginsLoader.js: -------------------------------------------------------------------------------- 1 | class PluginsLoader { 2 | constructor(options, notificator) { 3 | this.options = options; 4 | this.notificator = notificator; 5 | } 6 | 7 | async registerPlugins() { 8 | let allButtons = this.getRequestedButtons(); 9 | 10 | if (_.isEmpty(allButtons)) { 11 | return true; 12 | } 13 | 14 | if (allButtons.includes('embedly')) { 15 | try { 16 | await import( 17 | /* webpackChunkName: "embedly.min" */ 18 | 'froala-editor/js/third_party/embedly.min' 19 | ); 20 | } catch (e) { 21 | this.errorPluginLoadNotification('Embed.ly'); 22 | } 23 | } 24 | 25 | if (allButtons.includes('spellChecker')) { 26 | try { 27 | await import( 28 | /* webpackChunkName: "spell_checker.min" */ 29 | 'froala-editor/js/third_party/spell_checker.min' 30 | ); 31 | } catch (e) { 32 | this.errorPluginLoadNotification('SCAYT Web SpellChecker'); 33 | } 34 | } 35 | 36 | if (this.options.tuiEnable) { 37 | try { 38 | await import( 39 | /* webpackChunkName: "image_tui.min" */ 40 | 'froala-editor/js/third_party/image_tui.min.js' 41 | ); 42 | } catch (e) { 43 | this.errorPluginLoadNotification('TUI Advanced Image Editor'); 44 | } 45 | } 46 | 47 | return true; 48 | } 49 | 50 | getRequestedButtons() { 51 | const props = [ 52 | 'toolbarButtons', 53 | 'toolbarButtonsMD', 54 | 'toolbarButtonsSM', 55 | 'toolbarButtonsXS', 56 | ]; 57 | 58 | let buttons = []; 59 | 60 | for (let prop of props) { 61 | buttons.push(typeof this.options[prop] === 'undefined' ? null : this.options[prop]); 62 | } 63 | 64 | return buttons.flat(2); 65 | } 66 | 67 | errorPluginLoadNotification(name) { 68 | this.notificator.show( 69 | `Something wrong with ${name} plugin load. ` + 'Perhaps you forgot to publish it.', 70 | { type: 'error' } 71 | ); 72 | } 73 | } 74 | 75 | export default PluginsLoader; 76 | -------------------------------------------------------------------------------- /resources/js/TrixAttachmentsAdapter.js: -------------------------------------------------------------------------------- 1 | class TrixAttachmentsAdapter { 2 | constructor(resource, field) { 3 | this.resource = resource; 4 | this.field = field; 5 | } 6 | 7 | get cleanUpUrl() { 8 | return `/nova-api/${this.resource}/trix-attachment/${this.field.attribute}/${this.field.draftId}`; 9 | } 10 | 11 | get imageUploadUrl() { 12 | return `/nova-api/${this.resource}/trix-attachment/${this.field.attribute}`; 13 | } 14 | 15 | get imageRemoveUrl() { 16 | return this.imageUploadUrl; 17 | } 18 | 19 | get vieoUploadUrl() { 20 | return this.imageUploadUrl; 21 | } 22 | 23 | get fileUploadUrl() { 24 | return this.imageUploadUrl; 25 | } 26 | } 27 | 28 | export default TrixAttachmentsAdapter; 29 | -------------------------------------------------------------------------------- /resources/js/components/DetailField.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 54 | -------------------------------------------------------------------------------- /resources/js/components/FormField.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 69 | -------------------------------------------------------------------------------- /resources/js/components/IndexField.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 53 | -------------------------------------------------------------------------------- /resources/js/field.js: -------------------------------------------------------------------------------- 1 | require('froala-editor/js/froala_editor.pkgd.min'); 2 | require('froala-editor/js/plugins.pkgd.min.js'); 3 | 4 | import VueFroala from 'vue-froala-wysiwyg'; 5 | 6 | Nova.booting(Vue => { 7 | Vue.use(VueFroala); 8 | 9 | Vue.component('index-nova-froala-field', require('./components/IndexField')); 10 | Vue.component('detail-nova-froala-field', require('./components/DetailField')); 11 | Vue.component('form-nova-froala-field', require('./components/FormField')); 12 | }); 13 | -------------------------------------------------------------------------------- /resources/sass/field.scss: -------------------------------------------------------------------------------- 1 | @import "~froala-editor/css/froala_editor.pkgd.min.css"; 2 | @import "~froala-editor/css/froala_style.min.css"; 3 | 4 | // Plugins 5 | @import "~froala-editor/css/third_party/embedly.min.css"; 6 | @import "~froala-editor/css/third_party/spell_checker.css"; 7 | @import "~froala-editor/css/third_party/image_tui.min.css"; 8 | 9 | .fr-view-modal { 10 | width: 75vw; 11 | } 12 | 13 | .fr-view-modal__content { 14 | max-height: 80vh; 15 | overflow-y: scroll; 16 | } 17 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | getUploadMaxFilesize(); 46 | 47 | foreach ($uploadLimits as $key => $property) { 48 | $uploadLimits[$property] = $uploadMaxFilesize; 49 | unset($uploadLimits[$key]); 50 | } 51 | 52 | $this->withMeta([ 53 | 'options' => config('nova.froala-field.options', []) + $uploadLimits, 54 | 'draftId' => Str::uuid(), 55 | 'attachmentsDriver' => config('nova.froala-field.attachments_driver'), 56 | ]); 57 | } 58 | 59 | /** 60 | * Determine the server 'upload_max_filesize' as bytes. 61 | * 62 | * @return int 63 | */ 64 | protected function getUploadMaxFilesize(): int 65 | { 66 | $uploadMaxFilesize = config('nova.froala-field.upload_max_filesize') 67 | ?? ini_get('upload_max_filesize'); 68 | 69 | if (is_numeric($uploadMaxFilesize)) { 70 | return $uploadMaxFilesize; 71 | } 72 | 73 | $metric = strtoupper(substr($uploadMaxFilesize, -1)); 74 | $uploadMaxFilesize = (int) $uploadMaxFilesize; 75 | 76 | switch ($metric) { 77 | case 'K': 78 | return $uploadMaxFilesize * 1024; 79 | case 'M': 80 | return $uploadMaxFilesize * 1048576; 81 | case 'G': 82 | return $uploadMaxFilesize * 1073741824; 83 | default: 84 | return $uploadMaxFilesize; 85 | } 86 | } 87 | 88 | /** 89 | * Ability to pass any existing Froala options to the editor instance. 90 | * Refer to the Froala documentation {@link https://www.froala.com/wysiwyg-editor/docs/options} 91 | * to view a list of all available options. 92 | * 93 | * @param array $options 94 | * @return self 95 | */ 96 | public function options(array $options) 97 | { 98 | return $this->withMeta([ 99 | 'options' => array_merge($this->meta['options'], $options), 100 | ]); 101 | } 102 | 103 | /** 104 | * Specify that file uploads should not be allowed. 105 | */ 106 | public function withFiles($disk = null, $path = '/') 107 | { 108 | $this->withFiles = true; 109 | 110 | if (nova_version_at_least('2.7.0')) { 111 | $this->disk($disk)->path($path); 112 | } else { 113 | $this->disk($disk); 114 | } 115 | 116 | if (config('nova.froala-field.attachments_driver', self::DRIVER_NAME) !== self::DRIVER_NAME) { 117 | $this->images(new AttachedImagesList($this)); 118 | 119 | return parent::withFiles($disk, $path); 120 | } 121 | 122 | $this->attach(new StorePendingAttachment($this)) 123 | ->detach(new DetachAttachment) 124 | ->delete(new DeleteAttachments($this)) 125 | ->discard(new DiscardPendingAttachments) 126 | ->images(new AttachedImagesList($this)) 127 | ->prunable(); 128 | 129 | return $this; 130 | } 131 | 132 | /** 133 | * Hydrate the given attribute on the model based on the incoming request. 134 | * 135 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 136 | * @param string $requestAttribute 137 | * @param object $model 138 | * @param string $attribute 139 | * @return \Closure|null 140 | */ 141 | protected function fillAttribute(NovaRequest $request, $requestAttribute, $model, $attribute) 142 | { 143 | if (isset($this->fillCallback)) { 144 | return call_user_func( 145 | $this->fillCallback, 146 | $request, 147 | $model, 148 | $attribute, 149 | $requestAttribute 150 | ); 151 | } 152 | 153 | $this->fillAttributeFromRequest( 154 | $request, 155 | $requestAttribute, 156 | $model, 157 | $attribute 158 | ); 159 | 160 | if ($request->{$this->attribute.'DraftId'} && $this->withFiles) { 161 | $pendingAttachmentClass = 162 | config('nova.froala-field.attachments_driver', self::DRIVER_NAME) === self::DRIVER_NAME 163 | ? FroalaPendingAttachment::class 164 | : TrixPendingAttachment::class; 165 | 166 | return function () use ($request, $model, $pendingAttachmentClass) { 167 | $pendingAttachmentClass::persistDraft( 168 | $request->{$this->attribute.'DraftId'}, 169 | $this, 170 | $model 171 | ); 172 | }; 173 | } 174 | } 175 | 176 | /** 177 | * Specify the callback that should be used to get attached images list. 178 | * 179 | * @param callable $imagesCallback 180 | * @return $this 181 | */ 182 | public function images(callable $imagesCallback) 183 | { 184 | $this->withFiles = true; 185 | 186 | $this->imagesCallback = $imagesCallback; 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * Get the path that the field is stored at on disk. 193 | * 194 | * @return string|null 195 | */ 196 | public function getStorageDir() 197 | { 198 | return $this->storagePath ?? '/'; 199 | } 200 | 201 | /** 202 | * Get the full path that the field is stored at on disk. 203 | * 204 | * @return string|null 205 | */ 206 | public function getStoragePath() 207 | { 208 | return '/'; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/FroalaFieldServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->booted(function () { 24 | $this->routes(); 25 | }); 26 | 27 | Nova::serving(function (ServingNova $event) use ($froalaPlugins) { 28 | Nova::script('nova-froala-field', static::ASSETS_DIST_DIRECTORY.'/js/field.js'); 29 | Nova::style('nova-froala-field', static::ASSETS_DIST_DIRECTORY.'/css/field.css'); 30 | 31 | $froalaPlugins->import(); 32 | }); 33 | 34 | $this->registerPublishables(); 35 | } 36 | 37 | /** 38 | * Register the card's routes. 39 | * 40 | * @return void 41 | */ 42 | protected function routes() 43 | { 44 | if ($this->app->routesAreCached()) { 45 | return; 46 | } 47 | 48 | Route::middleware(['nova']) 49 | ->prefix('nova-vendor/froala-field') 50 | ->group(__DIR__.'/../routes/api.php'); 51 | } 52 | 53 | /** 54 | * Register any application services. 55 | * 56 | * @return void 57 | */ 58 | public function register() 59 | { 60 | $this->mergeConfigFrom(__DIR__.'/../config/froala-field.php', 'nova.froala-field'); 61 | 62 | $this->app->bind(FroalaPlugins::class, FroalaPluginsManager::class); 63 | 64 | if (config('nova.froala-field.attachments_driver') === 'trix') { 65 | $this->app->bind(TrixAttachmentController::class, FroalaToTrixAttachmentAdapterController::class); 66 | } 67 | } 68 | 69 | private function registerPublishables(): void 70 | { 71 | $this->publishes([ 72 | __DIR__.'/../dist/vendor/nova/froala' => public_path('vendor/nova/froala'), 73 | ], 'nova-froala-field-plugins'); 74 | 75 | $this->publishes([ 76 | __DIR__.'/../dist/css/froala_styles.min.css' => public_path('css/vendor/froala_styles.min.css'), 77 | ], 'froala-styles'); 78 | 79 | $this->publishes([ 80 | __DIR__.'/../config/froala-field.php' => config_path('nova/froala-field.php'), 81 | ], 'config'); 82 | 83 | if (! class_exists('CreateFroalaAttachmentTables')) { 84 | $timestamp = date('Y_m_d_His', time()); 85 | 86 | $this->publishes([ 87 | __DIR__.'/../database/migrations/create_froala_attachment_tables.php.stub' => database_path('migrations/'.$timestamp.'_create_froala_attachment_tables.php'), 88 | ], 'migrations'); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/FroalaPlugins.php: -------------------------------------------------------------------------------- 1 | config = $config; 16 | } 17 | 18 | private function importTuiImageManager(): self 19 | { 20 | if ($this->config->get('nova.froala-field.options.tuiEnable')) { 21 | Nova::style( 22 | 'tui-editor', 23 | FroalaFieldServiceProvider::ASSETS_DIST_DIRECTORY.'/css/plugins/tui-image-editor/tui-image-editor.min.css' 24 | ); 25 | Nova::style( 26 | 'tui-color-picker', 27 | FroalaFieldServiceProvider::ASSETS_DIST_DIRECTORY.'/css/plugins/tui-image-editor/tui-color-picker.min.css' 28 | ); 29 | 30 | Nova::script( 31 | 'fabric', 32 | FroalaFieldServiceProvider::ASSETS_DIST_DIRECTORY.'/js/plugins/tui-image-editor/fabric.js' 33 | ); 34 | Nova::script( 35 | 'tui-codesnippet', 36 | FroalaFieldServiceProvider::ASSETS_DIST_DIRECTORY.'/js/plugins/tui-image-editor/tui-code-snippet.min.js' 37 | ); 38 | Nova::script( 39 | 'tui-image-editor', 40 | FroalaFieldServiceProvider::ASSETS_DIST_DIRECTORY.'/js/plugins/tui-image-editor/tui-image-editor.min.js' 41 | ); 42 | } 43 | 44 | return $this; 45 | } 46 | 47 | private function importFontAwesome(): self 48 | { 49 | if (Str::startsWith( 50 | $this->config->get('nova.froala-field.options.iconsTemplate'), 51 | 'font_awesome_5' 52 | )) { 53 | Nova::script('font-awesome', 'https://use.fontawesome.com/releases/v5.0.8/js/all.js'); 54 | } 55 | 56 | return $this; 57 | } 58 | 59 | public function import() 60 | { 61 | $this->importTuiImageManager() 62 | ->importFontAwesome(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Handlers/AttachedImagesList.php: -------------------------------------------------------------------------------- 1 | field = $field; 28 | } 29 | 30 | /** 31 | * Attach a pending attachment to the field. 32 | */ 33 | public function __invoke(Request $request): array 34 | { 35 | $images = []; 36 | 37 | $disk = Storage::disk($this->field->disk); 38 | 39 | foreach ($disk->allFiles() as $file) { 40 | if (! app()->runningUnitTests() && Str::before((string) $disk->getMimetype($file), '/') !== 'image') { 41 | continue; 42 | } 43 | 44 | $url = $disk->url($file); 45 | $images[] = [ 46 | 'url' => $url, 47 | 'thumb' => $url, 48 | ]; 49 | } 50 | 51 | return $images; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Handlers/DeleteAttachments.php: -------------------------------------------------------------------------------- 1 | field = $field; 26 | } 27 | 28 | /** 29 | * Delete the attachments associated with the field. 30 | * 31 | * @param \Illuminate\Http\Request $request 32 | * @param mixed $model 33 | * @return array 34 | */ 35 | public function __invoke(Request $request, $model) 36 | { 37 | Attachment::where('attachable_type', get_class($model)) 38 | ->where('attachable_id', $model->getKey()) 39 | ->get() 40 | ->each 41 | ->purge(); 42 | 43 | return [$this->field->attribute => '']; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Handlers/DetachAttachment.php: -------------------------------------------------------------------------------- 1 | src) 19 | ->get() 20 | ->each 21 | ->purge(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Handlers/DiscardPendingAttachments.php: -------------------------------------------------------------------------------- 1 | draftId) 19 | ->get() 20 | ->each 21 | ->purge(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Handlers/StorePendingAttachment.php: -------------------------------------------------------------------------------- 1 | field = $field; 30 | } 31 | 32 | /** 33 | * Attach a pending attachment to the field. 34 | * 35 | * @param \Illuminate\Http\Request $request 36 | * @return string 37 | */ 38 | public function __invoke(Request $request) 39 | { 40 | $this->abortIfFileNameExists($request); 41 | 42 | $attachment = PendingAttachment::create([ 43 | 'draft_id' => $request->draftId, 44 | 'attachment' => config('nova.froala-field.preserve_file_names') 45 | ? $request->attachment->storeAs( 46 | $this->field->getStorageDir(), 47 | $request->attachment->getClientOriginalName(), 48 | $this->field->disk 49 | ) : $request->attachment->store($this->field->getStorageDir(), $this->field->disk), 50 | 'disk' => $this->field->disk, 51 | ])->attachment; 52 | 53 | $this->imageOptimize($attachment); 54 | 55 | return Storage::disk($this->field->disk)->url($attachment); 56 | } 57 | 58 | protected function abortIfFileNameExists(Request $request): void 59 | { 60 | $path = rtrim($this->field->getStorageDir(), '/').'/'.$request->attachment->getClientOriginalName(); 61 | 62 | if (config('nova.froala-field.preserve_file_names') 63 | && Storage::disk($this->field->disk) 64 | ->exists($path) 65 | ) { 66 | abort(response()->json([ 67 | 'status' => Response::HTTP_CONFLICT, 68 | ], Response::HTTP_CONFLICT)); 69 | } 70 | } 71 | 72 | protected function imageOptimize(string $attachment): void 73 | { 74 | if (config('nova.froala-field.optimize_images')) { 75 | $optimizerChain = OptimizerChainFactory::create(); 76 | 77 | if (count($optimizers = config('nova.froala-field.image_optimizers'))) { 78 | $optimizers = array_map( 79 | function (array $optimizerOptions, string $optimizerClassName) { 80 | return (new $optimizerClassName)->setOptions($optimizerOptions); 81 | }, 82 | $optimizers, 83 | array_keys($optimizers) 84 | ); 85 | 86 | $optimizerChain->setOptimizers($optimizers); 87 | } 88 | 89 | $optimizerChain->optimize(Storage::disk($this->field->disk)->path($attachment)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Http/Controllers/FroalaImageManagerController.php: -------------------------------------------------------------------------------- 1 | newResource() 13 | ->availableFields($request) 14 | ->findFieldByAttribute($request->field, function () { 15 | abort(404); 16 | }); 17 | 18 | return call_user_func( 19 | $field->imagesCallback, 20 | $request 21 | ); 22 | } 23 | 24 | public function destroy(NovaRequest $request) 25 | { 26 | if (config('nova.froala-field.attachments_driver') !== Froala::DRIVER_NAME) { 27 | $request->replace(['attachmentUrl' => $request->input('src')] + $request->except('src')); 28 | } 29 | 30 | $field = $request->newResource() 31 | ->availableFields($request) 32 | ->findFieldByAttribute($request->field, function () { 33 | abort(404); 34 | }); 35 | 36 | call_user_func( 37 | $field->detachCallback, 38 | $request 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Http/Controllers/FroalaToTrixAttachmentAdapterController.php: -------------------------------------------------------------------------------- 1 | setData([ 21 | 'link' => $response->getData()->url, 22 | ]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Controllers/FroalaUploadController.php: -------------------------------------------------------------------------------- 1 | newResource() 19 | ->availableFields($request) 20 | ->findFieldByAttribute($request->field, function () { 21 | abort(404); 22 | }); 23 | 24 | return response()->json(['link' => call_user_func( 25 | $field->attachCallback, 26 | $request 27 | )]); 28 | } 29 | 30 | /** 31 | * Delete a single, persisted attachment for a Trix field by URL. 32 | * 33 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 34 | * @return \Illuminate\Http\Response 35 | */ 36 | public function destroyAttachment(NovaRequest $request) 37 | { 38 | $field = $request->newResource() 39 | ->availableFields($request) 40 | ->findFieldByAttribute($request->field, function () { 41 | abort(404); 42 | }); 43 | 44 | return call_user_func($field->detachCallback, $request); 45 | } 46 | 47 | /** 48 | * Purge all pending attachments for a Trix field. 49 | * 50 | * @param \Laravel\Nova\Http\Requests\NovaRequest $request 51 | * @return \Illuminate\Http\Response 52 | */ 53 | public function destroyPending(NovaRequest $request) 54 | { 55 | $field = $request->newResource() 56 | ->availableFields($request) 57 | ->findFieldByAttribute($request->field, function () { 58 | abort(404); 59 | }); 60 | 61 | return call_user_func($field->discardCallback, $request); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Jobs/PruneStaleAttachments.php: -------------------------------------------------------------------------------- 1 | subDays(1)) 17 | ->orderBy('id', 'desc') 18 | ->chunk(100, function ($attachments) { 19 | $attachments->each->purge(); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Models/Attachment.php: -------------------------------------------------------------------------------- 1 | disk)->delete($this->attachment); 33 | 34 | $this->delete(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Models/PendingAttachment.php: -------------------------------------------------------------------------------- 1 | get()->each->persist($field, $model); 36 | } 37 | 38 | /** 39 | * Persist the pending attachment. 40 | * 41 | * @param \Froala\NovaFroalaField\Froala $field 42 | * @param mixed $model 43 | * @return void 44 | * @throws \Exception 45 | */ 46 | public function persist(Froala $field, $model) 47 | { 48 | Attachment::create([ 49 | 'attachable_type' => get_class($model), 50 | 'attachable_id' => $model->getKey(), 51 | 'attachment' => $this->attachment, 52 | 'disk' => $field->disk, 53 | 'url' => Storage::disk($field->disk)->url($this->attachment), 54 | ]); 55 | 56 | $this->delete(); 57 | } 58 | 59 | /** 60 | * Purge the attachment. 61 | * 62 | * @return void 63 | * @throws \Exception 64 | */ 65 | public function purge() 66 | { 67 | Storage::disk($this->disk)->delete($this->attachment); 68 | 69 | $this->delete(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | = 0; 10 | } 11 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | let distPath = 'dist'; 4 | 5 | mix.setPublicPath(distPath) 6 | .js('resources/js/field.js', 'js') 7 | .sass('resources/sass/field.scss', 'css') 8 | .copy( 9 | 'node_modules/froala-editor/css/froala_style.min.css', 10 | distPath + '/css/froala_styles.min.css' 11 | ) 12 | // Copy 3rd parties 13 | .copy( 14 | 'node_modules/tui-image-editor/dist/tui-image-editor.min.js', 15 | distPath + '/js/plugins/tui-image-editor' 16 | ) 17 | .copy( 18 | 'node_modules/tui-code-snippet/dist/tui-code-snippet.min.js', 19 | distPath + '/js/plugins/tui-image-editor' 20 | ) 21 | .copy( 22 | 'node_modules/fabric/dist/fabric.js', 23 | distPath + '/js/plugins/tui-image-editor/fabric.js' 24 | ) 25 | .copy( 26 | 'node_modules/tui-image-editor/dist/tui-image-editor.min.css', 27 | distPath + '/css/plugins/tui-image-editor' 28 | ) 29 | .copy( 30 | 'node_modules/tui-color-picker/dist/tui-color-picker.min.css', 31 | distPath + '/css/plugins/tui-image-editor' 32 | ) 33 | // ----------------- 34 | .webpackConfig({ 35 | output: { 36 | publicPath: '/', 37 | chunkFilename: 'vendor/nova/froala/[name].js', 38 | }, 39 | resolve: { 40 | symlinks: false 41 | }, 42 | }); 43 | --------------------------------------------------------------------------------