Language Change Ready to take over Conflicting Files NPM Resource Changed ? Pending

User tests: Successful: Unsuccessful:

avatar vlaucht
vlaucht
7 Aug 2020

Pull Request for Issue # .

Summary of Changes

Added a button to the media manager edit page to save changes as a copy, without overwriting the original file.
save2copy

The new files will be named like the original file with an added version
new_name

Testing Instructions

  1. Navigate to content -> media
  2. select an image and open the editor
  3. make some changes and click 'Save as Copy'
  4. make sure a copy of the file with the changes applied has been saved as 'original_filename-1.extension'
  5. make sure the original file has not been modified
  6. please verify that this also works in subfolders
  7. Change Plugin folder

Actual result BEFORE applying this Pull Request

All changes have been applied to the original image in a destructive way. The original image could not be restored.

Expected result AFTER applying this Pull Request

A copy of the original file is saved with changes applied, and the original file is not modified

Documentation Changes Required

Added one new method generateNewName in 'administrator/components/com_media/src/Controller/ApiController.php' and documentation is added

avatar vlaucht vlaucht - open - 7 Aug 2020
avatar vlaucht vlaucht - change - 7 Aug 2020
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 7 Aug 2020
Category Administration com_media JavaScript Repository NPM Change
avatar infograf768 infograf768 - change - 7 Aug 2020
Title
Added Save2Copy in Media Manager
[4.0] Added Save2Copy in Media Manager
avatar infograf768 infograf768 - edited - 7 Aug 2020
avatar vlaucht vlaucht - change - 7 Aug 2020
Labels Added: NPM Resource Changed ?
avatar bembelimen
bembelimen - comment - 7 Aug 2020

Thank you @vlaucht for your PR

avatar Quy
Quy - comment - 7 Aug 2020

It should be Save & Close with the dropdown item Save as Copy to be consistent with other components.

avatar vlaucht
vlaucht - comment - 8 Aug 2020

It should be Save & Close with the dropdown item Save as Copy to be consistent with other components.

I can change that, but then the save2copy option is not visible. I believe some sort of warning should be presented to the user that he is about to permanently overwrite the file if he clicks on save

avatar Quy
Quy - comment - 8 Aug 2020

There is no warning with the other components. It should be obvious that it is save and close per the label of the button.

avatar vlaucht
vlaucht - comment - 10 Aug 2020

There is no warning with the other components. It should be obvious that it is save and close per the label of the button.

Ok, I have swapped the order of the buttons

avatar infograf768
infograf768 - comment - 11 Aug 2020

hmm. Looks like I do not get a copy at all here. No console error.
save2copy_media

avatar vlaucht
vlaucht - comment - 11 Aug 2020

hmm. Looks like I do not get a copy at all here. No console error.

Hm, just retried on a fresh 4.0 installation, and It worked as expected.

avatar infograf768
infograf768 - comment - 11 Aug 2020

Tested again. The problem is with Firefox

Macintosh:
Firefox: does not work
Safari and Chrome: works fine

avatar infograf768
infograf768 - comment - 11 Aug 2020

@Fedik
Any idea to solve the Firefox issue?

avatar vlaucht
vlaucht - comment - 11 Aug 2020

I just checked on that, and for some reason, it does not call the method putFile, like it does on Chrome. Possibly an issue with the edit-images.js. I will look into that later.

avatar richard67
richard67 - comment - 11 Aug 2020

Possibly an issue on Firefox side, or even some new security feature they have?

avatar vlaucht
vlaucht - comment - 11 Aug 2020

Possibly an issue on Firefox side, or even some new security feature they have?

Clicking on save does trigger a PUT request tho. Save&Close and Save2Copy seem to send a GET request for some reason

avatar vlaucht
vlaucht - comment - 11 Aug 2020

The issue is with window.location = ${pathName}?option=com_media&path=${fileDirectory}; (line 165) in
build/media_source/com_media/js/edit-images.es6.js. If this line is removed, it triggers a PUT request and everything works as expected, but then it does not navigate back of course.
Is it possible that it navigates back before the request is sent, and for that reason it sends a GET request (like it does when entering the site)?

avatar Fedik
Fedik - comment - 11 Aug 2020

who doing "request" (Joomla.UploadFile) and then change window.location? I wonder how it work at all :)

avatar vlaucht
vlaucht - comment - 11 Aug 2020

So how about sending an isClose parameter in the response and do the navigation after the request has completed?

avatar Fedik
Fedik - comment - 11 Aug 2020

the navigation after the request has completed

yeap, the navigation should be after the request completed

avatar infograf768
infograf768 - comment - 11 Aug 2020

Further tests:

Normally, Save & Copy does NOT reload the manager. See how it behaves for an article. We stay in edit form with the new article displayed in the form.
Therefore I have tested modifying js to

        forUpload.isCopy = true;
        Joomla.UploadFile.exec(name, JSON.stringify(forUpload), uploadPath, url, type);
        Joomla.MediaManager.Edit.Reset(true); // Changed here
        break;

Although we have no message telling that the modified image is saved (would be good to have), we stay in the edit form and it now works fine, waiting for a Save (Apply) or Save & Close.

I think this is the correct behavior.
Remains to add a Success message and, if we could, display the name of the file concerned (whether we use a Save or Save & Copy)

What do you think?

avatar Fedik
Fedik - comment - 11 Aug 2020

as I see, the code (php part) generate a new file name for a copied file,
so the editor need to be reloaded somehow to use that name, if I am correct

avatar infograf768
infograf768 - comment - 11 Aug 2020

This what Joomla.MediaManager.Edit.Reset(true); does, if I do not mistake.

EDIT: should do in fact. What happens is that the page url remains stuck to the original file.

avatar infograf768
infograf768 - comment - 12 Aug 2020

I also tested

      case 'save2copy':
        forUpload.isCopy = true;
        Joomla.UploadFile.exec(name, JSON.stringify(forUpload), uploadPath, url, type);
        Joomla.MediaManager.Edit.Reset(true);
        window.location = "".concat(pathName, "?option=com_media&path=").concat(fileDirectory);
        break;

and it creates the copy in Firefox, then returns to media manager directly.

avatar vlaucht
vlaucht - comment - 12 Aug 2020

and it creates the copy in Firefox, then returns to media manager directly.

But navigating back right after the request was sent means it will navigate even if the request was unsuccessfull for some reason, and since there is no notification yet, the user wouldn't even notice something went wrong. I think the navigation part should be done once the request completes.

avatar infograf768
infograf768 - comment - 12 Aug 2020

