froala / nova-froala-field

A Laravel Nova Froala WYSIWYG Editor Field.
MIT License
113 stars 46 forks source link

Custom third party plugins #6

Closed jeffreyzant closed 5 years ago

jeffreyzant commented 5 years ago

Is it possible to include custom third party Froala plugins? So this means not only embedly etc.

Sadly, I can't find anything for this in the documentation.

slavarazum commented 5 years ago

How do you use a custom plugin with Vue Froala Wysiwyg ?

jeffreyzant commented 5 years ago

Hey, thanks for replying. I suppose by adding them to the webpack file. But that would mean i'll have to fork this and change the package. There has to be a better way to do this? I've looked in the repo you mentioned but I could not find any helpful examples.

I've tried a couple of things to make it work, but maybe i'm just missing out on a small piece.

slavarazum commented 5 years ago

To help with this question, I need to know how you created a custom plugin and use it without Nova. Maybe you used specific tutorial etc. In Nova you can add additional scripts and styles like this:

Nova::serving(function (ServingNova $event) {
       Nova::style('custom-styles', public_path('css/nova/custom-styles.css'));
       Nova::script('custom-script', public_path('js/nova/custom-script.js'));
});
jeffreyzant commented 5 years ago

I am trying to use this plugin: https://github.com/ecoach-lms/froala-audio

I have tried adding the .css and .js after I ran the javascript through webpack to add jQuery. But I am receiving a JS error that the Froala Editor is not found (or not accessible).

froala-audio-script:87 Uncaught TypeError: Cannot read property 'POPUP_TEMPLATES' of undefined
    at froala-audio-script:87
    at Object.<anonymous> (froala-audio-script:815)
    at Object.405 (froala-audio-script:816)
    at __webpack_require__ (froala-audio-script:20)
    at Object.404 (froala-audio-script:71)
    at __webpack_require__ (froala-audio-script:20)
    at froala-audio-script:63
    at froala-audio-script:66
  $.extend($.FE.POPUP_TEMPLATES, {
    'audio.insert': '[_BUTTONS_][_BY_URL_LAYER_][_UPLOAD_LAYER_][_PROGRESS_BAR_]',
    'audio.edit': '[_BUTTONS_]'
  });

I'm using the following code:

