From ab52b44eac12f902d0e39b617ed598703ff8959f Mon Sep 17 00:00:00 2001 From: Al Ganiev Date: Sun, 29 Aug 2021 19:04:05 +1000 Subject: [PATCH] Tinymce5 (#455) * Added symfony.bundle conf * Added bundle conf * Adding tinymce 5 --- .github/workflows/test.yaml | 2 +- composer.json | 2 +- src/Resources/public/tinymceElfinder.js | 207 ++++++++++++++++++ .../views/Elfinder/helper/_tinymce5.html.twig | 9 + src/Twig/Extension/FMElfinderExtension.php | 46 ++-- tests/Form/Type/ElFinderTypeTest.php | 25 --- .../Extension/FMElfinderExtensionTest.php | 15 +- 7 files changed, 240 insertions(+), 66 deletions(-) create mode 100644 src/Resources/public/tinymceElfinder.js create mode 100644 src/Resources/views/Elfinder/helper/_tinymce5.html.twig diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c114549..5275886 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: continue-on-error: false strategy: matrix: - php-version: ['7.2.5', '7.3', '7.4', '8.0'] + php-version: ['7.4', '8.0'] steps: - name: 'Checkout code' uses: actions/checkout@v2.3.3 diff --git a/composer.json b/composer.json index 10c70c7..ece38e2 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "exclude": ["/tests", "./github"] }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "ext-json": "*", "symfony/framework-bundle": "^4.4 || ^5.0 || ^5.2", "symfony/twig-bundle": "^4.4 || ^5.0 || ^5.2", diff --git a/src/Resources/public/tinymceElfinder.js b/src/Resources/public/tinymceElfinder.js new file mode 100644 index 0000000..82e04eb --- /dev/null +++ b/src/Resources/public/tinymceElfinder.js @@ -0,0 +1,207 @@ +window.tinymceElfinder = function(opts) { + // elFinder node + let elfNode = $('
'); + if (opts.nodeId) { + elfNode.attr('id', opts.nodeId); + delete opts.nodeId; + } + + // upload target folder hash + const uploadTargetHash = opts.uploadTargetHash || 'L1_Lw'; + delete opts.uploadTargetHash; + + // get elFinder insrance + const getfm = open => { + // CSS class name of TinyMCE conntainer + const cls = (tinymce.majorVersion < 5)? 'mce-container' : 'tox'; + return new Promise((resolve, reject) => { + // elFinder instance + let elf; + + // Execute when the elFinder instance is created + const done = () => { + if (open) { + // request to open folder specify + if (!Object.keys(elf.files()).length) { + // when initial request + elf.one('open', () => { + elf.file(open)? resolve(elf) : reject(elf, 'errFolderNotFound'); + }); + } else { + // elFinder has already been initialized + new Promise((res, rej) => { + if (elf.file(open)) { + res(); + } else { + // To acquire target folder information + elf.request({cmd: 'parents', target: open}).done(e =>{ + elf.file(open)? res() : rej(); + }).fail(() => { + rej(); + }); + } + }).then(() => { + if (elf.cwd().hash == open) { + resolve(elf); + } else { + // Open folder after folder information is acquired + elf.exec('open', open).done(() => { + resolve(elf); + }).fail(err => { + reject(elf, err? err : 'errFolderNotFound'); + }); + } + }).catch((err) => { + reject(elf, err? err : 'errFolderNotFound'); + }); + } + } else { + // show elFinder manager only + resolve(elf); + } + }; + + // Check elFinder instance + if (elf = elfNode.elfinder('instance')) { + // elFinder instance has already been created + done(); + } else { + // To create elFinder instance + elf = elfNode.dialogelfinder(Object.assign({ + // dialog title + title : 'File Manager', + // start folder setting + startPathHash : open? open : void(0), + // Set to do not use browser history to un-use location.hash + useBrowserHistory : false, + // Disable auto open + autoOpen : false, + // elFinder dialog width + width : '90%', + // elFinder dialog height + height : '90%', + // set getfile command options + commandsOptions : { + getfile: { + oncomplete : 'close' + } + }, + bootCallback : (fm) => { + // set z-index + fm.getUI().css('z-index', parseInt($('body>.'+cls+':last').css('z-index')) + 100); + }, + getFileCallback : (files, fm) => {} + }, opts)).elfinder('instance'); + done(); + } + }); + }; + + this.browser = function(callback, value, meta) { + getfm().then(fm => { + let cgf = fm.getCommand('getfile'); + const regist = () => { + fm.options.getFileCallback = cgf.callback = (file, fm) => { + var url, reg, info; + + // URL normalization + url = fm.convAbsUrl(file.url); + + // Make file info + info = file.name + ' (' + fm.formatSize(file.size) + ')'; + + // Provide file and text for the link dialog + if (meta.filetype == 'file') { + callback(url, {text: info, title: info}); + } + + // Provide image and alt text for the image dialog + if (meta.filetype == 'image') { + callback(url, {alt: info}); + } + + // Provide alternative source and posted for the media dialog + if (meta.filetype == 'media') { + callback(url); + } + }; + fm.getUI().dialogelfinder('open'); + }; + if (cgf) { + // elFinder booted + regist(); + } else { + // elFinder booting now + fm.bind('init', () => { + cgf = fm.getCommand('getfile'); + regist(); + }); + } + }); + + return false; + }; + + this.uploadHandler = function (blobInfo, success, failure) { + new Promise(function(resolve, reject) { + getfm(uploadTargetHash).then(fm => { + let fmNode = fm.getUI(), + file = blobInfo.blob(), + clipdata = true; + const err = (e) => { + var dlg = e.data.dialog || {}; + if (dlg.hasClass('elfinder-dialog-error') || dlg.hasClass('elfinder-confirm-upload')) { + fmNode.dialogelfinder('open'); + fm.unbind('dialogopened', err); + } + }, + closeDlg = () => { + if (!fm.getUI().find('.elfinder-dialog-error:visible,.elfinder-confirm-upload:visible').length) { + fmNode.dialogelfinder('close'); + } + }; + + // check file object + if (file.name) { + // file blob of client side file object + clipdata = void(0); + } + // Bind err function and exec upload + fm.bind('dialogopened', err).exec('upload', { + files: [file], + target: uploadTargetHash, + clipdata: clipdata, // to get unique name on connector + dropEvt: {altKey: true, ctrlKey: true} // diable watermark on demo site + }, void(0), uploadTargetHash) + .done(data => { + if (data.added && data.added.length) { + fm.url(data.added[0].hash, { async: true }).done(function(url) { + // prevent to use browser cache + url += (url.match(/\?/)? '&' : '?') + '_t=' + data.added[0].ts; + resolve(fm.convAbsUrl(url)); + }).fail(function() { + reject(fm.i18n('errFileNotFound')); + }); + } else { + reject(fm.i18n(data.error? data.error : 'errUpload')); + } + }) + .fail(err => { + const error = fm.parseError(err); + reject(fm.i18n(error? (error === 'userabort'? 'errAbort' : error) : 'errUploadNoFiles')); + }) + .always(() => { + fm.unbind('dialogopened', err); + closeDlg(); + }); + }).catch((fm, err) => { + const error = fm.parseError(err); + reject(fm.i18n(error? (error === 'userabort'? 'errAbort' : error) : 'errUploadNoFiles')); + }); + }).then((url) => { + success(url); + }).catch((err) => { + failure(err); + }); + }; +}; diff --git a/src/Resources/views/Elfinder/helper/_tinymce5.html.twig b/src/Resources/views/Elfinder/helper/_tinymce5.html.twig new file mode 100644 index 0000000..33fc5b6 --- /dev/null +++ b/src/Resources/views/Elfinder/helper/_tinymce5.html.twig @@ -0,0 +1,9 @@ + diff --git a/src/Twig/Extension/FMElfinderExtension.php b/src/Twig/Extension/FMElfinderExtension.php index 6f04f6d..f9de8ec 100644 --- a/src/Twig/Extension/FMElfinderExtension.php +++ b/src/Twig/Extension/FMElfinderExtension.php @@ -11,10 +11,8 @@ class FMElfinderExtension extends AbstractExtension { - /** - * @var Environment - */ - protected $twig; + + protected Environment $twig; public function __construct(Environment $twig) { @@ -33,21 +31,19 @@ public function getFunctions() return [ new TwigFunction('elfinder_tinymce_init', [$this, 'tinymce'], $options), new TwigFunction('elfinder_tinymce_init4', [$this, 'tinymce4'], $options), + new TwigFunction('elfinder_tinymce_init4', [$this, 'tinymce5'], $options), new TwigFunction('elfinder_summernote_init', [$this, 'summernote'], $options), ]; } /** - * @param string $instance - * @param array $parameters - * * @return mixed * * @throws LoaderError * @throws RuntimeError * @throws SyntaxError */ - public function tinymce($instance = 'default', $parameters = ['width' => 900, 'height' => 450, 'title' => 'elFinder 2.0']) + public function tinymce(string $instance = 'default', array $parameters = ['width' => 900, 'height' => 450, 'title' => 'elFinder 2.0']) { if (!is_string($instance)) { throw new RuntimeError('The function can be applied to strings only.'); @@ -65,21 +61,12 @@ public function tinymce($instance = 'default', $parameters = ['width' => 900, 'h } /** - * @param string $instance - * @param array $parameters - * - * @return mixed - * * @throws LoaderError * @throws RuntimeError * @throws SyntaxError */ - public function tinymce4($instance = 'default', $parameters = ['width' => 900, 'height' => 450, 'title' => 'elFinder 2.0']) + public function tinymce4(string $instance = 'default', array $parameters = ['width' => 900, 'height' => 450, 'title' => 'elFinder 2.0']): string { - if (!is_string($instance)) { - throw new RuntimeError('The function can be applied to strings only.'); - } - return $this->twig->render( '@FMElfinder/Elfinder/helper/_tinymce4.html.twig', [ @@ -91,23 +78,22 @@ public function tinymce4($instance = 'default', $parameters = ['width' => 900, ' ); } + public function tinymce5(string $instance = 'default'): string + { + return $this->twig->render( + '@FMElfinder/Elfinder/helper/_tinymce5.html.twig', [ + 'instance' => $instance, + ] + ); + } + /** - * @param string $instance - * @param string $selector - * @param array $parameters - * - * @return mixed - * * @throws LoaderError * @throws RuntimeError * @throws SyntaxError */ - public function summernote($instance = 'default', $selector = '.summenote', $parameters = ['width' => 900, 'height' => 450, 'title' => 'elFinder 2.0']) + public function summernote(string $instance = 'default', string $selector = '.summenote', array $parameters = ['width' => 900, 'height' => 450, 'title' => 'elFinder 2.0']): string { - if (!is_string($instance)) { - throw new RuntimeError('The function can be applied to strings only.'); - } - return $this->twig->render( '@FMElfinder/Elfinder/helper/_summernote.html.twig', [ @@ -125,7 +111,7 @@ public function summernote($instance = 'default', $selector = '.summenote', $par * * @see Twig_ExtensionInterface::getName() */ - public function getName() + public function getName(): string { return 'fm_elfinder_init'; } diff --git a/tests/Form/Type/ElFinderTypeTest.php b/tests/Form/Type/ElFinderTypeTest.php index dd3c7dd..6f76870 100644 --- a/tests/Form/Type/ElFinderTypeTest.php +++ b/tests/Form/Type/ElFinderTypeTest.php @@ -15,20 +15,8 @@ public function testGetName() $this->assertEquals('elfinder', $type->getName()); } - public function testGetParentOld() - { - if (version_compare(Kernel::VERSION_ID, '20800') >= 0) { - $this->markTestSkipped('No need to test on symfony >= 2.8'); - } - $type = new ElFinderType(); - $this->assertEquals('text', $type->getParent()); - } - public function testConfigureOptions() { - if (version_compare(Kernel::VERSION_ID, '20600') < 0) { - $this->markTestSkipped('No need to test on symfony < 2.6'); - } $resolver = new OptionsResolver(); $type = new ElFinderType(); $type->configureOptions($resolver); @@ -37,19 +25,6 @@ public function testConfigureOptions() $this->assertTrue($resolver->isDefined('homeFolder')); } - public function testLegacySetDefaultOptions() - { - if (version_compare(Kernel::VERSION_ID, '20600') >= 0) { - $this->markTestSkipped('No need to test on symfony >= 2.6'); - } - $resolver = new OptionsResolver(); - $type = new ElFinderType(); - $type->setDefaultOptions($resolver); - $this->assertTrue($resolver->isKnown('enable')); - $this->assertTrue($resolver->isKnown('instance')); - $this->assertTrue($resolver->isKnown('homeFolder')); - } - public function testBuildView() { $options = [ diff --git a/tests/Twig/Extension/FMElfinderExtensionTest.php b/tests/Twig/Extension/FMElfinderExtensionTest.php index 8e21551..3441dce 100644 --- a/tests/Twig/Extension/FMElfinderExtensionTest.php +++ b/tests/Twig/Extension/FMElfinderExtensionTest.php @@ -152,23 +152,20 @@ public function testSubClassOfTwigExtension() public function testSummernoteInstanceNotString() { - $this->expectException(\Twig\Error\RuntimeError::class); - $this->expectExceptionMessage('The function can be applied to strings only.'); - $this->extension->summernote([]); + $this->expectException(\Twig\Error\LoaderError::class); + $this->extension->summernote(1); } public function testTinyMCEInstanceNotString() { - $this->expectException(\Twig\Error\RuntimeError::class); - $this->expectExceptionMessage('The function can be applied to strings only.'); - $this->extension->tinymce([]); + $this->expectException(\Twig\Error\LoaderError::class); + $this->extension->tinymce(1); } public function testTinyMCE4InstanceNotString() { - $this->expectException(\Twig\Error\RuntimeError::class); - $this->expectExceptionMessage('The function can be applied to strings only.'); - $this->extension->tinymce4([]); + $this->expectException(\Twig\Error\LoaderError::class); + $this->extension->tinymce4(1); } public function testGetFunctions()