I agree. This is why I prefer the first solution, (#30313 (comment)) i.e. display a success message forcing to Save/Save & Close. + an error if fails.
Also display the name or path of file (easy for the existing file before save & Copy, but not so easy for the copied file)

avatar vlaucht
vlaucht - comment - 12 Aug 2020

We stay in edit form with the new article displayed in the form.

So does this mean if you click save2copy, you will now be editing the new copy instead of the original file?

avatar vlaucht
vlaucht - comment - 12 Aug 2020

I have added the file name in the header and a success message as shown in the screenshot below.
changes

If you click save2copy, the new copy will now be opened. The redirect on save&close will only be performed, if the request has successfully completed. There is no error message yet, because I don't really know how to access the exact error message from xhr.onerror. If anyone can help, please let me know.

avatar infograf768
infograf768 - comment - 12 Aug 2020

So does this mean if you click save2copy, you will now be editing the new copy instead of the original file?

Yes, that is what I meant. It did not show on screen obviously as we do not display the name of the file we edit.
Will now test your modified PR

avatar infograf768
infograf768 - comment - 13 Aug 2020

@wilsonge
Drone is failing here. https://ci.joomla.org/joomla/joomla-cms/34654/1/25
Could you have a look? Specially for the js errors which look weird. (The rest is common to all PR)

avatar joomla-cms-bot joomla-cms-bot - change - 13 Aug 2020
Category Administration com_media JavaScript Repository NPM Change Administration com_media Language & Strings JavaScript Repository NPM Change
avatar vlaucht
vlaucht - comment - 13 Aug 2020

@infograf768 I have added support for LTR for the toolbar title. Can you check if this is the correct way to do it?

avatar vlaucht vlaucht - change - 13 Aug 2020
Labels Added: ?
avatar infograf768
infograf768 - comment - 13 Aug 2020

I have added support for LTR for the toolbar title. Can you check if this is the correct way to do it?

Nothing to do with LTR. You don't need this here as the placement of the image name is independent from the text.

It was working perfectly as RTL or LTR with the - in the middle whatever the language.
In RTL, I had already tested it here by adding a fake value in Persian language . The title is RTL while the name of the file remains LTR as should be.
COM_MEDIA_EDIT="یر شناو"

avatar infograf768
infograf768 - comment - 13 Aug 2020

In fact, I suggest you modify the string.
I.e. do as we do when we edit an article.
COM_CONTENT_PAGE_EDIT_ARTICLE="Articles: Edit"

In our case, we would have
COM_MEDIA_EDIT="Media: Edit"
and the code
ToolbarHelper::title(Text::_('COM_MEDIA_EDIT') . ' - ' . $name, 'images mediamanager'); as before

avatar infograf768 infograf768 - test_item - 13 Aug 2020 - Tested successfully
avatar infograf768
infograf768 - comment - 13 Aug 2020

I have tested this item ✅ successfully on 062282b

ok here. failing drone unrelated.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar infograf768 infograf768 - test_item - 13 Aug 2020 - Tested successfully
avatar Quy
Quy - comment - 18 Aug 2020

Image is not created when in a directory. Here is the error in console: 409 Conflict

Firefox 79.0 (64-bit) on Windows

avatar infograf768 infograf768 - test_item - 18 Aug 2020 - Tested unsuccessfully
avatar infograf768
infograf768 - comment - 18 Aug 2020

I have tested this item 🔴 unsuccessfully on 062282b


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar infograf768
infograf768 - comment - 18 Aug 2020

I confirm @Quy findings.

Also we may have an issue when the plugin defines other folders than images

Screen Shot 2020-08-18 at 11 24 25

avatar vlaucht
vlaucht - comment - 18 Aug 2020

Image is not created when in a directory. Here is the error in console: 409 Conflict

Firefox 79.0 (64-bit) on Windows

This has now been fixed. Also an error message has been added if it failes to save the image

avatar vlaucht
vlaucht - comment - 18 Aug 2020

Also we may have an issue when the plugin defines other folders than images

Sorry I'm not really familiar fith the file system here. Do you mean because it's only checking for existence in the 'images' folder?
while (is_file(Path::check(JPATH_ROOT . '/images/' . dirname($path) . '/' . $name)))

avatar infograf768
infograf768 - comment - 18 Aug 2020

Do you mean because it's only checking for existence in the 'images' folder?

yep

avatar infograf768
infograf768 - comment - 18 Aug 2020

in fact it looks like it could be an issue for generate new name

avatar infograf768
infograf768 - comment - 18 Aug 2020

I confirm we have a 409 conflict

avatar infograf768
infograf768 - comment - 18 Aug 2020

and no error
link is http://localhost:8888/newfolder/joomla40/administrator/index.php?option=com_media&format=json&task=api.files&path=local-1:/my%20sub%20custom/mysubsusb/foldersubsubsub/evenement-sans-titre-01.jpg

folder is
Screen Shot 2020-08-18 at 12 15 45

params in the plugin (in my case) is

{"directories":{"__field10":{"directory":"images"},"__field11":{"directory":"mycustomimagefolder"}}}
avatar vlaucht
vlaucht - comment - 18 Aug 2020

How about utilizing public function search(string $path, string $needle, bool $recursive = false): array; from adapter to get all files that contain the original file name from any location, and then iterate over these files to generate a new name?

avatar vlaucht
vlaucht - comment - 18 Aug 2020

Not sure if there is a better way, but this would probably give me all files containing the file name without verion number from any location, then I could iterate them and generate a new file name:
`

           $extension = File::getExt($name);
	$name = File::stripExt($name);
	$base = count(explode('-', $name)) > 1 ? substr($name, 0, strrpos( $name, '-')) : $name;
	
	$options = [];
	$options['search'] = $base;
	$options['recursive'] = true;
	$files = $model->getFiles($adapter, dirname($path), $options);`

But not too sure how it behaves if you have a file name like 'image-name.jpg' for example, because it strips off everything after the last '-'. I mean it should still get the right images, but could possibly get heaps of unwanted images to iterate over as well.

avatar vlaucht
vlaucht - comment - 18 Aug 2020

@infograf768 @Quy please test latest commit, should work now with all folders and subfolders. Also error messages for exceptions have been fixed.

avatar vlaucht vlaucht - change - 19 Aug 2020
The description was changed
avatar vlaucht vlaucht - edited - 19 Aug 2020
avatar infograf768
infograf768 - comment - 19 Aug 2020

Will test tomorrow. Looks like drone may have an issue with test

avatar infograf768 infograf768 - test_item - 20 Aug 2020 - Tested successfully
avatar infograf768
infograf768 - comment - 20 Aug 2020

I have tested this item ✅ successfully on 90ff4f6


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar infograf768
infograf768 - comment - 20 Aug 2020

Restarted drone to see if we need new tests or not

avatar Quy Quy - test_item - 21 Aug 2020 - Tested successfully
avatar Quy
Quy - comment - 21 Aug 2020

I have tested this item ✅ successfully on 90ff4f6


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar Quy Quy - change - 21 Aug 2020
Status Pending Ready to Commit
avatar Quy
Quy - comment - 21 Aug 2020

RTC


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar richard67 richard67 - change - 21 Aug 2020
Labels Added: ?
avatar richard67
richard67 - comment - 21 Aug 2020

I've updated the branch of this PR to latest 4.0-dev to see if that helps with Drone.

@vlaucht All ok with your PR, Drone is our automated testing tool.

avatar infograf768
infograf768 - comment - 21 Aug 2020

restarted drone again

avatar richard67
richard67 - comment - 21 Aug 2020

The system tests fail in the media manager when renaming a folder. That could be related to this PR, but I'm not sure with that.

avatar richard67
richard67 - comment - 21 Aug 2020

The screenshot of the test environment when it fails shows an empty folder in the media manager, so maybe it is something wrong with the test?

avatar richard67 richard67 - test_item - 21 Aug 2020 - Tested unsuccessfully
avatar richard67
richard67 - comment - 21 Aug 2020

I have tested this item 🔴 unsuccessfully on 0ee94ae

When having this PR applied and run npm ciand cleared browser cache, and then rename an image in the media manager, and immediately edit the same image, I get an internal server error without clear error message. If I then try again to edit the image, it works. So the error happens after rename with first attempt to edit.

Without having this PR applied, that doesn't happen, editing works immediately after renaming.

I think that's also the reason for the failing system test.

@vlaucht Sorry for that, as you have put much time into your PR, and the save2copy works well. I will see if I can find out more details. Please stay tunded, I'm sure we get your PR fixed soon.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar richard67 richard67 - change - 21 Aug 2020
Status Ready to Commit Pending
avatar richard67
richard67 - comment - 21 Aug 2020

Back to pending due to negative test.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar richard67
richard67 - comment - 21 Aug 2020
An error has occurred.
    0 

Call stack
#	Function	Location
1	()	JROOT/plugins/filesystem/local/src/Adapter/LocalAdapter.php:104
2	Joomla\Plugin\Filesystem\Local\Adapter\LocalAdapter->getFile()	JROOT/administrator/components/com_media/src/Model/ApiModel.php:93
3	Joomla\Component\Media\Administrator\Model\ApiModel->getFile()	JROOT/administrator/components/com_media/src/Model/FileModel.php:65
4	Joomla\Component\Media\Administrator\Model\FileModel->getFileInformation()	JROOT/administrator/components/com_media/src/View/File/HtmlView.php:47
5	Joomla\Component\Media\Administrator\View\File\HtmlView->display()	JROOT/libraries/src/MVC/Controller/BaseController.php:691
6	Joomla\CMS\MVC\Controller\BaseController->display()	JROOT/libraries/src/MVC/Controller/BaseController.php:729
7	Joomla\CMS\MVC\Controller\BaseController->execute()	JROOT/libraries/src/Dispatcher/ComponentDispatcher.php:146
8	Joomla\CMS\Dispatcher\ComponentDispatcher->dispatch()	JROOT/libraries/src/Component/ComponentHelper.php:389
9	Joomla\CMS\Component\ComponentHelper::renderComponent()	JROOT/libraries/src/Application/AdministratorApplication.php:142
10	Joomla\CMS\Application\AdministratorApplication->dispatch()	JROOT/libraries/src/Application/AdministratorApplication.php:185
11	Joomla\CMS\Application\AdministratorApplication->doExecute()	JROOT/libraries/src/Application/CMSApplication.php:231
12	Joomla\CMS\Application\CMSApplication->execute()	JROOT/administrator/includes/app.php:63
13	require_once()	JROOT/administrator/index.php:36
avatar richard67
richard67 - comment - 21 Aug 2020

It seems the GET request is sent by the javascript using the old image name from before the rename when trying to edit directly after the rename:

save2copy-error

avatar infograf768
infograf768 - comment - 22 Aug 2020

and immediately edit the same image, I get an internal server error without clear error message.

Good find. I had not tested that indeed.

avatar vlaucht
vlaucht - comment - 22 Aug 2020

@richard67 I will have a look. Also, since it is checking for existence of the file when generating a new name, if you rename lets say file powered_by-4.png and there is a powered_by-5.png, and then save2copy powered_by-3.png I guess it will be saved as powered_by-4.png instead of powered_by-6.png since this is not existing anymore. What should be the correct behavior here?

avatar infograf768
infograf768 - comment - 22 Aug 2020

What should be the correct behavior here?

I have no issue getting as new file with an intermediary number as the name will be displayed on top and the file has to be saved anyway. Users should anyway know what they do...

avatar infograf768
infograf768 - comment - 22 Aug 2020

I'm still concerned by the system-test-mysql failing.
@wilsonge any idea?

avatar vlaucht
vlaucht - comment - 22 Aug 2020

found the issue with editing after renaming, will push a fix soon

avatar vlaucht
vlaucht - comment - 22 Aug 2020

So the issue is, that the putFiles method in ApiController returns the file now in
$resp->file = $this->getModel()->getFile($adapter, $path);
return $resp;

instead of return $this->getModel()->getFile($adapter, $path);.

The renaming is handled in administrator/components/com_media/resources/scripts/store/actions.js but nothing I change in this file seems to be applied.

avatar richard67
richard67 - comment - 22 Aug 2020

@vlaucht You have to be on a development environment and run npm ci or npm run bild:js to get the changes in your js applied (transpiled to es5 and cpoied to the media folder).

avatar vlaucht
vlaucht - comment - 22 Aug 2020

@vlaucht You have to be on a development environment and run npm ci or npm run bild:js to get the changes in your js applied (transpiled to es5 and cpoied to the media folder).

I tried with npm run bild:js but it's not recompiling the mediamanager.min.js, only the edit-images.js

avatar richard67
richard67 - comment - 22 Aug 2020

Ah, wait, that's a different kind of script location. No idea what happens with this.

@nikosdion Could you have a look on it? The issue with this PR currently is described in my test result #30313 (comment) and the comments below it.

avatar richard67
richard67 - comment - 22 Aug 2020

No panic, let's see if we get some advise. Unfortunately I'm not the js expert ;-)