mix.styles('node_modules/froala-audio/froala-audio.css', 'public/css/vendor/froala_audio.min.css');
mix.js('node_modules/froala-audio/froala-audio.js', 'public/js/vendor/froala_audio.min.js')
   .autoload({
       jquery: ['$', 'jQuery', 'jquery', 'window.jQuery'],
   });`
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();

        Nova::serving(function (ServingNova $event) {
            Nova::style('froala-audio-styles', public_path('css/vendor/froala_audio.min.css'));
            Nova::script('froala-audio-script', public_path('js/vendor/froala_audio.min.js'));
        });
    }
slavarazum commented 5 years ago

Looks like this plugin not working properly with vue-froala-wysiwyg. I have tried to include it directly into the nova-froala-field package (which use vue-froala-wysiwyg) and it crashed with TypeError: $ is undefined. It depends on jQuery without any checks.

jeffreyzant commented 5 years ago

That's cause within your webpack.mix.js you are only including jQuery on $ and on jQuery. But this plugin requires it as window.jQuery. You'll only have to add that to your Webpack file to make that part (jQuery) work..

jeffreyzant commented 5 years ago

Just npm install froala-audio Add this to the PluginsLoader.js

     await import(
            'froala-audio/froala-audio'
     )

Change the webpack file to:

           new webpack.ProvidePlugin({
               $: "jquery",
               jQuery: "jquery",
               "window.jQuery": "jquery",
           })

npm run production

And it will work.

slavarazum commented 5 years ago

You are right. But as I can see webpack has some limitations for dynamic imports to be able to easy import of custom plugins without changing PluginsLoader.js directly.

Will investigate this question deeper and welcome any implementation ideas here or in PR.

jeffreyzant commented 5 years ago

I've thought about a couple of solutions, but I don't think it's doable without compiling the assets outside the package. Would using an Artisan command to (re)compile the assets and publishing them to the /public folder be a likeable solution?

slavarazum commented 5 years ago

@jeffreyzant Will try to find a less complex solution

klimov-paul commented 5 years ago

There is no way for simple integration of the 3rd party or hand-made plugin. You'll have to re-compile the assets of this extension with manual adjustments.

I have developed the following console command to do so:

<?php

namespace App\Nova\Extensions\Froala\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

/**
 * BuildFroalaAssets builds custom assets for "froala/nova-froala-field" extension.
 *
 * Unfortunally this is the only way to apply custom plugins to the Froala editor.
 *
 * ```php
 * class NovaServiceProvider extends NovaApplicationServiceProvider
 * {
 *     public function boot()
 *     {
 *         parent::boot();
 *
 *         Nova::serving(function (ServingNova $event) {
 *             Nova::script('nova-froala-field', storage_path('nova/froala/dist/js/field.js'));
 *         });
 *     }
 *
 *     public function register()
 *     {
 *         $this->commands([
 *             BuildFroalaAssets::class,
 *         ]);
 *     }
 * }
 * ```
 *
 * @see https://novapackages.com/packages/froala/nova-froala-field
 */
class BuildFroalaAssets extends Command
{
    /**
     * {@inheritdoc}
     */
    protected $signature = 'nova:build-froala-assets';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Build custom assets for Nova Froala editor field';

    /**
     * @var string path to the Nova Froala field extension.
     */
    protected $srcPath;

    /**
     * @var string path used for the assets build.
     */
    protected $buildPath;

    /**
     * {@inheritdoc}
     */
    public function __construct()
    {
        parent::__construct();

        $this->srcPath = base_path('vendor/froala/nova-froala-field');
        $this->buildPath = storage_path('nova/froala');
    }

    public function handle()
    {
        $this->info('Building Froala assets...');

        $this->prepareSourceFiles();

        $this->patchSourceFiles();

        $this->buildAssets();

        $this->info('...complete.');
    }

    protected function prepareSourceFiles()
    {
        $this->info('Prepare source files...');

        if (! file_exists($this->buildPath)) {
            File::makeDirectory($this->buildPath, 0775, true);
        }

        $names = [
            'resources',
            '.babelrc',
            'package.json',
            'webpack.mix.js',
        ];

        foreach ($names as $name) {
            $srcName = $this->srcPath.DIRECTORY_SEPARATOR.$name;
            $dstName = $this->buildPath.DIRECTORY_SEPARATOR.$name;
            if (is_dir($srcName)) {
                File::copyDirectory($srcName, $dstName);
            } else {
                File::copy($srcName, $dstName);
            }
        }

        $resourcePath = realpath(__DIR__.'/../..');

        $names = [
            'resources/js/custom-plugins.js',
        ];

        foreach ($names as $name) {
            File::copy($resourcePath.DIRECTORY_SEPARATOR.$name, $this->buildPath.DIRECTORY_SEPARATOR.$name);
        }

        $this->info('...done.');
    }

    protected function patchSourceFiles()
    {
        $this->info('Patching source files...');

        $fieldSrcFile = $this->buildPath.'/resources/js/field.js';
        $patchContent = "\n\nrequire('./custom-plugins');";

        file_put_contents($fieldSrcFile, file_get_contents($fieldSrcFile).$patchContent);

        $this->info('...done.');
    }

    protected function buildAssets()
    {
        $this->info('Building assets...');

        passthru('(cd '.escapeshellarg($this->buildPath).'; yarn install; yarn run prod;)');
    }
}

Custom plugin JS code placed at 'custom-plugins.js' may look like following:

/**
 * @see https://www.froala.com/wysiwyg-editor/docs/concepts/custom/button
 */
import FroalaEditor from 'froala-editor';

FroalaEditor.DefineIcon('insert', {NAME: 'plus', SVG_KEY: 'add'});
FroalaEditor.RegisterCommand('insert', {
    title: 'Insert HTML',
    focus: true,
    undo: true,
    refreshAfterCallback: true,
    callback: function () {
        this.html.insert('My New HTML');
    }
});
slavarazum commented 5 years ago

Great! I think it will be useful If you need to integrate a custom plugin.

klimov-paul commented 5 years ago

See https://github.com/illuminatech/override-build