avatar vlaucht
vlaucht - comment - 22 Aug 2020

It seems to work with npm ci

avatar richard67
richard67 - comment - 22 Aug 2020

The miracles of J4 ;-)

avatar richard67
richard67 - comment - 22 Aug 2020

Renaming a file works now in the system tests according to the log. Now it fails at renaming a folder, but I'm not sure if a failure or just a timeout which sometimes happens. Will restart the tests to see if they fail at the same place next time.

avatar vlaucht vlaucht - change - 22 Aug 2020
Labels Removed: ?
avatar vlaucht
vlaucht - comment - 22 Aug 2020

All tests have passed now, please test again

avatar richard67 richard67 - test_item - 22 Aug 2020 - Tested successfully
avatar richard67
richard67 - comment - 22 Aug 2020

I have tested this item ✅ successfully on 16cd912

Now everything works, or I just haven't found a way to break it ;-)

@vlaucht Really cool how fast you have found the reason for the last issue. I'd be happy, and I'm sure it's not only me, to see you contributing more, whenever you have time and mood and however it's comfortable for you.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.
avatar nikosdion
nikosdion - comment - 22 Aug 2020

@richard67 I am not sure why you at-mentioned me in particular. I have not been involved with Media Manager or the JavaScript tooling. I am also not the best person to ask about JavaScript, in general.

avatar richard67
richard67 - comment - 22 Aug 2020

@nikosdion Oh, sorry, still am learning who knows what ;-)

avatar nikosdion
nikosdion - comment - 22 Aug 2020

No problem :)

avatar chmst chmst - test_item - 23 Aug 2020 - Tested successfully
avatar chmst
chmst - comment - 23 Aug 2020

I have tested this item ✅ successfully on 16cd912

I have tested copying an image several times, modified the copies. Renamed and resized copy. All works as described. Very useful!


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar infograf768
infograf768 - comment - 23 Aug 2020

Once this string is added, please now look at frontend usage.

When editing an article in frontend, one can use the CMS Content to insert an image
Editing as well as Save as Copy is also available there.

Screen Shot 2020-08-23 at 16 54 17

Issue 1: the name of the file edited does not display on top of modal.
The Image title of the modal is defined in the plugin from PLG_IMAGE_BUTTON_IMAGE

Issue2: Save & Close loads the backend manager. This bug is also present when this PR is not used!

Screen Shot 2020-08-23 at 17 00 12

avatar richard67 richard67 - alter_testresult - 23 Aug 2020 - richard67: Not tested
avatar joomla-cms-bot joomla-cms-bot - change - 23 Aug 2020
Category Administration com_media JavaScript Repository NPM Change Language & Strings JavaScript Administration com_media NPM Change Language & Strings Repository Front End Plugins
avatar vlaucht
vlaucht - comment - 23 Aug 2020

@richard67 Thank you :)

Well, this is never going to end, is it 😃

As for the first issue, I came up with a rather hacky approach. Problem here is, that the modal and the title is rendered before any content is being loaded. There is most likely a better solution for this, but I am lacking some knowledge in how these components are loaded to come up with something better.
(I know that the title is not set back if the modal is closed by backdrop or the close button, don't want do dig too deep in case this solution is not accepted)

Will look into the second issue tomorrow.

avatar infograf768
infograf768 - comment - 24 Aug 2020

Concerning the bug (second issue). We have the same wrong behaviour in the modal when using the CMS insert image.

EDIT: file name is now correctly displayed (original as well as copied)

avatar infograf768
infograf768 - comment - 24 Aug 2020

@dgrammatiko
What do you think about the solution by @vlaucht for displaying the title of the edited image in the modal?
#30313 (comment)

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

@infograf768 it is wrong in many asspects:

  • has xss problems (no InnerHTML)
  • poor understanding of the existing code, Also not sure why they need the file name in the modal title
avatar infograf768
infograf768 - comment - 24 Aug 2020

Also not sure why they need the file name in the modal title

We need it when editing the image and save and copy to know the new name

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

We need it when editing the image and save and copy to know the new name

Well the code is nowhere near the ok mark (at least due to XSS)

avatar vlaucht
vlaucht - comment - 24 Aug 2020

As per my understanding, the title is rendered in the modal before any content is loaded, and I could not find a way to change it dynamically on a certain event, other than using a js event listener. I'm open for suggestions if you have a better approach @dgrammatiko

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

As per my understanding, the title is rendered in the modal before any content is loaded

  • DON'T use innerHTML to set content.
  • There are events for all modal events (check Bootstrap, unfortunately Joomla still uses their js part)
  • Why do you need to expose it as a Joomla obj? where do you need this functionality, eg where do you call this?
avatar bembelimen
bembelimen - comment - 24 Aug 2020

We should watch out, that this PR is not here to fix all issues in com_media, so I'm not sure if this PR should be responsible to change all this titles and fix all this bugs, which are not directly related to "save2copy" I think.

Probably @vlaucht could split it up in two PRs?

avatar infograf768
infograf768 - comment - 24 Aug 2020

@bembelimen
Except for the save & close pre-existing important bug which loads backend without tmpl component in the modal, the rest is directly related to this new Save & Copy feature.

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

@infograf768 this part of the code then belongs to the modal not the media field eg bootstrap-legacy.js legacy/bootstrap-init.js

BTW There was a reason I named that file legacy

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

Please try this:

((customElements, Joomla) => {
  if (!Joomla) {
    throw new Error('Joomla API is not properly initiated');
  }

  Joomla.selectedFile = {};

  const execTransform = (resp, editor, fieldClass) => {
    if (resp.success === true) {
      if (resp.data[0].url) {
        if (/local-/.test(resp.data[0].adapter)) {
          const { rootFull } = Joomla.getOptions('system.paths');

          // eslint-disable-next-line prefer-destructuring
          Joomla.selectedFile.url = resp.data[0].url.split(rootFull)[1];
          if (resp.data[0].thumb_path) {
            Joomla.selectedFile.thumb = resp.data[0].thumb_path;
          } else {
            Joomla.selectedFile.thumb = false;
          }
        } else if (resp.data[0].thumb_path) {
          Joomla.selectedFile.thumb = resp.data[0].thumb_path;
        }
      } else {
        Joomla.selectedFile.url = false;
      }

      const isElement = (o) => (
        typeof HTMLElement === 'object' ? o instanceof HTMLElement
          : o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
      );

      if (Joomla.selectedFile.url) {
        if (!isElement(editor) && (typeof editor !== 'object')) {
          Joomla.editors.instances[editor].replaceSelection(`<img loading="lazy" src="${Joomla.selectedFile.url}" alt=""/>`);
        } else if (!isElement(editor) && (typeof editor === 'object' && editor.id)) {
          window.parent.Joomla.editors.instances[editor.id].replaceSelection(`<img loading="lazy" src="${Joomla.selectedFile.url}" alt=""/>`);
        } else {
          editor.value = Joomla.selectedFile.url;
          fieldClass.updatePreview();
        }
      }
    }
  };

  /**
   * Create and dispatch onMediaFileSelected Event
   *
   * @param {object}  data  The data for the detail
   *
   * @returns {void}
   */
  Joomla.getImage = (data, editor, fieldClass) => new Promise((resolve, reject) => {
    if (!data || (typeof data === 'object' && (!data.path || data.path === ''))) {
      Joomla.selectedFile = {};
      resolve({
        resp: {
          success: false,
        },
      });
      return;
    }

    const apiBaseUrl = `${Joomla.getOptions('system.paths').rootFull}administrator/index.php?option=com_media&format=json`;

    Joomla.request({
      url: `${apiBaseUrl}&task=api.files&url=true&path=${data.path}&${Joomla.getOptions('csrf.token')}=1&format=json`,
      method: 'GET',
      perform: true,
      headers: { 'Content-Type': 'application/json' },
      onSuccess: (response) => {
        const resp = JSON.parse(response);
        resolve(execTransform(resp, editor, fieldClass));
      },
      onError: (err) => {
        reject(err);
      },
    });
  });

  class JoomlaFieldMedia extends HTMLElement {
    constructor() {
      super();

      this.onSelected = this.onSelected.bind(this);
      this.show = this.show.bind(this);
      this.clearValue = this.clearValue.bind(this);
      this.onMediaFileSelected = this.onMediaFileSelected.bind(this);
      this.onMediaFileEdit = this.onMediaFileEdit.bind(this);
      this.setTitle = this.setTitle.bind(this);
    }

    static get observedAttributes() {
      return ['type', 'base-path', 'root-folder', 'url', 'modal-container', 'modal-width', 'modal-height', 'input', 'button-select', 'button-clear', 'button-save-selected', 'preview', 'preview-width', 'preview-height'];
    }

    get type() { return this.getAttribute('type'); }

    set type(value) { this.setAttribute('type', value); }

    get basePath() { return this.getAttribute('base-path'); }

    set basePath(value) { this.setAttribute('base-path', value); }

    get rootFolder() { return this.getAttribute('root-folder'); }

    set rootFolder(value) { this.setAttribute('root-folder', value); }

    get url() { return this.getAttribute('url'); }

    set url(value) { this.setAttribute('url', value); }

    get modalContainer() { return this.getAttribute('modal-container'); }

    set modalContainer(value) { this.setAttribute('modal-container', value); }

    get input() { return this.getAttribute('input'); }

    set input(value) { this.setAttribute('input', value); }

    get buttonSelect() { return this.getAttribute('button-select'); }

    set buttonSelect(value) { this.setAttribute('button-select', value); }

    get buttonClear() { return this.getAttribute('button-clear'); }

    set buttonClear(value) { this.setAttribute('button-clear', value); }

    get buttonSaveSelected() { return this.getAttribute('button-save-selected'); }

    set buttonSaveSelected(value) { this.setAttribute('button-save-selected', value); }

    get modalWidth() { return this.getAttribute(parseInt('modal-width', 10)); }

    set modalWidth(value) { this.setAttribute('modal-width', value); }

    get modalHeight() { return this.getAttribute(parseInt('modal-height', 10)); }

    set modalHeight(value) { this.setAttribute('modal-height', value); }

    get previewWidth() { return this.getAttribute(parseInt('preview-width', 10)); }

    set previewWidth(value) { this.setAttribute('preview-width', value); }

    get previewHeight() { return this.getAttribute(parseInt('preview-height', 10)); }

    set previewHeight(value) { this.setAttribute('preview-height', value); }

    get preview() { return this.getAttribute('preview'); }

    set preview(value) { this.setAttribute('preview', value); }

    get previewContainer() { return this.getAttribute('preview-container'); }

    // attributeChangedCallback(attr, oldValue, newValue) {}
    onMediaFileSelected(e) {
      Joomla.selectedFile = e.detail;
      this.setTitle(e.detail.file.name);
    }

    onMediaFileEdit(e) {
      this.setTitle(e.detail.file.name);
      Joomla.selectedFile = e.detail;
    }

    connectedCallback() {
      this.button = this.querySelector(this.buttonSelect);
      this.buttonClearEl = this.querySelector(this.buttonClear);
      this.show = this.show.bind(this);
      this.modalClose = this.modalClose.bind(this);
      this.clearValue = this.clearValue.bind(this);
      this.setValue = this.setValue.bind(this);
      this.updatePreview = this.updatePreview.bind(this);

      this.button.addEventListener('click', this.show);

      window.document.addEventListener('onMediaFileSelected', this.onMediaFileSelected);
      window.document.addEventListener('onMediaFileEdit', this.onMediaFileEdit);
      this.addEventListener('onSetTitle', this.setTitle);

      if (this.buttonClearEl) {
        this.buttonClearEl.addEventListener('click', this.clearValue);
      }

      this.updatePreview();
    }

    disconnectedCallback() {
      if (this.button) {
        this.button.removeEventListener('click', this.show);
      }
      if (this.buttonClearEl) {
        this.buttonClearEl.removeEventListener('click', this.clearValue);
      }

      window.document.removeEventListener('onMediaFileSelected', this.onMediaFileSelected);
      window.document.removeEventListener('onMediaFileEdit', this.onMediaFileEdit);
      this.removeEventListener('onSetTitle', this.setTitle);
    }

    setTitle(titleString) {
      const title = this.querySelector('.modal-title');

      if (title) {
        const titleEl = title.querySelector('h2') /// or whatever H# is the actual title
        if (titleEl) {
          titleEl.innerText = `${Joomla.JText._('PLG_IMAGE_BUTTON_IMAGE')}-${titleString}`;
        }
      }
    }
    onSelected(event) {
      // event.target.removeEventListener('click', this.onSelected);
      event.preventDefault();
      event.stopPropagation();

      this.modalClose();
      return false;
    }

    show() {
      this.querySelector('[role="dialog"]').open();

      Joomla.selectedFile = {};

      this.querySelector(this.buttonSaveSelected).addEventListener('click', this.onSelected);
    }

    modalClose() {
      const input = this.querySelector(this.input);

      Joomla.getImage(Joomla.selectedFile, input, this)
        .then(() => {
          Joomla.Modal.getCurrent().close();
          Joomla.selectedFile = {};
        })
        .catch(() => {
          Joomla.Modal.getCurrent().close();
          Joomla.selectedFile = {};
          Joomla.renderMessages({
            error: [Joomla.Text._('JLIB_APPLICATION_ERROR_SERVER')],
          });
        });
    }

    setValue(value) {
      this.querySelector(this.input).value = value;
      this.updatePreview();
    }

    clearValue() {
      this.setValue('');
    }

    updatePreview() {
      if (['true', 'static'].indexOf(this.preview) === -1 || this.preview === 'false') {
        return;
      }

      // Reset preview
      if (this.preview) {
        const input = this.querySelector(this.input);
        const { value } = input;
        const div = this.querySelector('.field-media-preview');

        if (!value) {
          div.innerHTML = '<span class="field-media-preview-icon"></span>';
        } else {
          div.innerHTML = '';
          const imgPreview = new Image();

          switch (this.type) {
            case 'image':
              imgPreview.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
              imgPreview.setAttribute('alt', '');
              break;
            default:
              // imgPreview.src = dummy image path;
              break;
          }

          div.style.width = this.previewWidth;
          div.appendChild(imgPreview);
        }
      }
    }
  }
  customElements.define('joomla-field-media', JoomlaFieldMedia);
})(customElements, Joomla);
avatar infograf768
infograf768 - comment - 24 Aug 2020

I have solved the save &Close bug here by using in edit-images.js

      case 'save':
        forUpload.isClose = true;
        Joomla.UploadFile.exec(name, JSON.stringify(forUpload), uploadPath, url, type);
        if (window.self !== window.top) {
          window.location = "".concat(pathName, "?option=com_media&path=").concat(fileDirectory, "&tmpl=component");
        } else {
          window.location = "".concat(pathName, "?option=com_media&path=").concat(fileDirectory);
        }
        break;

instead of

 case 'save':
        forUpload.isClose = true;
        Joomla.UploadFile.exec(name, JSON.stringify(forUpload), uploadPath, url, type);
        break;
avatar vlaucht
vlaucht - comment - 24 Aug 2020

Please try this:

well, I have added the event listeners outside the class like the already existing onMediaFileSelected because they are not getting triggered the way they are implemented in your solution.

avatar vlaucht
vlaucht - comment - 24 Aug 2020

I have solved the save &Close bug here by using in edit-images.js

      case 'save':
        forUpload.isClose = true;
        Joomla.UploadFile.exec(name, JSON.stringify(forUpload), uploadPath, url, type);
        if (window.self !== window.top) {
          window.location = "".concat(pathName, "?option=com_media&path=").concat(fileDirectory, "&tmpl=component");
        } else {
          window.location = "".concat(pathName, "?option=com_media&path=").concat(fileDirectory);
        }
        break;

instead of

 case 'save':
        forUpload.isClose = true;
        Joomla.UploadFile.exec(name, JSON.stringify(forUpload), uploadPath, url, type);
        break;

This will probably reintroduce the issue with firefox, and also cause the window to close even if the save failed. This should probably be moved to the success case of the response object

avatar richard67
richard67 - comment - 24 Aug 2020

Currently the PR has a javascript code style error in drone: https://ci.joomla.org/joomla/joomla-cms/34920/1/25 ... but if it will changed anyway then maybe no need to fix it now.

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

because they are not getting triggered the way they are implemented in your solution.

They will be triggered the same way as if they were plain listeners in the document. The only difference is that they will be added when the field is initialised, which shouldn't be a problem (before the field is initialised the Media Manager is not even called)

I have added the event listeners outside the class like the already existing onMediaFileSelected

That part is just setting the state in an object, no side processing, the title is processing that needs to be done PER MEDIA FIELD, therefore having a global function which will have to search for the appropriate modal title is:

  • risky code (very volatile to bugs, you're searching all the modals titles with a string 'whatever' and then setting the title to the first one. Pretty sure this will never work with subforms
  • Extra unneeded lookups
avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

Update, actually the listeners should be added at the opening of the modal and removed when the modal closes:

((customElements, Joomla) => {
  if (!Joomla) {
    throw new Error('Joomla API is not properly initiated');
  }

  Joomla.selectedFile = {};

  const execTransform = (resp, editor, fieldClass) => {
    if (resp.success === true) {
      if (resp.data[0].url) {
        if (/local-/.test(resp.data[0].adapter)) {
          const { rootFull } = Joomla.getOptions('system.paths');

          // eslint-disable-next-line prefer-destructuring
          Joomla.selectedFile.url = resp.data[0].url.split(rootFull)[1];
          if (resp.data[0].thumb_path) {
            Joomla.selectedFile.thumb = resp.data[0].thumb_path;
          } else {
            Joomla.selectedFile.thumb = false;
          }
        } else if (resp.data[0].thumb_path) {
          Joomla.selectedFile.thumb = resp.data[0].thumb_path;
        }
      } else {
        Joomla.selectedFile.url = false;
      }

      const isElement = (o) => (
        typeof HTMLElement === 'object' ? o instanceof HTMLElement
          : o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
      );

      if (Joomla.selectedFile.url) {
        if (!isElement(editor) && (typeof editor !== 'object')) {
          Joomla.editors.instances[editor].replaceSelection(`< img loading = "lazy" src = "${Joomla.selectedFile.url}" alt = "" /> `);
        } else if (!isElement(editor) && (typeof editor === 'object' && editor.id)) {
          window.parent.Joomla.editors.instances[editor.id].replaceSelection(`< img loading = "lazy" src = "${Joomla.selectedFile.url}" alt = "" /> `);
        } else {
          editor.value = Joomla.selectedFile.url;
          fieldClass.updatePreview();
        }
      }
    }
  };

  /**
   * Create and dispatch onMediaFileSelected Event
   *
   * @param {object}  data  The data for the detail
   *
   * @returns {void}
   */
  Joomla.getImage = (data, editor, fieldClass) => new Promise((resolve, reject) => {
    if (!data || (typeof data === 'object' && (!data.path || data.path === ''))) {
      Joomla.selectedFile = {};
      resolve({
        resp: {
          success: false,
        },
      });
      return;
    }

    const apiBaseUrl = `${ Joomla.getOptions('system.paths').rootFull } administrator / index.php ? option = com_media & format=json`;

    Joomla.request({
      url: `${ apiBaseUrl }& task=api.files & url=true & path=${ data.path }& ${ Joomla.getOptions('csrf.token') }=1 & format=json`,
      method: 'GET',
      perform: true,
      headers: { 'Content-Type': 'application/json' },
      onSuccess: (response) => {
        const resp = JSON.parse(response);
        resolve(execTransform(resp, editor, fieldClass));
      },
      onError: (err) => {
        reject(err);
      },
    });
  });

  class JoomlaFieldMedia extends HTMLElement {
    constructor() {
      super();

      this.onSelected = this.onSelected.bind(this);
      this.show = this.show.bind(this);
      this.clearValue = this.clearValue.bind(this);
      this.onMediaFileSelected = this.onMediaFileSelected.bind(this);
      this.onMediaFileEdit = this.onMediaFileEdit.bind(this);
      this.setTitle = this.setTitle.bind(this);
    }

    static get observedAttributes() {
      return ['type', 'base-path', 'root-folder', 'url', 'modal-container', 'modal-width', 'modal-height', 'input', 'button-select', 'button-clear', 'button-save-selected', 'preview', 'preview-width', 'preview-height'];
    }

    get type() { return this.getAttribute('type'); }

    set type(value) { this.setAttribute('type', value); }

    get basePath() { return this.getAttribute('base-path'); }

    set basePath(value) { this.setAttribute('base-path', value); }

    get rootFolder() { return this.getAttribute('root-folder'); }

    set rootFolder(value) { this.setAttribute('root-folder', value); }

    get url() { return this.getAttribute('url'); }

    set url(value) { this.setAttribute('url', value); }

    get modalContainer() { return this.getAttribute('modal-container'); }

    set modalContainer(value) { this.setAttribute('modal-container', value); }

    get input() { return this.getAttribute('input'); }

    set input(value) { this.setAttribute('input', value); }

    get buttonSelect() { return this.getAttribute('button-select'); }

    set buttonSelect(value) { this.setAttribute('button-select', value); }

    get buttonClear() { return this.getAttribute('button-clear'); }

    set buttonClear(value) { this.setAttribute('button-clear', value); }

    get buttonSaveSelected() { return this.getAttribute('button-save-selected'); }

    set buttonSaveSelected(value) { this.setAttribute('button-save-selected', value); }

    get modalWidth() { return this.getAttribute(parseInt('modal-width', 10)); }

    set modalWidth(value) { this.setAttribute('modal-width', value); }

    get modalHeight() { return this.getAttribute(parseInt('modal-height', 10)); }

    set modalHeight(value) { this.setAttribute('modal-height', value); }

    get previewWidth() { return this.getAttribute(parseInt('preview-width', 10)); }

    set previewWidth(value) { this.setAttribute('preview-width', value); }

    get previewHeight() { return this.getAttribute(parseInt('preview-height', 10)); }

    set previewHeight(value) { this.setAttribute('preview-height', value); }

    get preview() { return this.getAttribute('preview'); }

    set preview(value) { this.setAttribute('preview', value); }

    get previewContainer() { return this.getAttribute('preview-container'); }

    // attributeChangedCallback(attr, oldValue, newValue) {}
    onMediaFileSelected(e) {
      Joomla.selectedFile = e.detail;
      this.setTitle(e.detail.file.name);
    }

    onMediaFileEdit(e) {
      this.setTitle(e.detail.file.name);
      Joomla.selectedFile = e.detail;
    }

    connectedCallback() {
      this.button = this.querySelector(this.buttonSelect);
      this.buttonClearEl = this.querySelector(this.buttonClear);
      this.show = this.show.bind(this);
      this.modalClose = this.modalClose.bind(this);
      this.clearValue = this.clearValue.bind(this);
      this.setValue = this.setValue.bind(this);
      this.updatePreview = this.updatePreview.bind(this);

      this.button.addEventListener('click', this.show);
      this.addEventListener('onSetTitle', this.setTitle);

      if (this.buttonClearEl) {
        this.buttonClearEl.addEventListener('click', this.clearValue);
      }

      this.updatePreview();
    }

    disconnectedCallback() {
      if (this.button) {
        this.button.removeEventListener('click', this.show);
      }
      if (this.buttonClearEl) {
        this.buttonClearEl.removeEventListener('click', this.clearValue);
      }

      this.removeEventListener('onSetTitle', this.setTitle);
    }

    setTitle(titleString) {
      const title = this.querySelector('.modal-title');

      if (title) {
        const titleEl = title.querySelector('h2') /// or whatever H# is the actual title
        if (titleEl) {
          titleEl.innerText = `${ Joomla.JText._('PLG_IMAGE_BUTTON_IMAGE') } -${ titleString } `;
        }
      }
    }
    onSelected(event) {
      // event.target.removeEventListener('click', this.onSelected);
      event.preventDefault();
      event.stopPropagation();

      this.modalClose();
      return false;
    }

    show() {
      this.querySelector('[role="dialog"]').open();

      Joomla.selectedFile = {};

      window.document.addEventListener('onMediaFileSelected', this.onMediaFileSelected);
      window.document.addEventListener('onMediaFileEdit', this.onMediaFileEdit);
      this.querySelector(this.buttonSaveSelected).addEventListener('click', this.onSelected);
    }

    modalClose() {
      const input = this.querySelector(this.input);

      window.document.removeEventListener('onMediaFileSelected', this.onMediaFileSelected);
      window.document.removeEventListener('onMediaFileEdit', this.onMediaFileEdit);

      Joomla.getImage(Joomla.selectedFile, input, this)
        .then(() => {
          Joomla.Modal.getCurrent().close();
          Joomla.selectedFile = {};
        })
        .catch(() => {
          Joomla.Modal.getCurrent().close();
          Joomla.selectedFile = {};
          Joomla.renderMessages({
            error: [Joomla.Text._('JLIB_APPLICATION_ERROR_SERVER')],
          });
        });
    }

    setValue(value) {
      this.querySelector(this.input).value = value;
      this.updatePreview();
    }

    clearValue() {
      this.setValue('');
    }

    updatePreview() {
      if (['true', 'static'].indexOf(this.preview) === -1 || this.preview === 'false') {
        return;
      }

      // Reset preview
      if (this.preview) {
        const input = this.querySelector(this.input);
        const { value } = input;
        const div = this.querySelector('.field-media-preview');

        if (!value) {
          div.innerHTML = '<span class="field-media-preview-icon"></span>';
        } else {
          div.innerHTML = '';
          const imgPreview = new Image();

          switch (this.type) {
            case 'image':
              imgPreview.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
              imgPreview.setAttribute('alt', '');
              break;
            default:
              // imgPreview.src = dummy image path;
              break;
          }

          div.style.width = this.previewWidth;
          div.appendChild(imgPreview);
        }
      }
    }
  }
  customElements.define('joomla-field-media', JoomlaFieldMedia);
})(customElements, Joomla);
avatar vlaucht
vlaucht - comment - 24 Aug 2020

That part is just setting the state in an object, no side processing, the title is processing that needs to be done PER MEDIA FIELD, therefore having a global function which will have to search for the appropriate modal title is:

I see, sorry I'm still fairly new to this ;-)

@richard67 This is due to wrong string concatination and will be solved

avatar vlaucht
vlaucht - comment - 24 Aug 2020

Update, actually the listeners should be added at the opening of the modal and removed when the modal closes:

I'm either missing something, or the event listeners are still not getting registered

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

I'm either missing something, or the event listeners are still not getting registered

How are you testing this?

The expected workflow is to open the modal and THEN fire an event eg:

document.dispatchEvent(new Event("onMediaFileEdit", {"bubbles":true, "cancelable":false}));
avatar vlaucht
vlaucht - comment - 24 Aug 2020

I'm either missing something, or the event listeners are still not getting registered

How are you testing this?

The expected workflow is to open the modal and THEN fire an event eg:

document.dispatchEvent(new Event("onMediaFileEdit", {"bubbles":true, "cancelable":false}));

The same way I did it before. The event gets dispatched on click on the edit button of a particular image, thus, the modal has to be loaded at this point. And it's neither working with
window.parent.document.dispatchEvent(new CustomEvent('onMediaFileEdit',{detail: this.item.name,}));
like it did before, nor with document.dispatchEvent(new Event("onMediaFileEdit", {"bubbles":true, "cancelable":false}));

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

And it's neither working with

When you're saying it's not working you mean the debugger is not stopping in the relative function or the title is not changing?

I left a comment for the title part as right now I have no clue on top of my head what element is wrapping the title, it might not be H2, that's something you can check by looking at the Dom of the modal

avatar vlaucht
vlaucht - comment - 24 Aug 2020

And it's neither working with

When you're saying it's not working you mean the debugger is not stopping in the relative function or the title is not changing?

I left a comment for the title part as right now I have no clue on top of my head what element is wrapping the title, it might not be H2, that's something you can check by looking at the Dom of the modal

I've seen that and changed it already, but I'm talking about the event listener is not getting triggered

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

I have no clue how you're debugging this but
Screenshot 2020-08-24 at 12 29 01

change the setTitle(titleString) to

    setTitle(titleString) {
      const title = this.querySelector('.modal-title');

      if (title) {
        title.textContent = `${ Joomla.JText._('PLG_IMAGE_BUTTON_IMAGE') } - ${ titleString } `;
      }
    }

Also fire the events like

document.dispatchEvent(new CustomEvent("onMediaFileEdit", {
  detail: {
    file: {
        name: 'works'
    }
  }
}))

or window.parent.document.... depending where you are

Screenshot 2020-08-24 at 12 46 24

Screenshot 2020-08-24 at 12 47 48

avatar vlaucht
vlaucht - comment - 24 Aug 2020

I have no clue how you're debugging this but

Well, I have tried it with Chrome and Firefox now, and it works fine the way it was implemented before, but the debugger doesnt stop in your implementation. Do you have chrome or firefox to check on that?

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

Both working fine here
Screenshot 2020-08-24 at 13 08 27
Screenshot 2020-08-24 at 13 08 55

You do realise that the payload is important here right?
So you have to adjust the places where you're firing the events

avatar vlaucht
vlaucht - comment - 24 Aug 2020

I know, but it doesnt even stop if I fire it with the console. As far as I can tell the constructor of JoomlaFieldMedia is not called, and therefore the event listener is not registered. I don't really know why. Would you mind skyping with me real quick?

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

@vlaucht here you go, replace the files from this zip Archive.zip

and you should be good

avatar vlaucht
vlaucht - comment - 24 Aug 2020

Retried on a fresh J4.0 installation with these files applied on chrome and firefox with windows, and it's still not working. Could someone else please test and check wether it is an issue at my end?

avatar richard67
richard67 - comment - 24 Aug 2020

@vlaucht Have you run npm ci after having applied the changes?

avatar vlaucht
vlaucht - comment - 24 Aug 2020

@vlaucht Have you run npm ci after having applied the changes?

of course. Probably 50 times by now 😃. I can also see it in the debugger, but it's not being executed

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

You have to make the payload the same in all emitted events. When I wrote the comments above #30313 (comment) I expected you to understand that this was an example, the actual payload should be something like:

							window.parent.document.dispatchEvent(
									new CustomEvent(
											'onMediaFileEdit',
											{
												"bubbles": true,
												"cancelable": false,
												"detail": {
													path: this.item.path,
													thumb: this.item.thumb,
													fileType: this.item.mime_type ? this.item.mime_type : false,
													extension: this.item.extension ? this.item.extension : false,
													file: {
														name: this.item.name ? this.item.name : ''
													}
												},
											}
									)
							);
avatar vlaucht
vlaucht - comment - 24 Aug 2020

You have to make the payload the same in all emitted events. When I wrote the comments above [#30313 (comment)]

Well, I do understand that, but as I already mentioned, the constructor of JoomlaFieldMedia is not being called. Therefore all event listeners inside this class are not registered, thus, no matter what the payload is, they will not be cought. This is why I asked if someone else could test the latest commit and check wether this is an issue at my end.

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

@vlaucht just delete the media folder then run npm install. Do you get the needed files? (check the media/system/fields/..., etc)
Also do you get any errors? What's you npm version? Are you sure you're in the right directory?
(You could download the installer from the link below in the Github and try that one if your npm has any errors)

avatar vlaucht
vlaucht - comment - 24 Aug 2020

@vlaucht just delete the media folder then run npm install. Do you get the needed files? (check the media/system/fields/..., etc)

NPM: 6.13.4
Edit: just updated to latest 6.14.8 and no difference

image

image

avatar dgrammatiko
dgrammatiko - comment - 24 Aug 2020

@vlaucht just by navigating to article -> new do you have any errors in the browser console?

avatar vlaucht
vlaucht - comment - 24 Aug 2020

@vlaucht just by navigating to article -> new do you have any errors in the browser console?

Nope. Should I? I had a blank error notification once, but I could not reproduce it. Browser console is empty.

avatar infograf768
infograf768 - comment - 25 Aug 2020

Please fix conflicts.
(because of #30455 merge)

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

the constructor of JoomlaFieldMedia is not being called

@vlaucht the constructor is called only once, after the js was parsed, you have to debug the actual function eg setTitle(titleString)

avatar vlaucht
vlaucht - comment - 25 Aug 2020

@vlaucht the constructor is called only once, after the js was parsed, you have to debug the actual function eg setTitle(titleString)

@dgrammatiko I'm pretty sure I have tested it correctly.

  • I can't find the listener attached to the dom in inspector (I do find the listener registered outside the class)

  • The debugger does not stop in the setTitle(titleString) (It does stop in the onMediaFileSelected registered outside)

  • Nothing I put in the constructor is being executed

  • Neither firing with console, nor with UI does trigger onMediaFileEdit

  • The whole thing simply does not work. Again, thats the reason why I would like someone to test the commit and see if this can be reproduced

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

The whole thing simply does not work.

Well that's probably something on your side (do you have any browser plugins?) because I shared images that this works on all browsers. Also please share your workflow how you're testing this, I've tested it both manually and also trying to edit an image using the first image in the article form. I can't help you if I don't know what you did and what expected to happen...

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

BTW the whole js code is bloated for no reason, you don't need an event for the title, eg in the edit and image.vue you can check if the view is modal (there's a state variable for that AFAIK, I added it) and set the title directly there no need for all this complex ritual...

avatar vlaucht
vlaucht - comment - 25 Aug 2020

Also please share your workflow how you're testing this

Clone this branch into htdocs folder, run composer install and npm ci, clear browser chache, set up the joomla site, install test sample data,
navigate to frontend -> single article -> australian parks ->edit -> CMS content
Open debugger, set breakpoints, check with UI and console
Tested with firefox on windows, only extension is xdebug

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

I think I was pretty clear many comments ago: you need to open the images modal and then check the debugger😉

avatar vlaucht
vlaucht - comment - 25 Aug 2020

I think I was pretty clear many comments ago: you need to open the images modal and then check the debugger😉

Well thats what I meant with ->CMS content. Let me clarify then: CMS content -> Image

I thought this is self explainatory, since the image modal is what is being tested here and CMS content is just an option list.

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

Ok you're missing most of the code replace the media field with:

((customElements, Joomla) => {
  if (!Joomla) {
    throw new Error('Joomla API is not properly initiated');
  }

  Joomla.selectedFile = {};

  window.document.addEventListener('onMediaFileSelected', (e) => {
    Joomla.selectedFile = e.detail;
  });

  const execTransform = (resp, editor, fieldClass) => {
    if (resp.success === true) {
      if (resp.data[0].url) {
        if (/local-/.test(resp.data[0].adapter)) {
          const { rootFull } = Joomla.getOptions('system.paths');

          // eslint-disable-next-line prefer-destructuring
          Joomla.selectedFile.url = resp.data[0].url.split(rootFull)[1];
          if (resp.data[0].thumb_path) {
            Joomla.selectedFile.thumb = resp.data[0].thumb_path;
          } else {
            Joomla.selectedFile.thumb = false;
          }
        } else if (resp.data[0].thumb_path) {
          Joomla.selectedFile.thumb = resp.data[0].thumb_path;
        }
      } else {
        Joomla.selectedFile.url = false;
      }

      const isElement = (o) => (
        typeof HTMLElement === 'object' ? o instanceof HTMLElement
          : o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
      );

      if (Joomla.selectedFile.url) {
        if (!isElement(editor) && (typeof editor !== 'object')) {
          Joomla.editors.instances[editor].replaceSelection(`<img loading="lazy" src="${Joomla.selectedFile.url}" alt=""/>`);
        } else if (!isElement(editor) && (typeof editor === 'object' && editor.id)) {
          window.parent.Joomla.editors.instances[editor.id].replaceSelection(`<img loading="lazy" src="${Joomla.selectedFile.url}" alt=""/>`);
        } else {
          editor.value = Joomla.selectedFile.url;
          fieldClass.updatePreview();
        }
      }
    }
  };

  /**
   * Create and dispatch onMediaFileSelected Event
   *
   * @param {object}  data  The data for the detail
   *
   * @returns {void}
   */
  Joomla.getImage = (data, editor, fieldClass) => new Promise((resolve, reject) => {
    if (!data || (typeof data === 'object' && (!data.path || data.path === ''))) {
      Joomla.selectedFile = {};
      resolve({
        resp: {
          success: false,
        },
      });
      return;
    }

    const apiBaseUrl = `${Joomla.getOptions('system.paths').rootFull}administrator/index.php?option=com_media&format=json`;

    Joomla.request({
      url: `${apiBaseUrl}&task=api.files&url=true&path=${data.path}&${Joomla.getOptions('csrf.token')}=1&format=json`,
      method: 'GET',
      perform: true,
      headers: { 'Content-Type': 'application/json' },
      onSuccess: (response) => {
        const resp = JSON.parse(response);
        resolve(execTransform(resp, editor, fieldClass));
      },
      onError: (err) => {
        reject(err);
      },
    });
  });

  class JoomlaFieldMedia extends HTMLElement {
    constructor() {
      super();

      this.onSelected = this.onSelected.bind(this);
      this.show = this.show.bind(this);
      this.clearValue = this.clearValue.bind(this);
      this.modalClose = this.modalClose.bind(this);
      this.setValue = this.setValue.bind(this);
      this.updatePreview = this.updatePreview.bind(this);
      this.onMediaFileEdit = this.onMediaFileEdit.bind(this);
      this.setTitle = this.setTitle.bind(this);
    }

    static get observedAttributes() {
      return ['type', 'base-path', 'root-folder', 'url', 'modal-container', 'modal-width', 'modal-height', 'input', 'button-select', 'button-clear', 'button-save-selected', 'preview', 'preview-width', 'preview-height'];
    }

    get type() { return this.getAttribute('type'); }

    set type(value) { this.setAttribute('type', value); }

    get basePath() { return this.getAttribute('base-path'); }

    set basePath(value) { this.setAttribute('base-path', value); }

    get rootFolder() { return this.getAttribute('root-folder'); }

    set rootFolder(value) { this.setAttribute('root-folder', value); }

    get url() { return this.getAttribute('url'); }

    set url(value) { this.setAttribute('url', value); }

    get modalContainer() { return this.getAttribute('modal-container'); }

    set modalContainer(value) { this.setAttribute('modal-container', value); }

    get input() { return this.getAttribute('input'); }

    set input(value) { this.setAttribute('input', value); }

    get buttonSelect() { return this.getAttribute('button-select'); }

    set buttonSelect(value) { this.setAttribute('button-select', value); }

    get buttonClear() { return this.getAttribute('button-clear'); }

    set buttonClear(value) { this.setAttribute('button-clear', value); }

    get buttonSaveSelected() { return this.getAttribute('button-save-selected'); }

    set buttonSaveSelected(value) { this.setAttribute('button-save-selected', value); }

    get modalWidth() { return this.getAttribute(parseInt('modal-width', 10)); }

    set modalWidth(value) { this.setAttribute('modal-width', value); }

    get modalHeight() { return this.getAttribute(parseInt('modal-height', 10)); }

    set modalHeight(value) { this.setAttribute('modal-height', value); }

    get previewWidth() { return this.getAttribute(parseInt('preview-width', 10)); }

    set previewWidth(value) { this.setAttribute('preview-width', value); }

    get previewHeight() { return this.getAttribute(parseInt('preview-height', 10)); }

    set previewHeight(value) { this.setAttribute('preview-height', value); }

    get preview() { return this.getAttribute('preview'); }

    set preview(value) { this.setAttribute('preview', value); }

    get previewContainer() { return this.getAttribute('preview-container'); }

    // attributeChangedCallback(attr, oldValue, newValue) {}

    connectedCallback() {
      this.button = this.querySelector(this.buttonSelect);
      this.inputElement = this.querySelector(this.input);
      this.buttonClearEl = this.querySelector(this.buttonClear);
      this.modalElement = this.querySelector('.joomla-modal');
      this.buttonSaveSelectedElement = this.querySelector(this.buttonSaveSelected);
      this.previewElement = this.querySelector('.field-media-preview');

      if (!this.button || !this.inputElement || !this.buttonClearEl || !this.modalElement
        || !this.buttonSaveSelectedElement) {
        throw new Error('Misconfiguaration...');
      }

      if (Joomla.Bootstrap && Joomla.Bootstrap.initModal && typeof Joomla.Bootstrap.initModal === 'function') {
        Joomla.Bootstrap.initModal(this.modalElement);
      }

      this.button.addEventListener('click', this.show);

      if (this.buttonClearEl) {
        this.buttonClearEl.addEventListener('click', this.clearValue);
      }

      this.updatePreview();
    }

    disconnectedCallback() {
      if (this.button) {
        this.button.removeEventListener('click', this.show);
      }
      if (this.buttonClearEl) {
        this.buttonClearEl.removeEventListener('click', this.clearValue);
      }
    }

    onSelected(event) {
      event.preventDefault();
      event.stopPropagation();

      this.modalClose();
      return false;
    }

    show() {
      this.modalElement.open();

      Joomla.selectedFile = {};

      window.document.addEventListener('onMediaFileEdit', this.onMediaFileEdit);
      this.buttonSaveSelectedElement.addEventListener('click', this.onSelected);
    }

    modalClose() {
      window.document.removeEventListener('onMediaFileEdit', this.onMediaFileEdit);

      Joomla.getImage(Joomla.selectedFile, this.inputElement, this)
        .then(() => {
          Joomla.Modal.getCurrent().close();
          Joomla.selectedFile = {};
        })
        .catch(() => {
          Joomla.Modal.getCurrent().close();
          Joomla.selectedFile = {};
          Joomla.renderMessages({
            error: [Joomla.Text._('JLIB_APPLICATION_ERROR_SERVER')],
          });
        });
    }

    setValue(value) {
      this.inputElement.value = value;
      this.updatePreview();
    }

    clearValue() {
      this.setValue('');
    }

    updatePreview() {
      if (['true', 'static'].indexOf(this.preview) === -1 || this.preview === 'false' || !this.previewElement) {
        return;
      }

      // Reset preview
      if (this.preview) {
        const { value } = this.inputElement;

        if (!value) {
          this.previewElement.innerHTML = '<span class="field-media-preview-icon"></span>';
        } else {
          this.previewElement.innerHTML = '';
          const imgPreview = new Image();

          switch (this.type) {
            case 'image':
              imgPreview.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
              imgPreview.setAttribute('alt', '');
              break;
            default:
              // imgPreview.src = dummy image path;
              break;
          }

          this.previewElement.style.width = this.previewWidth;
          this.previewElement.appendChild(imgPreview);
        }
      }
    }

    onMediaFileEdit(e) {
      if (e.detail && e.detail.file && e.detail.file.name) {
        this.setTitle(e.detail.file.name);
        Joomla.selectedFile = e.detail;
      }
    }

    setTitle(titleString) {
      const title = this.querySelector('.modal-title');

      if (title) {
        title.textContent = `${ Joomla.JText._('PLG_IMAGE_BUTTON_IMAGE') } - ${ titleString } `;
      }
    }
  }
  customElements.define('joomla-field-media', JoomlaFieldMedia);
})(customElements, Joomla);

and try again setting the debugger inside setTitle(titleString) and onMediaFileEdit(e)

avatar vlaucht
vlaucht - comment - 25 Aug 2020

Ok you're missing most of the code replace the media field with:

I don't see how this would change anything relevant to the problem, that the listeners are not getting registered. It just moves the methods to the end and removes some lines irrelevant to the issue. (Still tested it tho, but issue is still present)

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

Honestly I have no clue how you try to debug this but it works fine here
Screenshot 2020-08-25 at 14 46 07
Screenshot 2020-08-25 at 14 46 21

You can try adding some console.log('whatever'); inside setTitle(titleString) and onMediaFileEdit(e) and then recompile npm run build:js

avatar vlaucht
vlaucht - comment - 25 Aug 2020

Honestly I have no clue how you try to debug this but it works fine here

We have been at this point before. I know that it works for you, it just doesnt work for me. I tried console.logs already, but as I already mentioned, the listeners are not registered, so this never gets executed. All listeners registered outside the class work like expected. Since I also tried multiple times with fresh joomla installation, there is nothing I can do besides waiting until someone tests this and can either confirm that this is an issue at my end, or that there is something else wrong.

avatar richard67
richard67 - comment - 25 Aug 2020

@vlaucht Sorry I can't help here, am not a js guy ... but I did read you use xdebug. Maybe it's an xdebug issue that the event listeners don't register, or at least an issue which happens only when using xdebug? I remember having read something similar in some other issue in past.

avatar dgrammatiko
dgrammatiko - comment - 25 Aug 2020

xdebug is for PHP, for debugging JS you need to use the browser's debugger. The PHP debugger will NEVER break on any JS breakpoint...

avatar richard67
richard67 - comment - 25 Aug 2020

Silly me ;-)

avatar vlaucht
vlaucht - comment - 25 Aug 2020

@richard67 You dont need any JS knowledge. If you follow the steps in Comment and then click on the pencil icon of an image in the modal, it should open the edit view and change the name in the modal title. If not, you can open the browser debug tool, under sources navigate to media/system/fields/joomla-field-media.min.js and pretty-print it. Then find the methods onMediaFileEdit and setTitle and set a breakpoint. Then try to open the edit window and see if the debugger stops, or go to the browser console and try to call
document.dispatchEvent( new CustomEvent( 'onMediaFileEdit', { "bubbles": true, "cancelable": false, "detail": { file: { name: 'test' } }, } ) );

avatar mqueme
mqueme - comment - 17 Oct 2020

I tested this. I click on the save as copy button, and nothing happens. no errors displayed

avatar mqueme mqueme - test_item - 17 Oct 2020 - Tested unsuccessfully
avatar mqueme
mqueme - comment - 17 Oct 2020

I have tested this item 🔴 unsuccessfully on c889ca5

Tested, drop down appears but nothing happens when clicked


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/30313.

avatar drmenzelit
drmenzelit - comment - 2 Dec 2021

@vlaucht interesting feature, any chance you can solve conflicts?

avatar HLeithner
HLeithner - comment - 27 Jun 2022

This pull request has automatically rebased to 4.2-dev.

avatar rdeutz
rdeutz - comment - 21 Oct 2022

Nice feature needs a new start, somewhere else.

avatar rdeutz rdeutz - change - 21 Oct 2022
Status Pending Closed
Closed_Date 0000-00-00 00:00:00 2022-10-21 09:34:58
Closed_By rdeutz
Labels Added: Language Change Ready to take over ?
Removed: ? ?
avatar rdeutz rdeutz - close - 21 Oct 2022

Add a Comment

Login with GitHub to post a comment