Problem/Motivation

This is a follow-up to #2940029: Add an input filter to display embedded Media entities, and the second step of #2801307: [META] Support WYSIWYG embedding of media entities.

The first part is a @Filter plugin, that's what #2940029: Add an input filter to display embedded Media entities provides: Drupal stores media embeds in a structured way using <drupal-media> tags, that are transformed on output. This way, Drupal stays true to its ethos of structured content.

The second part is to make these <drupal-media> tags have a great authoring experience. We don't want content authors to have to look, understand and manipulate that markup: that'd be a usability nightmare.

Fortunately, for this very reason the CKEditor team developed the CKEditor Widget system — which is also what powers Drupal 8's image support in CKEditor today (see #2039163: Update CKEditor library to 4.4).

In a nutshell, the Widget system allows us to have a storage representation (structured content, yay!) and an editing representation (to offer a great editing UX); the corresponding CKEditor plugin is responsible for this mapping.

The scope of this issue is solely the visual representation of an existing <drupal-media> tag; generating <code><drupal-media> tags by selecting from the Media Library is the scope #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library!

Proposed resolution

Just like in #2801307: [META] Support WYSIWYG embedding of media entities, we should stabilize the existing CKEditor integration of the https://www.drupal.org/project/entity_embed module.

It should:

  1. talk to the server to generate an accurate preview of Media entities using the front end (default) theme's templates
  2. offer exactly the same great in-place caption editing UX as we currently have for images (core's DrupalImageCaption CKEditor plugin)
  3. offer exactly the same linking support
  4. have its code reviewed & approved by the CKEditor team to ensure long-term maintainability and stability
  5. ensure a good UX even on high-latency connections: either (a) entering and leaving source mode or (b) saving the entity and then editing it again should not cause network roundtrips to fetch media previews. This is also to ensure parity with the existing image support: that is rendered entirely client-side, but that's impossible to achieve for arbitrary media entities. Hence the importance of this and it meriting a bullet here!

Remaining tasks

  1. Stabilize https://www.drupal.org/project/entity_embed, port over all test coverage, to ensure a maximally stable new CKEditor plugin in core.
  2. Reviews!
  3. Convert to *.es6.js ✅Done.
  4. Follow-up blocked on a future CKEditor release: undo support is present, but sometimes a single user action requires triggering "undo" multiple times. This is due to bugs in CKEditor itself, which they're working to fix. See #3060245: [upstream] CKEditor's "undo" functionality not working correctly for embeds: offers multiple undo steps where there should be only one and #3063964: [upstream][PP-2] Editing an embedded entity and submitting the `EntityEmbedDialog` without changing anything results in an unwanted "undo' step.
  5. Follow-up blocked on a future CKEditor release: mask support, to prevent interaction with the embedded media. See #3064572: Add mask to prevent interaction with embedded media.

User interface changes

None.

API changes

None.

Data model changes

None.

Testing steps

Testing steps

  • Install latest 8.8.x
  • Apply the patch
  • Go to Administration => Configuration => Content authoring => Text formats and editors
  • Select a text format (e.g. Basic HTML)
  • Check " Embed media" under "Enabled filters"
  • Add <drupal-media data-entity-type data-entity-uuid data-align data-caption> in "Limit allowed HTML tags and correct faulty HTML" if the filter enabled and "Save configuration"
  • Go to node add article (/node/add/article)
  • Click the "Source" button in CKEditor
  • Add
    <drupal-media data-align="center" data-caption="The first core Media embed ever." data-entity-type="media" data-entity-uuid="84911dc4-c086-4781-afc3-eb49b7380ff5"></drupal-media>
    
    <p>and</p>
    
    <drupal-media data-align="center" data-caption="This media entity is missing" data-entity-type="media" data-entity-uuid="wrong"></drupal-media>
    
  • Click the "Source" button in CKEditor again. Now you should see:
  • Now just like in #2940029: Add an input filter to display embedded Media entities's testing steps, save the article, and you should see:

    Good preview, isn't it? 🙂
  • Now go back to edit it, and observe how the preview loads instantly — just like in https://www.drupal.org/files/issues/2019-06-28/media_embed%20cached%20pr...
CommentFileSizeAuthor
#226 interdiff-225-226.txt577 byteseffulgentsia
#226 2994696-226.patch84.72 KBeffulgentsia
#225 interdiff-222-225.txt6.42 KBeffulgentsia
#225 2994696-225.patch84.7 KBeffulgentsia
#222 2994696-222.patch84.44 KBoknate
#222 2994696-interdiff--220-222.txt621 bytesoknate
#220 2994696-219.patch84.42 KBwim leers
#220 interdiff.txt3.03 KBwim leers
#220 2994696-219.png430.99 KBwim leers
#218 2994696-218.patch86 KBwim leers
#218 interdiff.txt2.28 KBwim leers
#216 2994696-216.patch88.17 KBwim leers
#216 interdiff.txt12.38 KBwim leers
#216 interdiff-css.txt6.07 KBwim leers
#216 interdiff-details.txt7.37 KBwim leers
#216 missing-media-after.png19.41 KBwim leers
#216 missing-media-before.png11.85 KBwim leers
#214 2994696-214.patch88.2 KBwim leers
#214 interdiff.txt29.17 KBwim leers
#210 2994696-interdiff--194-210.txt109.97 KBoknate
#210 2994696-interdiff--206-210.txt7.75 KBoknate
#210 2994696-210.patch92.48 KBoknate
#208 Screen Shot 2019-08-01 at 21.36.12.png53.65 KBlauriii
#206 2994696-206.patch91.36 KBoknate
#206 2994696--interdiff-203-206.txt1.58 KBoknate
#206 2994696--interdiff-194-206.txt24.1 KBoknate
#203 2994696--interdiff-200-203.txt704 bytesoknate
#203 2994696-203.patch91.34 KBoknate
#203 2994696--interdiff-194-203.txt23.67 KBoknate
#201 2994696-198.patch91.29 KBoknate
#201 2994696--interdiff-194-198.txt23.35 KBoknate
#194 2994696-194.patch89.63 KBoknate
#194 2994696-interdiff--193-194.txt754 bytesoknate
#193 2994696-193.patch90.36 KBoknate
#193 2994696-interdiff--192-193.txt681 bytesoknate
#192 2994696-192.patch91.03 KBoknate
#192 2994696-interdiff--189-192.txt15.08 KBoknate
#189 right-align--with-caption.png307.78 KBstarshaped
#189 right-align--no-caption.png337.77 KBstarshaped
#189 center-align--with-caption.png233.31 KBstarshaped
#189 center-align--no-caption.png323.57 KBstarshaped
#189 left-align--with-caption.png344.79 KBstarshaped
#189 left-align--no-caption.png338.92 KBstarshaped
#189 no-align--with-caption.png304.92 KBstarshaped
#189 no-align--no-caption.png430.94 KBstarshaped
#189 interdiff-2994696-187-189.txt1.95 KBstarshaped
#189 2994696-189.patch91.42 KBstarshaped
#187 2994696-187.patch90.76 KBoknate
#187 2994696--interdiff-186-187.txt677 bytesoknate
#186 2994696-186.patch90.76 KBoknate
#186 2994696--interdiff-184-186.txt12.03 KBoknate
#185 2994696-185.patch211.74 KBoknate
#185 2994696--interdiff-184-185.txt12.03 KBoknate
#184 2994696-184.patch89.93 KBoknate
#184 2994696--interdiff-182-184.txt3.27 KBoknate
#182 2994696-182.patch89.93 KBoknate
#182 2994696--interdiff-181-182.txt1.4 KBoknate
#181 2994696-181.patch89.95 KBoknate
#181 2994696--interdiff-180-181.txt7.19 KBoknate
#180 margin inside outline again.png64.69 KBoknate
#180 drupallink context menu on media-embed.png50.99 KBoknate
#180 2994696-180.patch86.42 KBoknate
#180 2994696--interdiff-179-180.txt2.62 KBoknate
#179 2994696-179.patch85.52 KBoknate
#179 2994696--interdiff-176-179.txt18.75 KBoknate
#178 2994696-178.patch85.54 KBoknate
#178 2994696--interdiff-176-178.txt16.64 KBoknate
#176 2994696-176.patch83.49 KBoknate
#176 2994696--interdiff-175-176.txt1.14 KBoknate
#175 2994696-175.patch82.97 KBoknate
#175 2994696--interdiff-174-175.txt1.78 KBoknate
#174 2994696-174.patch82.95 KBoknate
#174 2994696--interdiff-173-174.txt1.18 KBoknate
#173 2994696-173.patch82.81 KBoknate
#173 2994696--interdiff-172-173.txt4.03 KBoknate
#172 2994696-172.patch82.9 KBoknate
#172 2994696--interdiff-167-172.txt26.76 KBoknate
#167 revised-io-error.png180.92 KBoknate
#167 2994696-167.patch83.54 KBoknate
#167 2994696--interdiff-161-167.txt4.87 KBoknate
#165 2994696-161.patch83.98 KBoknate
#165 2994696--interdiff-160-161.txt1.74 KBoknate
#165 no-header-label.png180.03 KBoknate
#160 missing-media-message-update-6.png264.24 KBoknate
#160 missing-media-message-update-5.png241.6 KBoknate
#160 missing-media-message-update-4.png240.8 KBoknate
#160 missing-media-message-update-3.png202.4 KBoknate
#160 missing-media-message-update-2.png200.98 KBoknate
#160 missing-media-message-update-1.png232.79 KBoknate
#160 2994696-160.patch84.32 KBoknate
#160 2994696--interdiff-159-160.txt2.85 KBoknate
#159 2994696-159.patch83.67 KBoknate
#159 2994696--interdiff-157-159.txt1.8 KBoknate
#157 align-right before paragraph.png190.74 KBoknate
#157 align-right before paragraph markup.png162.18 KBoknate
#157 2994696-157.patch83.35 KBoknate
#157 2994696--interdiff-154-157.txt2.92 KBoknate
#157 2994696-157--FAIL.patch83.02 KBoknate
#154 2994696--interdiff-151-152.txt5.35 KBoknate
#154 2994696-152.patch82.71 KBoknate
#154 2994696-152--FAIL.patch82.1 KBoknate
#152 2994696-151.patch78.74 KBoknate
#152 2994696--interdiff-150-151.txt8.21 KBoknate
#151 2994696-150.patch80.72 KBoknate
#151 2994696--interdiff-147-150.txt6 KBoknate
#148 Screen Shot 2019-07-22 at 22.20.25.png8.02 KBlauriii
#147 2994696--interdiff-146-147.txt6.48 KBoknate
#147 2994696-147.patch75.66 KBoknate
#146 2994696--interdiff-135-146.txt38.66 KBoknate
#146 2994696--interdiff-145-146.txt4.53 KBoknate
#146 2994696-146.patch74.39 KBoknate
#145 missing media - like media library 2.png36.04 KBoknate
#145 missing media - like media library.png34.91 KBoknate
#145 2994696-145.patch71.17 KBoknate
#145 2994696--interdiff-144-145.txt1.75 KBoknate
#144 error display using classy theming.png33.96 KBoknate
#144 2994696--interdiff-140-144.txt6.63 KBoknate
#144 2994696-144.patch71.18 KBoknate
#142 2994696-140.patch71.54 KBoknate
#142 2994696--interdiff-139-140.txt9.09 KBoknate
#141 2994696-139.patch68.69 KBoknate
#141 2994696--interdiff-138-139.txt703 bytesoknate
#138 error due to arrow function.png153.62 KBoknate
#138 2994696-138.patch68.69 KBoknate
#138 2994696--interdiff-135-138.txt24.29 KBoknate
#135 2994696-135.patch68.82 KBoknate
#135 2994696--interdiff-128-135.txt9.06 KBoknate
#135 2994696--interdiff-133-135.txt3.82 KBoknate
#133 2994696-133.patch65.3 KBoknate
#133 2994696--interdiff-131-133.txt748 bytesoknate
#131 2994696-131.patch65.31 KBoknate
#131 2994696--interdiff-130-131.txt4.49 KBoknate
#131 2994696--interdiff-128-131.txt5.97 KBoknate
#130 bartik--right--caption.png79.26 KBoknate
#130 bartik--center--caption.png80.51 KBoknate
#130 bartik--left--caption.png78.94 KBoknate
#130 bartik--right--no-caption.png67.74 KBoknate
#130 bartik--center--no-caption.png68.82 KBoknate
#130 bartik--left--no-caption.png67.14 KBoknate
#130 2994696-130.patch65.34 KBoknate
#130 2994696--interdiff-128-130.txt6.16 KBoknate
#128 2994696-128.patch63.1 KBoknate
#128 2994696--interdiff-121-128.txt754 bytesoknate
#126 2994696-126--revert-bem-style-changes.patch61.06 KBoknate
#126 2994696--interdiff-121-126-revert.txt3.45 KBoknate
#125 2994696-124--revert-bem-style-changes.patch61.91 KBoknate
#125 2994696--interdiff-121-124-revert.txt2.08 KBoknate
#125 2994696-124--use-set-style-drop-css-file.patch61.42 KBoknate
#125 2994696--interdiff-121-124-set-style.txt5.22 KBoknate
#121 2994696-121.patch62.36 KBoknate
#121 2994696--interdiff-119-121.txt4.02 KBoknate
#120 error output.png40.13 KBoknate
#119 2994696-119.patch61.28 KBoknate
#119 2994696--interdiff-112-119.txt16.81 KBoknate
#112 2994696-112.patch57.04 KBoknate
#112 2994696--interdiff-111-112.txt3.03 KBoknate
#111 2994696-111.patch55.85 KBoknate
#111 2994696--interdiff-109-111.txt2.39 KBoknate
#109 2994696-109.patch55.4 KBoknate
#109 2994696--interdiff-106-109.txt2.82 KBoknate
#109 2994696--interdiff-99-109.txt4.61 KBoknate
#107 2994696--interdiff-102-106.txt2.34 KBoknate
#106 2994696-106.patch55.16 KBoknate
#106 2994696--interdiff-105-106.txt339 bytesoknate
#106 2994696--interdiff-105-106.txt339 bytesoknate
#105 2994696-105.patch55.49 KBoknate
#105 2994696--interdiff-104-105.txt3.17 KBoknate
#104 2994696-104.patch55.29 KBoknate
#104 2994696--interdiff-102-104.txt1.16 KBoknate
#102 2994696-102.patch54.49 KBoknate
#102 2994696--interdiff-99-102.txt2.68 KBoknate
#99 2994696-99.patch54.26 KBoknate
#99 2994696-interdiff--98-99.txt2.38 KBoknate
#98 aria-hidden-on-p.png42.13 KBoknate
#98 aria-hidden-on-image.png41.89 KBoknate
#98 2994696--interdiff-93-98.txt2.48 KBoknate
#98 2994696-98.patch52.6 KBoknate
#97 2994696--interdiff-25-93.txt24.29 KBoknate
#96 2994696--interdiff-25-69.txt22.11 KBoknate
#94 93 sans caption.png47.61 KBoknate
#94 93 with caption.png58.17 KBoknate
#93 2994696-93.patch52.33 KBoknate
#93 2994696--interdiff-91-93.txt1.74 KBoknate
#92 interdiff--changes-removed-from-69.patch2.52 KBoknate
#91 2994696-91.patch51.29 KBoknate
#91 2994696--interdiff-83-91.txt16.7 KBoknate
#91 2994696--interdiff-69-91.txt12.59 KBoknate
#88 Screen Shot 2019-07-15 at 23.48.08.png9.04 KBlauriii
#83 2994696-83.patch44.09 KBoknate
#83 2994696--interdiff-79-83.txt3.95 KBoknate
#82 Screen Shot 2019-07-15 at 17.53.26.png6.94 KBlauriii
#79 2994696-79.patch44 KBoknate
#79 2994696--interdiff-62-79.txt6.03 KBoknate
#76 Screen Shot 2019-07-12 at 23.45.21.png14.76 KBlauriii
#76 Screen Shot 2019-07-12 at 23.45.14.png442.55 KBlauriii
#69 interdiff-2994696-65-69.txt16.11 KBphenaproxima
#69 2994696-69.patch47.29 KBphenaproxima
#65 interdiff-2994696-62-65.txt18.47 KBphenaproxima
#65 2994696-65.patch45.25 KBphenaproxima
#62 2994696-62.patch40.16 KBoknate
#62 2994696-interdiff--25-62.patch2.52 KBoknate
#25 2994696-25.patch40.37 KBwim leers
#25 interdiff.txt7.85 KBwim leers
#24 2994696-24.patch35.93 KBwim leers
#23 2994696-23-combined-2940029-99.patch103.4 KBwim leers
#23 2994696-23-do-not-test.patch35.93 KBwim leers
#23 interdiff.txt2.34 KBwim leers
#22 2994696-16-combined-2940029-91.patch101.92 KBwim leers
#16 2994696-16-do-not-test.patch36.02 KBwim leers
#16 2994696-16-combined-2940029-68.patch95.97 KBwim leers
#16 interdiff.txt1.14 KBwim leers
#12 2994696-12-combined-2940029-68.patch96.05 KBwim leers
#12 2994696-12-do-not-test.patch36.1 KBwim leers
#12 interdiff.txt7.89 KBwim leers
#11 2994696-11-combined-2940029-68.patch90.13 KBwim leers
#11 2994696-11-do-not-test.patch30.17 KBwim leers
#11 interdiff.txt11.07 KBwim leers
#10 media_embed cached preview requests.mov3.24 MBwim leers
#10 2994696-10-combined-2940029-68.patch81.59 KBwim leers
#10 2994696-10-do-not-test.patch21.65 KBwim leers
#10 media_embed CKEditor testing steps screenshot.png887.27 KBwim leers

Comments

phenaproxima created an issue. See original summary.

phenaproxima’s picture

Title: Represent embedded entities in CKeditor » [PP-1] Represent embedded entities in CKeditor
legovaer’s picture

Title: [PP-1] Represent embedded entities in CKeditor » [PP-2] Represent embedded entities in CKeditor
phenaproxima’s picture

Title: [PP-2] Represent embedded entities in CKeditor » [PP-1] Represent embedded entities in CKeditor

What else is this blocked on? Please add a related issue before changing the postponement counter.

phenaproxima’s picture

Title: [PP-1] Represent embedded entities in CKeditor » [PP-2] Represent embedded entities in CKeditor
wim leers’s picture

Title: [PP-2] Represent embedded entities in CKeditor » [PP-2] Represent embedded entities in CKEditor

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

wim leers’s picture

Title: [PP-2] Represent embedded entities in CKEditor » [PP-1] Render embedded media items in CKEditor
Assigned: Unassigned » wim leers
Priority: Normal » Major
Issue tags: +Usability
Related issues: +#2940029: Add an input filter to display embedded Media entities

As of #5, this has been marked as postponed on #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library. I understand the original reasoning, but … we have to have a @Filter plugin first, that's what #2940029: Add an input filter to display embedded Media entities does. If we want to do the filter first, initially one would have to craft the HTML manually anyway (and note that this won't ship with a Drupal release as-is, we'll want to complete this and #2994699 too in the same release!). So, then it makes much more sense to have the same thing be visible both in a rendered blog posts (/node/1 has a visible embedded image media item thanks to the filter) and in edited blog posts (/node/1/edit has a visible embedded image media item thanks to a CKEditor plugin).

So, swapping the order.

Ironically, this means that the first sentence in the issue summary is once again accurate: This is a follow-up to #2940029: Add an input filter to display embedded Media entities, and the second step of #2801307: [META] Support WYSIWYG embedding of media entities. 🥳

wim leers’s picture

Issue summary: View changes
Related issues: +#2039163: Update CKEditor library to 4.4
wim leers’s picture

Here we go! 🚀

  • Initial patch with the basics: the <drupal-media> HTML tag is upcast by the CKEditor Widget into a visual representation closely matching that on the front end (a preview).
  • No configuration needed: thanks to \Drupal\ckeditor\CKEditorPluginContextualInterface it is automatically enabled whenever @Filter=media_embed is enabled.
  • Testing steps added to the issue summary, they build on those for #2940029: Add an input filter to display embedded Media entities! It looks like … well, see the screenshot below :)
  • Also see the attached screencast showing how it behaves on high-latency connections (I simulated 500 ms latency).

P.S.: I know that this still needs to be converted to *.es6.js, but brew install npm broke my PHP, because https://github.com/Homebrew/homebrew-core/issues/39567 🤷‍♂️ 🤯🤬I don't want to waste time on fixing my dev environment again. We can do that in a future patch iteration 🙂

wim leers’s picture

StatusFileSize
new11.07 KB
new30.17 KB
new90.13 KB

Foundations are green :)

Now let's add the next important feature:

offer exactly the same great in-place caption editing UX as we currently have for images (core's DrupalImageCaption CKEditor plugin)

In the previous patch, the caption is visible (thanks to the server-side rendered preview), but it's not yet editable. That's what this interdiff adds.

Again with test coverage of course — note that Drupal core's existing captioning support for images does not have any test coverage!

wim leers’s picture

StatusFileSize
new7.89 KB
new36.1 KB
new96.05 KB

Still green! ✅

Time to add the last feature:

offer exactly the same linking support

Now you can select the embedded media and press CTRL+K/CMD+K or click the "Link" button, to make the embedded media link somewhere.

Again with test coverage. And again Drupal core doesn't have any test coverage for this. (It predates us having JavaScript tests.)

wim leers’s picture

And still green ✅🥳

With regards to

have its code reviewed & approved by the CKEditor team to ensure long-term maintainability and stability

I can share this, from @kkrzton, the CKEditor 4 Lead Developer:

What is your current assessment is of the code quality and particularly long-term maintainability?

I would say the code is CK-like 🙂 What I mean is that when working on it it felt familiar to our code base (apart from some code styling and spacing but it’s not important), easy to understand and has proper separation so it’s 👍 from me 😉 When it comes to long-term maintainability it would be good to have some significant code coverage. AFAIR you already created some test with Selenium, so maybe that’s already covered? It would be good to have basic scenarios cover (like single insert, edit, one undo/redo, etc). and then a few more tests with more complex scenarios (e.g. combining insertion of 2 widgets and undo).

So we're in a good place. Many thanks to the CKEditor team for their feedback and advice!

wim leers’s picture

I want to provide a little bit of context:

  1. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,230 @@
    +  function getFocusedWidget(editor) {
    ...
    +  function linkCommandIntegrator(editor) {
    ...
    +      linkCommandIntegrator(editor);
    

    This is copied verbatim from drupalimage. It can be confusing to those who don't know this code (it was to me after not having seen this for about five years), but this was written by the CKEditor team themselves.

  2. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,230 @@
    +      // drupallink has a hardcoded integration with drupalimage. Work around that, to reuse the same integration.
    +      var originalGetFocusedWidget = null;
    +      if (CKEDITOR.plugins.drupalimage) {
    +        originalGetFocusedWidget = CKEDITOR.plugins.drupalimage.getFocusedWidget;
    +      }
    +      else {
    +        CKEDITOR.plugins.drupalimage = {};
    +      }
    +      CKEDITOR.plugins.drupalimage.getFocusedWidget = function () {
    +        var ourFocusedWidget = getFocusedWidget(editor);
    +        if (ourFocusedWidget) {
    +          return ourFocusedWidget;
    +        }
    +        // If drupalimage is loaded, call that next, to not break its link command integration.
    +        if (originalGetFocusedWidget) {
    +          return originalGetFocusedWidget(editor);
    +        }
    +        return null;
    +      };
    

    This can presumably be simplified by modifying the drupalimage CKEditor plugin to make fewer assumptions. Then all this code could go away. The entity_embed contrib module of course could not do this, but here we can 🙂

phenaproxima’s picture

  1. +++ b/core/modules/media/media.routing.yml
    @@ -39,3 +39,10 @@ media.settings:
    +media.filter.preview:
    

    'media.filter.preview' seems like a somewhat generic name for the route. How about 'media.embed_filter.preview'?

  2. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,93 @@
    +   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    +   *   Throws an exception if 'text' parameter is not found in the request.
    

    I think this should be a BadRequestHttpException, since technically the URL is "found", it's just being given garbage parameters.

  3. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,93 @@
    +    $text = $request->get('text');
    

    Nit: Should we specifically check the POST data for 'text', rather than just $request->get()?

  4. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,276 @@
    +  use ContentTypeCreationTrait;
    

    WebDriverTestBase already uses ContentTypeCreationTrait. :)

  5. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,276 @@
    +  public static $modules = [
    

    Should be protected.

  6. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,276 @@
    +    // Assert that `<p data-* …>` is not upcast into a CKEditor Widget.
    

    Nit: This ellipsis is a specialized character; we should probably use three ASCII periods instead.

  7. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,276 @@
    +(function(){
    +  return window.performance
    +    .getEntries()
    +    .filter(function (entry) {
    +      return entry.initiatorType == 'xmlhttprequest' && entry.name.indexOf('/media/test_format/preview') !== -1;
    +    })
    +    .pop()
    +    .transferSize;
    +})()
    

    😲

wim leers’s picture

StatusFileSize
new1.14 KB
new95.97 KB
new36.02 KB

#15:

  1. 🤔 I disagree. How many filters will the media module add?
  2. 🤔 This is just following an existing pattern in core, see \Drupal\editor\EditorController::filterXss().
  3. 🤔 See previous point.
  4. ✅ Nice catch! Fixed :)
  5. ✅ Another nice catch! This is apparently wrong in many places in the Entity Embed module :P
  6. 👎There's plenty of examples of this in Drupal core already.
  7. 🤓 That was the most fun and coolest test I've written in like a year!
wim leers’s picture

Issue summary: View changes

This uncovered two bugs in CKEditor itself by the way; those will need follow-ups.

phenaproxima’s picture

I disagree. How many filters will the media module add?

Probably only this one, but if we ever add another one, the route name will become ambiguous. Renaming it would make it more clear anyway -- I still think we should change it.

This is just following an existing pattern in core, see \Drupal\editor\EditorController::filterXss().

Hmmm...I guess I don't feel too strongly about it, but it still seems kinda like security-by-obscurity to me.

oknate’s picture

For #15.4 and #15.5, I added an issue to Entity Embed to fix those in the corresponding test. #3064846: [META] Port good ideas from core media embed to entity embed

Thanks, Phenaproxima.

wim leers’s picture

Probably only this one, but if we ever add another one, the route name will become ambiguous.

I think we can change it at that time. The route name says it's for the media module's filter, namely to preview it. Hence media.filter.preview I don't see what's ambiguous about that?

I think the suggested media.embed_filter.preview route name is worse, because there is nothing named embed_filter. If you want to disambiguate between different filters, then media.filter.media_embed.preview perhaps makes sense. But … this route does not actually do anything specific to the media_embed filter at all. Its controller docs even say exactly that!

But perhaps media.editor.preview would be a better name? Because it's about previewing in the context of a text editor.

I don't feel strongly about the route name either, but if we're going to change it, I think we should all agree it's clearer. Hopefully this comment helps us get there 🙂

wim leers’s picture

Other important context:

  • This patch only allows the caption text to be edited in-place.
  • It does not allow the caption itself to be turned on or off (that requires going into "source" mode and adding/removing the data-caption attribute yourself right now).
  • It does not allow the alignment to be changed (that too requires going into "source" mode and adding/removing the data-align attribute yourself right now).

Why?

This is blocked on design and UX team sign-off. See #2994702-12: Allow editors to alter embed-specific metadata, as well as `data-align` and `data-caption` for details. See #2994702-13: Allow editors to alter embed-specific metadata, as well as `data-align` and `data-caption` and #2994702-14: Allow editors to alter embed-specific metadata, as well as `data-align` and `data-caption` for patches that add support for this.

In other words: we're not punting on this, it just doesn't make sense to be included in the scope of this issue.

wim leers’s picture

StatusFileSize
new101.92 KB

A few rounds of feedback have been incorporated into #2940029, rebasing #16 on top of #2940029-91: Add an input filter to display embedded Media entities.

wim leers’s picture

StatusFileSize
new2.34 KB
new35.93 KB
new103.4 KB

Rerolled #16 to match #2940029-97: Add an input filter to display embedded Media entities's marking of data-view-mode as optional.

wim leers’s picture

Title: [PP-1] Render embedded media items in CKEditor » Render embedded media items in CKEditor
Status: Postponed » Needs review
StatusFileSize
new35.93 KB
wim leers’s picture

StatusFileSize
new7.85 KB
new40.37 KB

One last self-review now that I haven't looked at this in a few weeks:

  1. +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    --- /dev/null
    +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    

    This still needs to be converted to *.es6.js. Last time I installed the necessary tooling, my dev env stopped working. I'm traveling next week. I am hence very hesitant to do this myself right now. If anybody else would like to do that, that'd be splendid 🙏

    (This hasn't happened yet in the Entity Embed contrib module because apparently https://www.drupal.org/node/2815083 doesn't work for contrib, only for core. #2957390: Use ES6 for contrib and custom JavaScript development exists to bring this to contrib too.)

  2. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,230 @@
    +      // drupallink has a hardcoded integration with drupalimage. Work around that, to reuse the same integration.
    +      var originalGetFocusedWidget = null;
    +      if (CKEDITOR.plugins.drupalimage) {
    +        originalGetFocusedWidget = CKEDITOR.plugins.drupalimage.getFocusedWidget;
    +      }
    +      else {
    +        CKEDITOR.plugins.drupalimage = {};
    +      }
    +      CKEDITOR.plugins.drupalimage.getFocusedWidget = function () {
    +        var ourFocusedWidget = getFocusedWidget(editor);
    +        if (ourFocusedWidget) {
    +          return ourFocusedWidget;
    +        }
    +        // If drupalimage is loaded, call that next, to not break its link command integration.
    +        if (originalGetFocusedWidget) {
    +          return originalGetFocusedWidget(editor);
    +        }
    +        return null;
    +      };
    

    Now that this is being brought to Drupal core, it probably makes more sense to instead make the drupallink plugin not be as tightly coupled to drupalimage.

    Done. ✅

  3. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,230 @@
    +        // Minimum HTML which is required by this widget to work.
    

    This comment needs to be moved one line. ✅

  4. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,93 @@
    + *   This is an internal part of the oEmbed system and should only be used by
    

    C/P leftover. Fixed. ✅

  5. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,84 @@
    +      drupal_get_path('module', 'system') . '/css/components/hidden.module.css',
    

    🤔

    This arguably should not be done here, in the drupalmedia CKEditor plugin, but in \Drupal\ckeditor\Plugin\Editor\CKEditor::buildContentsCssJSSetting() directly. But … this only actually becomes relevant once Drupal-rendered content is embedded in CKEditor. So that arguably means this is correct.

    I don't feel strongly either way, let's find if reviewers have strong opinions about this :)

  6. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,487 @@
    +    File::create([
    +      'uri' => $this->getTestFiles('image')[0]->uri,
    +    ])->save();
    

    Nit: this could use File::setFileUri(). Argh, but because File::setFileUri() has a broken implementation, we actually can't. Never mind 🙄

wim leers’s picture

Issue summary: View changes

Updating IS to match changes made in #2940029: Add an input filter to display embedded Media entities. #23 should already have done that.

wim leers’s picture

Here too I want to make sure all people who ever contributed are credited. People who contributed to Entity Embed's js/plugins/drupalentity/plugin.js, which is the spiritual predecessor:

a.milkovsky
Ambidex
artis
BartK
Berdir
cs_shadow
CTaPByK
Dave Reid
duncan.moo
idflood
JamesK
John Pitcairn
kmoll
ls206
marcoscano
MobliMic
narnua
oknate
phenaproxima
pixelmord
Reinmar
root_brute
Ruuds
slashrsm
thenchev
webflo
Wim Leers
wrd
yched

Status: Needs review » Needs work

The last submitted patch, 25: 2994696-25.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review

The failures are in SkipOpTest, which was added yesterday by #2982684: Add a composer scaffolding plugin to core.

Looks like a random failure:

1) Drupal\Tests\Component\Scaffold\Integration\SkipOpTest::testProcess
RuntimeException: /tmp/composer-scaffold-test-805077626069a387b07f5263c386f338 does not exist and could not be created.

Retesting.

oknate’s picture

For what it's worth. The test CKEditorIntegrationTest isn't passing on my local.

1) Drupal\Tests\media\FunctionalJavascript\CKEditorIntegrationTest::testLinkability
WebDriver\Exception\ElementNotVisible: element not interactable
  (Session info: chrome=75.0.3770.100)
  (Driver info: chromedriver=2.46.628411 (3324f4c8be9ff2f70a05a30ebc72ffb013e1a71e),platform=Mac OS X 10.11.6 x86_64)

/Users/oknate/dev/entity_browser_testing/web/vendor/instaclick/php-webdriver/lib/WebDriver/Exception.php:155
/Users/oknate/dev/entity_browser_testing/web/vendor/instaclick/php-webdriver/lib/WebDriver/AbstractWebDriver.php:157
/Users/oknate/dev/entity_browser_testing/web/vendor/instaclick/php-webdriver/lib/WebDriver/AbstractWebDriver.php:218
/Users/oknate/dev/entity_browser_testing/web/vendor/instaclick/php-webdriver/lib/WebDriver/Container.php:224
/Users/oknate/dev/entity_browser_testing/web/vendor/behat/mink-selenium2-driver/src/Selenium2Driver.php:775
/Users/oknate/dev/entity_browser_testing/web/vendor/behat/mink-selenium2-driver/src/Selenium2Driver.php:763
/Users/oknate/dev/entity_browser_testing/web/vendor/behat/mink/src/Element/NodeElement.php:153
/Users/oknate/dev/entity_browser_testing/web/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php:330
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestCase.php:1071
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestCase.php:939
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestResult.php:698
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestCase.php:894

and

Drupal\Tests\media\FunctionalJavascript\CKEditorIntegrationTest::testPreviewUsesDefaultThemeAndIsClientCacheable
Failed asserting that 913 is greater than 1024.

/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php:117
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php:62
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/Assert.php:2116
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/Assert.php:687
/Users/oknate/dev/entity_browser_testing/web/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php:198
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestCase.php:1071
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestCase.php:939
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestResult.php:698
/Users/oknate/dev/entity_browser_testing/web/vendor/phpunit/phpunit/src/Framework/TestCase.php:894

I can fix these errors with these changes:

diff --git a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
index b81e636891..5bfbe86d42 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
@@ -189,13 +189,13 @@ public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
     $element = $this->assertSession()->elementExists('css', '[data-media-embed-test-active-theme]');
     $this->assertSame('stable', $element->getAttribute('data-media-embed-test-active-theme'));
 
-    // Assert that the first preview request transferred >1 KB over the wire.
+    // Assert that the first preview request transferred >912 B over the wire.
     // Then toggle source mode on and off. This causes the CKEditor widget to be
     // destroyed and then reconstructed. Assert that during this reconstruction,
     // a second request is sent. This second request should have transferred 0
     // bytes: the browser should have cached the response, thus resulting in a
     // much better user experience.
-    $this->assertGreaterThan(1024, $this->getLastPreviewRequestTransferSize());
+    $this->assertGreaterThan(912, $this->getLastPreviewRequestTransferSize());
     $this->pressEditorButton('source');
     $this->assertSession()->waitForElement('css', 'textarea.cke_source');
     $this->pressEditorButton('source');
@@ -327,7 +327,7 @@ public function testLinkability() {
     $this->getSession()->switchToIFrame('ckeditor');
 
     // Select the CKEditor Widget and click the "link" button.
-    $this->assertSession()->elementExists('css', 'drupal-media')->click();
+    $this->assertSession()->waitForElementVisible('css', 'drupal-media')->click();
     $this->pressEditorButton('drupallink');
     $this->assertSession()->waitForId('drupal-modal');

I think these changes might make the test more robust. I sure would like it to work on my local. I certainly think the first change is harmless enough:

-    $this->assertSession()->elementExists('css', 'drupal-media')->click();
+    $this->assertSession()->waitForElementVisible('css', 'drupal-media')->click();

For the second one, with the Byte size, can you think of why that might be different for me? I'm just running it with chromedriver with php PHP 7.1.26 .

wim leers’s picture

Status: Needs review » Needs work

#30:

  • For that ElementNotVisible error: interesting that it passes both for me and testbot locally and it's not new test coverage, it's coming from Entity Embed! Is Entity Embed's CKEditor integration test coverage passing for you?

    I guess your computer must be running the test significantly slower, but it's certainly better to change >elementExists('css', 'drupal-media')->click() to waitForElementVisible('css', 'drupal-media')->click(). Go ahead and make that change! 👍

  • RE: 913 is greater than 1024 — hah, this 1024 byte thing was fairly arbitrary, but it seemed like a lower bound (since I'm test with d8.test as my domain, no subdirectory, so it can't get much shorter than that). Apparently I underestimated how much shorter it can be. Let's change it to >500 then to err on the safe side.

phenaproxima’s picture

Crediting people Wim listed in #27.

phenaproxima’s picture

Continuing with that. It's a lot of people!

phenaproxima credited wrd.

phenaproxima’s picture

Last batch.

oknate’s picture

@wim: re:

For that ElementNotVisible error: interesting that it passes both for me and testbot locally and it's not new test coverage, it's coming from Entity Embed! Is Entity Embed's CKEditor integration test coverage passing for you?

No. Some of the entity embed tests don't pass for me. You recently changed it from waitForElementVisible to elementExists. I'm suggesting to change it back in one place I encountered this. See #3064846.5. https://www.drupal.org/files/issues/2019-06-29/3064846-5.patch

If you're on a slower system it takes a bit for the preview to display, I guess.

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new2.52 KB
new40.16 KB

Updated patch based on #30 and #31.

wim leers’s picture

#61: 👍
#62: 🙏 One nice bonus punctuation nitpick you fixed there! 🤓 (FYI: give interdiffs the extension .txt to ensure DrupalCI doesn't try to test them. Yes this is weird, but it's the convention we have, and it works, once you know it :)

phenaproxima’s picture

Very cursory review -- looks pretty good to me! The JavaScript parts are very complex and will take some serious brain time to puzzle out. Since there's a bunch of CSS and JavaScript changes here, tagging for front-end framework manager review.

  1. +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
    @@ -61,6 +61,34 @@
    +  let registeredLinkableWidgets = [];
    

    Nit: I think this could be a const, since I don't think it will be reassigned. Also, it could probably use a comment to explain what it's for.

  2. +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
    @@ -61,6 +61,34 @@
    +   * @param {string} widgetName
    +   */
    +  function registerLinkableWidget(widgetName) {
    

    No description for the param?

  3. +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
    @@ -61,6 +61,34 @@
    +    if (widget && registeredLinkableWidgets.indexOf(widget.name) !== -1) {
    

    Nit: I think we could use registeredLinkableWidgets.includes() here, since it's an ES6 feature and should be automatically transpiled back to indexOf() for older browsers.

  4. +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    @@ -0,0 +1,15 @@
    +drupal-media {
    +  display: inline-block;
    +}
    +drupal-media[data-align=left],
    +drupal-media[data-align=right] {
    +  display: inline;
    +}
    +drupal-media[data-align=center] {
    +  display: flex;
    +}
    

    I think all of these could benefit from comments explaining why these display values will work as expected.

  5. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,212 @@
    +  function getFocusedWidget(editor) {
    

    This, and the other helper functions, would benefit from explanatory comments.

  6. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,212 @@
    +  function linkCommandIntegrator(editor) {
    

    integrateWithLinkCommands() seems like a more straightforward name for this function.

  7. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    @@ -0,0 +1,212 @@
    +            allowedContent: 'a[!href]; em strong cite code br',
    

    Should we also allow things like B and I tags here? How about span or button, for even more exotic cases? (i.e., visually-hidden bits of text, or a button disguised as a link...)

  8. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,84 @@
    +      drupal_get_path('module', 'media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css',
    +      drupal_get_path('module', 'system') . '/css/components/hidden.module.css',
    

    If we inject the extension.list.module service, we can avoid using drupal_get_path() here.

phenaproxima’s picture

StatusFileSize
new45.25 KB
new18.47 KB

Now with more ES6!

Status: Needs review » Needs work

The last submitted patch, 65: 2994696-65.patch, failed testing. View results

Wim Leers credited kkrzton.

wim leers’s picture

#64:

The JavaScript parts are very complex and will take some serious brain time to puzzle out. Since there's a bunch of CSS and JavaScript changes here, tagging for front-end framework manager review.

+1, but remember that in #13 I shared that the CKEditor 4 Lead Developer @kkrzton already reviewed this code; his suggestions were incorporated. In fact, I should credit him too!

  1. Sure, I'm sadly not familiar with ES6 yet.
  2. Good catch. Feel free to fix :)
  3. See 1 :)
  4. Answers can be found in #3037316: Show an outline around the <drupal-entity> element within CKEditor , specifically #3037316-24: Show an outline around the <drupal-entity> element within CKEditor . There's no elegant explanation for every one of these, they're the ones that worked through trial and error. I'd love this to be reviewed (and improved!) by a CSS expert. 🙏
  5. Those comments can largely be lifted from drupalimage/plugin.js.
  6. 👎 This is the name that CKEditor core maintainers chose to use in drupalimage/plugin.js and in other CKEditor plugins: https://github.com/ckeditor/ckeditor-dev/search?q=linkcommandintegrator&.... Let's stay consistent.
  7. Good remark, but … this is exactly the same as drupalimagecaption/plugin.js. Let's stay consistent.
  8. That'd be a nice minor improvement :) We should do the same for existing CKEditor plugins probably!

So I think I made points 1–5 and 8 actionable, I think points 6 and 7 do not require changes.

phenaproxima’s picture

Status: Needs work » Needs review
StatusFileSize
new47.29 KB
new16.11 KB

Addressed Wim's feedback in #68 and fixed the broken functionality. Turns out I got some this scoping wrong. :)

effulgentsia’s picture

I've only started reviewing this, so this is incomplete, but here's what I found so far...

  1. +++ b/core/modules/media/media.routing.yml
    @@ -39,3 +39,10 @@ media.settings:
    +  requirements:
    +    _entity_access: 'filter_format.use'
    

    Can we add an additional access requirement that the Media Embed filter is enabled for this format? E.g., if anonymous users only have access to formats that don't allow media embedding, then there's no reason to let them execute this controller.

  2. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,93 @@
    +    $text = $request->get('text');
    

    Can we change this to $request->query->get('text')? Or is there a reason that we need the flexibility of $request->get()?

Status: Needs review » Needs work

The last submitted patch, 69: 2994696-69.patch, failed testing. View results

wim leers’s picture

  1. +1 Question: it'd be easiest to check this in the controller, but in principle this should happen at route access checking time. That would require a new route access checker. Do you have a preference?
  2. This also was raised in #15.2. My answer then: This is just following an existing pattern in core, see \Drupal\editor\EditorController::filterXss().
oknate’s picture

I don't see a reason an anonymous user should ever be able to access the media.filter.preview route. What about adding
_user_is_logged_in: 'TRUE'?

How about a new operation,

_entity_access: 'filter_format.preview'

Then update FilterFormatAccessControlHandler to specify for preview access you must be logged in and have the same access as the 'use' access.

I don't think a specific access check for whether the filter_ckeditor_media_embed filter is enabled is necessary, from a security perspective, as long as the route is protected. I think it's fine for it just to not render the embed if the filter isn't enabled, if only users with the appropriate permission can access the route.

If we add another entity operation, site builders won't have to set it every time they create a filter format.

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new430 bytes
new40.19 KB
new1.97 KB
new41.7 KB

Here are some options for protecting the route with a new access configuration.

I noticed that filter_format.use is normally not given to anonymous users, but it can be. By explicitly preventing anonymous users from hitting this route, we add additional security.

I'm leaving out the es6 changes for now, until those are fixed. Apologies to @phenaproxima. I want to test these access changes, and I don't know es6 yet.

wim leers’s picture

#73:

The anonymous user may be able to use a text editor (for example for posting comments) and hence be able to use a format that includes media embeds. They may even be allowed to select media from the media library!

Adding a preview operation for filters may be possible too, but what does that mean? What would the semantical difference between preview and use be?

I don't think a specific access check for whether the filter_ckeditor_media_embed filter is enabled is necessary, from a security perspective, as long as the route is protected.

It isn't necessary strictly speaking, but we err on the side of caution and safety in Drupal core. Having one more route/controller be accessible to users that can use a text format (which could be the anonymous user!) means one extra potential attack vector. This is why @effulgentsia's proposal to neutralize this potential attack vector by denying access if the relevant filter isn't in use by the format makes sense.

lauriii’s picture

I started reviewing the patch. Unfortunately, I got stuck reviewing the 9 lines of CSS on the patch. Given the complexity around that, I thought I'd post the problems I found here to see if anyone else have time to trial and error more on how to solve this. I'm planning to continue reviewing other parts of the patch next week.

The styles must work in all iterations of data-attribute, as well as without it. Currently, there's some margin set to a child element so that it's rendered inside the border. The margin should be instead outside of the border. Whatever styles we come up with, should presumably rely on the frontend theme styles so that the CKEditor content matches closely what can be seen on the page.

Here's how this looks currently:

bnjmnm’s picture

There's one part of #76 that I'd like to get a bit more clarification on:

Whatever styles we come up with, should presumably rely on the frontend theme styles so that the CKEditor content matches closely what can be seen on the page.

I know that frontend theme styles can be provided to ckeditor via the theme's ckeditor_stylesheets: property in its info.yml file, but it seems like this approach would largely benefit Bartik (and Bartik-subtheme) users. Is there perhaps a different approach I'm overlooking that would more easily integrate this with a wider variety of themes?
If not, adding it to Bartik will still be beneficial even to non-bartik users, as they'll have something in the codebase to reference if they want to include this in their own themes. I'm interested in knowing if theres a more elegant contrib-friendly way to incorporate some of those FE styles in CKEditor.

wim leers’s picture

FWIW: I had a call with @lauriii earlier today to walk him through what the challenges are. In a nutshell: #68.4 + we cannot change the DOM structure (which involves two wrappers) + CKEditor Widgets have a cke_widget_focused style that adds an outline that should work. The challenge is that due to the wrappers, the outline looks slightly weird, like @lauriii demonstrates in these screenshots.

#77: That's why this CSS does not live in a theme, but is loaded into CKEditor as part of the CKEditor plugin:

+++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css

+++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
@@ -0,0 +1,123 @@
+      $this->moduleList->getPath('media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css',

I'm sure @lauriii meant that the CSS should not be coupled to Bartik, just like the current CSS is not. You can find inspiration in core/modules/media/css/filter.caption.css for CSS tweaks that are theme-agnostic yet are able to remove margins and padding that a particular theme is adding.

oknate’s picture

StatusFileSize
new6.03 KB
new44 KB

Reworking the preview route access. Adding custom access check to check if media_embed filter is enabled on the given filter format.

Adding one unrelated coding style fix. I don't think semicolons belong in the places I changed, and the coder linter was complaining these lines don't end in a period. Perhaps those should be colons, if the point is to indicate that the sentence refers to the example code below.

-    //   and therefore we have to do it explicitly here for the embedded entity;
+    //   and therefore we have to do it explicitly here for the embedded entity.
     $build['#access'] = $media->access('view', NULL, TRUE);
wim leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -90,4 +92,23 @@ public function preview(Request $request, FilterFormatInterface $filter_format)
    +   *   The currently logged in account.
    

    Nit: s/account/user/

  2. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -90,4 +92,23 @@ public function preview(Request $request, FilterFormatInterface $filter_format)
    +   *   The text format.
    

    Nit: The text format for which to check access.

  3. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -90,4 +92,23 @@ public function preview(Request $request, FilterFormatInterface $filter_format)
    +  public function checkAccess(AccountInterface $account, FilterFormatInterface $filter_format) {
    +
    +    $media_embed = $filter_format->filters('media_embed');
    

    Nit: Extraneous \n.

  4. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -90,4 +92,23 @@ public function preview(Request $request, FilterFormatInterface $filter_format)
    +    return AccessResult::allowedIf($filter_format->access('use', $account) &&  $media_embed->status === TRUE)
    +      ->cachePerPermissions()
    +      ->addCacheableDependency($filter_format);
    

    This should instead do:

    return $filter_format->access('use', $account, TRUE)
      ->andIf(AccessResult::allowedIf($media_embed->status === TRUE));
    

    Because that removes the need for the cachePerPermissions() and the addCacheableDependency() calls.

  5. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -386,6 +387,66 @@ public function testLinkabilityWhenDrupalImageIsAbsent() {
    +   *   Whether to test with media_embed filter enabled on the filter_format.
    ...
    +   *   Whether the logged-in user has access to use the filter_format.
    

    Nits:

    • s/on the filter_format/on the text format/
    • s/logged-in/logged in/
    • s/has access to/is allowed to/
  6. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -386,6 +387,66 @@ public function testLinkabilityWhenDrupalImageIsAbsent() {
    +    if (!$media_embed_enabled) {
    ...
    +    if (!$can_use_format) {
    

    In both cases, you're modifying the default situation. This requires the reader of the test to know about the default situation created in ::setUp().

    I think it'd be clearer if this always created a new text format and always created a new user, then there's no need for overrides and understanding things outside this test.

  7. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -386,6 +387,66 @@ public function testLinkabilityWhenDrupalImageIsAbsent() {
    +      $filter_format->setFilterConfig('media_embed', $media_embed->getConfiguration());
    

    Why is this line necessary?

lauriii’s picture

  1. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,245 @@
    +      // Register drupal-media element as an allowed child in each tag that can
    +      // contain a div element.
    ...
    +      dtd['a']['drupal-media'] = 1;
    

    This comment doesn't take into account that we allow this to be placed inside a elements at all. Could be great if we could explain why we want this to behave like a div, with the exception that it is also allowed to be placed inside a elements.

  2. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,245 @@
    +          if (element.name !== 'drupal-media' || attributes['data-entity-type'] !== 'media' || attributes['data-entity-uuid'] === undefined) {
    

    Could we explain somewhere why we require drupal-media to include data-entity-type attribute if we only expect it to have a single value?

  3. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,245 @@
    +          // Allow ckeditor.drupalmedia.css to respond to changes (for example in alignment).
    +          this.element.setAttributes(this.data.attributes);
    

    For better consistency across Drupal, we should convert the current selectors used for targeting the drupal-media element into following BEM and CSS classes.

  4. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,245 @@
    +          // Track the previous state, to allow for smarter decisions.
    

    "to allow for smarter decisions" could use some extra clarity.

  5. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,245 @@
    +        _loadPreview(callback) {
    ...
    +              callback(this);
    

    Should we handle scenarios where the HTTP request fails as well? Currently, if the request fails for some reason, there's no indication at all for the user that something is missing or that something went wrong.

lauriii’s picture

Issue summary: View changes
StatusFileSize
new6.94 KB


This is a bit confusing because there's no explanation at all about what went wrong. Was there HTTP error or is there something wrong with the content?

oknate’s picture

StatusFileSize
new3.95 KB
new44.09 KB

Updating patch based on Wim's review in #80:
1) ✅
2) ✅
3) ✅
4) ✅Much nicer handing of the access results, and makes total sense.
5) ✅I checked before I added patch 79 and "logged in" and "logged-in" are both used as adjectival form in core. But changing, per your recommendation. Why are they called text formats if the machine name is FilterFormat? I guess, because that's how it's done?
6) ✅I reworked the test to use a new text format.
7) 👍You're right, that line wasn't necessary. It's been refactored away now. But I tested, and because the object is passed by reference, that line wasn't necessary.

oknate’s picture

@laurii, in regards to #82. The motivation for the placeholder was that it was invisible and therefore you couldn't focus on the broken embed to delete it. It has alt and title text to indicate something has gone wrong. Are you suggesting there should be some visible text as well, indicating what the issue is?

lauriii’s picture

The motivation for the placeholder was that it was invisible and therefore you couldn't focus on the broken embed to delete it.

If users are unable to remove broken embeds, how are they supposed to deal with broken embeds? I'm also wondering if this is something that should already happen with the current patch? For some reason, I'm able to focus on broken media and delete it.

It has alt and title text to indicate something has gone wrong. Are you suggesting there should be some visible text as well, indicating what the issue is?

Having a title and alt attributes do help deliver the message. If we would make the same text visible also in the UI, it would be even clearer and easier for users to understand why the media isn't rendered as they would expect. 🙂

oknate’s picture

I guess I wasn't clear:

If users are unable to remove broken embeds, how are they supposed to deal with broken embeds?

The point of adding a broken image indicator was, before the current patch (back in the original version of the filter in entity_embed module), the embed just wouldn't render, so there was nothing in the editor to allow focus. The current patch has the image indicating that the embed is broken because the entity can't be loaded.

I'm able to focus on broken media and delete it.

That's what the placeholder image allows.

wim leers’s picture

#81

  1. 👍 +1 for an extra comment for the dtd['a']… line.
  2. Because it matches the behavior of the filter. See #2940029: Add an input filter to display embedded Media entities. A similar question about the data-entity-type attribute was raised there. Would you like a comment such as // Match the behavior of the corresponding filter plugin.?
  3. 🤔 Hm … I do not understand what this is asking.
  4. 👍
  5. 🤔 Yes … except pretty much nothing in Drupal core does this, so I chose to keep it simple and similar. The indication will be that it looks broken.

#82: this relates to #81.5 — I agree this could be improved. But let's be clear: this is an exceptional case. The alt and title do explain what is wrong. The content author must be able to go in and make their one change without having to click through multiple error notifications and without being forced to fix this. But they do need to be able to fix this.
#2982322: Mark missing embedded entities in WYSIWYG is the issue that did this. The rationale:

Embeds of missing entities are made accessible (and visible) in both CKEditor (for the content authors, so they can fix it) and the filtered text (for the readers, so they can still make sense of the text, and potentially report it to the site owner).

Perhaps we just need a better "missing media" image?


#83.5: because FilterFormat is the internal code-level name, "text format" is the name of the concept. Just like we say "content type" in the UI and "node type" in the code.

lauriii’s picture

Issue summary: View changes
StatusFileSize
new9.04 KB

#87.3: we are currently using the element type and data attribute for attaching styles to the element. Instead, we should be using CSS classes.

#87.5 there's no indication that something went from. All the user will be able to see is white.

#87: I agree that we shouldn't have error notification or anything like that. I think it would be enough to just display the current title/alt text on the screen along with the image. Something like this:

phenaproxima’s picture

I discussed this whole "Ghostbusters" snafu with @lauriii and @Wim Leers on Slack.

Here is a summary of the problem:

  • If we cannot load the media item during text processing, we replace it with the Ghostbusters logo. It has an alt text of "Missing media", but this isn't particularly helpful to sighted users. It just looks broken with no real explanation, which is subpar UX.
  • It would therefore be nice to provide some visible text stating that the media item is missing, as per @lauriii's mockup in #88.
  • However, we cannot just add text into the generated markup, after the img tag, because that will break filter_caption if the media item has data-caption set. According to Wim, the resulting HTML would look like this: <figure><img><figcaption>foo</figcaption></figure><p>MESSAGE</p>. Not great.
  • We also can't just override the data-caption attribute to say something like "Missing media", because if the text format doesn't have filter_caption enabled, it won't show up (i.e., we'd be introducing a hidden dependency on filter_caption).

Seems like a pickle. Wim did suggest something like this as a possible solution:

.missing-media::after {
  content: '<p>Missing media.</p>';
}

This solution met with no real complaints from @lauriii, but it does present two sticking points:

  1. It's not translatable.
  2. The content property does not support HTML.

However, both of those sticking points have solutions too:

  1. On the server side, we could set a translated version of the "Missing media" string in a data attribute, and use it for the content property like so: content: attr(data-missing-media-placeholder).
  2. We don't really need HTML. The ::after area just needs to be aligned correctly and displayed as a block-level element, both of which are totally doable.

MDN confirms that attr() is extremely well supported by browsers: https://developer.mozilla.org/en-US/docs/Web/CSS/attr

However, this would still need manual testing and screenshots to confirm that it works correctly in all our supported browsers. That said, it's the best idea we have and we should sally forth with it.

wim leers’s picture

+1 to #89. I didn't know about The ::after area just needs to be aligned correctly and displayed as a block-level element, is true — looking forward to seeing that 😀

we should sally forth with it.

TIL this is an idiom and not a typo: https://www.merriam-webster.com/dictionary/sally%20forth

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new12.59 KB
new16.7 KB
new51.29 KB

Adding back phenaproxima's es6 changes from #69 (minus changes to drupallink, which was the source of the test failure).

This combines

  • #83
  • #69
  • leaves out changes to drupallink - those changes need to be re-examined and reworked
oknate’s picture

StatusFileSize
new2.52 KB

Here are the changes removed from 69 that will need to be re-examined and reworked.

I'm still trying to remember when I make interdiffs manually not to put .patch extension.

oknate’s picture

StatusFileSize
new1.74 KB
new52.33 KB

Here's an experiment to add a message to the missing media indicator without having to use a creative solution (see #89 for suggested creative solution). Perhaps I'm missing something, but when testing locally this seemed to work fine.

oknate’s picture

StatusFileSize
new58.17 KB
new47.61 KB

Here are two screenshots of changes in patch #93:

With caption:

with caption

Without caption:

no caption

wim leers’s picture

#91: I am very confused by that "69-91" interdiff. That includes most of the changes I made in #25, which were already in #69?

#92: again, the non-ES6 changes were made by me as early as #25.

#93 + #94: Hm … interesting. I'm not a fan because this generates different markup: it's no longer <img> but <div><img><p></div>. But perhaps this is what it takes. It looks acceptable to me as a visual user … but I think a screen reader user would not be happy to hear the exact same information twice. I think we could solve that by removing the <p> from the accessibility tree. Evidently I'm concerned we will need a new round of accessibility review, because the current approach already incorporated feedback from the accessibility maintainer in #3063705: Improve `alt` and `title` attributes for missing entity indicator after they gave feedback in the first core patch that went in, for the filter: #2940029: Add an input filter to display embedded Media entities.
Looking forward to @phenaproxima's feedback and especially @lauriii's.

oknate’s picture

StatusFileSize
new22.11 KB

That 69-91 interdiff must be off. I think because I removed the changes to the drupallink plugin.js, they showed up in the interdiff. Ignore that interdiff.

I tracked the failure to this bit of code:

diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
index b313a25b91..786a18812a 100644
--- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js
@@ -50,6 +50,7 @@
   }
 
   var registeredLinkableWidgets = [];
+
   function registerLinkableWidget(widgetName) {
     registeredLinkableWidgets.push(widgetName);
   }
@@ -101,19 +102,20 @@
         modes: { wysiwyg: 1 },
         canUndo: true,
         exec: function exec(editor) {
-          var focusedLinkableWidget = getFocusedLinkableWidget(editor);
+          var drupalImageUtils = CKEDITOR.plugins.drupalimage;
+          var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
           var linkElement = getSelectedLink(editor);
 
           var existingValues = {};
           if (linkElement && linkElement.$) {

if you change

 var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);

to

 var focusedImageWidget = getFocusedLinkableWidget(editor);

the error goes away.

After some sleuthing I found the issue:

The es6 in #25 doesn't transpile to the working js. Wim said as much in #25, and we have just missed it for several comments.

oknate’s picture

StatusFileSize
new24.29 KB

sigh, ignore this one, this has some changes from 69 on the 25 branch.

oknate’s picture

Issue tags: +Needs accessibility review
StatusFileSize
new52.6 KB
new2.48 KB
new41.89 KB
new42.13 KB

98 adds aria-hidden on the p tag. I tested adding on the image too, but decided to go with the p tag.
I also added some of the comments Lauriii recommended. #81.1 and #81.4.

oknate’s picture

StatusFileSize
new2.38 KB
new54.26 KB

in #25 Wim wrote:

This still needs to be converted to *.es6.js. Last time I installed the necessary tooling, my dev env stopped working . . . If anybody else would like to do that, that'd be splendid.

This lead to the drupalink javascript file in #69 to #98 to be either out of sync or wrong (failing). This patch fixes that, it changes the drupallink es6, so that when transpiled, it matches the js.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs review » Needs work

#97: Thanks for that separate interdiff, that makes it much easier for me to do a comprehensive review :)
#99 Thanks for figuring that out!

  1. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,112 @@
    +   *   The text format for which to check access.
    +   * @return \Drupal\Core\Access\AccessResultInterface
    

    Needs a blank line in between. (I'm surprised phpcs doesn't complain?!)

  2. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,112 @@
    +  public function checkAccess(AccountInterface $account, FilterFormatInterface $filter_format) {
    +
    +    $media_embed = $filter_format->filters('media_embed');
    

    Needs blank line to be removed.

  3. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    --- a/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    

    I'm not a fan of the fact that we're modifying the filter in this patch. #2940029: Add an input filter to display embedded Media entities just introduced it. But the only thing we're changing (besides punctuation nits in comments) is the missing media indicator.

    The importance of that indicator becomes more important in this issue, so refinements being made here is reasonable.

  4. +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    @@ -0,0 +1,19 @@
    +.missing-media__message {
    +  text-align: center;
    +}
    

    Why would this only be relevant in CKEditor? This is also relevant when it's displayed to the end user. Because the filter is what generates this message, and hence this markup also is displayed to end users.

    So this should be moved to filter.caption.css.

  5. +++ b/core/modules/media/media.routing.yml
    @@ -45,4 +45,4 @@ media.filter.preview:
    -    _entity_access: 'filter_format.use'
    +    _custom_access: '\Drupal\media\Controller\MediaFilterController::checkAccess'
    
    +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -90,4 +92,21 @@ public function preview(Request $request, FilterFormatInterface $filter_format)
    +  public function checkAccess(AccountInterface $account, FilterFormatInterface $filter_format) {
    +
    +    $media_embed = $filter_format->filters('media_embed');
    +    return $filter_format->access('use', $account, TRUE)
    +      ->andIf(AccessResult::allowedIf($media_embed->status === TRUE));
    +  }
    

    Actually … if we keep both _entity_access: … and _custom_access: …, then we can simplify the callback to just check if $media_embed->status === TRUE. And then the callback can be renamed to ::formatUsesMediaEmbedFilter().

    Because all requirements are ANDed.

wim leers’s picture

Once my #100 remarks are addressed, the patch looks ready to me. Next steps:

  1. @lauriii to confirm his concern from #88 has been addressed
  2. if @lauriii confirms this, we need to get accessibility maintainer sign-off per #98
  3. @effulgentsia to confirm his concern from #70.1 has been addressed and my answer to .2 was adequate
oknate’s picture

StatusFileSize
new2.68 KB
new54.49 KB

Addressing feedback from #100:

1 👍
2 👍
3 I'm not a big fan of changing the missing media indicator either. I think the ghostbusters logo is sufficient. We put a lot of work into making sure that was bug free for Entity Embed. To change it here last minute here is less-than-desirable.
4 Moved the css rule. UPDATE: This needs work now. It's no longer center-aligned, neither in the CKEditor, nor when the page is saved.
5 👍 Updated the access on the route to use two requirements.

oknate’s picture

Status: Needs work » Needs review
oknate’s picture

StatusFileSize
new1.16 KB
new55.29 KB

Fixing the css for #100.4. I checked and filter.caption.css seems to be loaded even if filter caption is disabled for the format, because the stable theme is loading it.

I feel there's something wrong with the way we're loading the css rule for the missing media here. It should to be tied to the media embed filter, rather than the caption filter. That way, if a theme doesn't have filter.caption.css it still works.

UPDATE

I can confirm, if your theme doesn't override filter.caption.css and filter_caption is not enabled for a text format, the missing media embed's p tag is not text aligned centered. So we need a different way to attach it.

oknate’s picture

StatusFileSize
new3.17 KB
new55.49 KB

Continuing to work on the css for the revised missing media indicator. I found the center alignment no longer works if you turn off filter caption (and if you're not using a theme that includes it, such as stable). Also, the width of the missing-media div would go full screen without filter caption on and a caption present. So I borrowed some of the css from filter.caption.css and applied it to the .missing-media div. I added a new library for this.
While the filter caption library is tied to the rendered media embed, it is not tied to the missing media indicator. So we'd have to add the caption library to missing embed indicator as well. Which would work, but seems like not the quite right place for it. If you have disabled filter caption to require its css for a completely separate element that seems odd.

oknate’s picture

StatusFileSize
new339 bytes
new339 bytes
new55.16 KB

This is the same as the last patch (#105), except that I had forgotten to delete one css rule:

diff --git a/core/modules/media/css/filter.caption.css b/core/modules/media/css/filter.caption.css
index dc69ce8aa6..a92505c308 100644
--- a/core/modules/media/css/filter.caption.css
+++ b/core/modules/media/css/filter.caption.css
@@ -8,7 +8,3 @@
   float: none;
   margin: unset;
 }
-
-.missing-media__message {
-  text-align: center;
-}
oknate’s picture

StatusFileSize
new2.34 KB

I missed this interdiff on the last comment, accidentally uploaded the same interdiff twice.

Status: Needs review » Needs work

The last submitted patch, 106: 2994696-106.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

oknate’s picture

StatusFileSize
new4.61 KB
new2.82 KB
new55.4 KB

I changed my mind about adding a new css library. When I saw the failure from #106 I realized I'd have to add it to the stable theme as well. And then I realized that I'm adding two css files right along media/filter.caption.css. So now I feel the redundancy is worse than the lack of accuracy of the name of the media/filter.caption.css (I was feeling it was inaccurate because it now affects more than just the caption). I do think we could rename media/filter.caption.css to media/filter.media_embed.css, I don't think it's absolutely necessary. And since this css file is already added to 8.8.x, I'd rather not start a discussion about renaming unnecessarily. It could turn into a red herring / bikeshed from the goal of getting this RTBC. So going back to Wim's original suggestion that I add it to filter.caption.css. It wasn't working, because I needed to add it to stable as well. Now that I am adding it to both the media/filter.caption.css in the media module and in the stable theme, the changes take affect properly. But we do need to add it to the wysiwyg.

diff --git a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
index 470d879c1c..316da2ae2f 100644
--- a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
+++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
@@ -77,7 +77,7 @@ public function isEnabled(Editor $editor) {
   public function getCssFiles(Editor $editor) {
     return [
       drupal_get_path('module', 'media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css',
+      drupal_get_path('module', 'media') . '/css/filter.caption.css',
       drupal_get_path('module', 'system') . '/css/components/hidden.module.css',
     ];
   }

as well as the render array for the missing media:

diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbed.php b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
index f134691f98..1a957f1825 100644
--- a/core/modules/media/src/Plugin/Filter/MediaEmbed.php
+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -233,7 +233,7 @@ protected function renderMissingMedia() {
         ],
       ],
       '#attached' => [
+        'library' => ['media/filter.caption'],
       ],
     ];
   }

This takes care of this:

If your theme doesn't override filter.caption.css and filter_caption is not enabled for a text format, the missing media embed's p tag is not text aligned centered.

This allows it work both in the wysiwyg and when viewing the page after saving.

oknate’s picture

Status: Needs work » Needs review

This still needs addressing:

#87.3: we are currently using the element type and data attribute for attaching styles to the element. Instead, we should be using CSS classes.

I started working on it. I think Lauriii wants these rules changed to use classes:

drupal-media[data-align=left],
drupal-media[data-align=right] {
  display: inline;
}
drupal-media[data-align=center] {
  display: flex;
}

This is a bit of a heavy lift, I believe, as the drupalmedia plugin.js has allowed attributes, the text format / editor form has validation of the allowed attributes on the drupal-media tag and CKEditor is manipulating the class tag to add "cke_widget_element".
I'm thinking we could change them to classes:
.drupal-media--align-left
.drupal-media--align-right
.drupal-media--align-center
but allowing those classes to persist on the drupal-media tag might require changing a lot of code.

After looking into this a bit more, I don't think we'll need to change the validation on the text format / editor form. Also, they don't need to persist as they are only in the wysiwyg and can be removed on downcasting.

So the plugin.js could add these classes to the widget, if the data properties are there. These rules don't persist out of the wysiwyg.

oknate’s picture

StatusFileSize
new2.39 KB
new55.85 KB

Addressing #87.3 (and #110). It would be good as a follow-up to this patch to add some simple test coverage that the css styles in this patch are applied.

oknate’s picture

StatusFileSize
new3.03 KB
new57.04 KB

This patch adds:

  • test coverage for #111 (Addressing #87.3).
  • some coding standard fixes.
phenaproxima’s picture

Status: Needs review » Needs work

Didn't find much to complain about. Virtually all of these are minor points.

  1. +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
    @@ -61,6 +61,34 @@
    +  let registeredLinkableWidgets = [];
    

    I think this can be a const, since it will not be reassigned. (In ES6, const variables are mutable, but not re-assignable.)

  2. +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
    @@ -61,6 +61,34 @@
    +   * @param {string} widgetName
    +   */
    +  function registerLinkableWidget(widgetName) {
    

    widgetName needs a description.

  3. +++ b/core/modules/media/css/filter.caption.css
    @@ -8,3 +8,11 @@
    +.missing-media {
    +  display: table;
    +}
    

    Why does this need display: table? That seems like an unusual choice :) So if this was the only way to get it to work, maybe this could benefit from a comment explaining the rationale.

  4. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +   * Integrates the drupalmedia widget with the drupallink plugin.
    +   *
    +   * Makes embedded media items linkable.
    

    We can combine these lines into one: "Makes embedded media items linkable by integrating with the drupallink plugin."

  5. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +      let dtd = CKEDITOR.dtd, tagName;
    

    dtd should be a const, since it won't be reassigned.

  6. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +      dtd['drupal-media'] = {'#': 1};
    

    This line could use a comment.

  7. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +          // This matches the behavior of the corresponding filter plugin.
    

    Can we say "...corresponding server-side text filter plugin", to make it clear we're talking about something running within Drupal?

  8. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +          // Allow ckeditor.drupalmedia.css to respond to changes (for example in alignment).
    

    This line is longer than 80 characters.

  9. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +            // Ensure that any changes made to the caption are persisted in the
    +            // widget's data-caption attribute.
    +            const widget = this;
    

    I'm not sure we need const widget = this, since arrow functions bind to the lexical this automatically. (Maybe try changing it locally and see if tests pass.)

  10. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +            this.captionEditableMutationObserver = new MutationObserver(() => {
    

    captionEditableMutationObserver is a bit of a long-winded name. How about just captionObserver?

  11. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +          if (dataToHash.attributes.hasOwnProperty('data-caption')) {
    +            delete dataToHash.attributes['data-caption'];
    +          }
    

    I'm not sure we need to do the hasOwnProperty() check. According to MDN, it's basically just a harmless no-op if we delete a key that doesn't exist; see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operat...

  12. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +          if (dataToHash.link && dataToHash.link.hasOwnProperty('href')) {
    +            delete dataToHash.link.href;
    +          }
    

    Same here. This can just be if (dataToHash.link) {...}.

  13. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,252 @@
    +          jQuery.get({
    +            url: Drupal.url('media/' + editor.config.drupal.format + '/preview'),
    +            data: {
    +              text: this.downcast().getOuterHtml(),
    +            },
    +            dataType: 'html',
    +            success: (previewHtml) => {
    +              this.element.setHtml(previewHtml);
    +              callback(this);
    +            },
    +          });
    

    I think @lauriii was on to something -- we should do some sort of error handling, perhaps, if the HTTP request fails. Even if it's something as simple as setting the element's HTML to Drupal.t('An error occurred while trying to preview the media.');

  14. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,109 @@
    + *   This is an internal part of the media embed filter text editor integration
    + *   and should only be used by media embed filter-related code in Drupal core.
    

    Let's rephrase this a bit, as it's a little confusing. How about "This is an internal part of the media system in Drupal core and may be subject to change in minor releases. This class should not be instantiated or extended by external code."

  15. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,85 @@
    +class DrupalMedia extends PluginBase implements CKEditorPluginInterface, CKEditorPluginContextualInterface, CKEditorPluginCssInterface {
    

    I think we should mark this class @internal with the same wording as the preview controller. Additionally, both CKEditorPluginContextualInterface and CKEditorPluginCssInterface extend CKEditorPluginInterface, so there's no need to implement that one explicitly.

  16. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,85 @@
    +    return drupal_get_path('module', 'media') . '/js/plugins/drupalmedia/plugin.js';
    

    Ideally, we should inject the extension.list.module service and use it to get extension paths.

  17. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,85 @@
    +    return $filters->has('media_embed') && $filters->get('media_embed')->status;
    

    Why aren't we using ::has() in MediaFilterController::formatUsersMediaEmbedFilter()?

  18. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -213,12 +213,28 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    +        '#uri' => file_url_transform_relative(file_create_url('core/modules/media/images/icons/no-thumbnail.png')),
    

    Looks like we're using drupal_get_path() in the DrupalMedia CKEditor plugin, but hard-coding the path to the Media module here. Can we make it consistent (ideally by injecting the extension.list.module service)?

  19. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,581 @@
    +    $this->assertSession()->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000);
    +    $this->assertSession()->elementNotExists('css', 'figure');
    

    Nit: It's a little more readable to set $assert_session = $this->assertSession(); at the top of the test method, then call methods on $assert_session throughout.

phenaproxima’s picture

Sent this to @lauriii and I don't think we have addressed his feedback in #76 yet.

phenaproxima’s picture

I demoed this to the UX team yesterday as well. @andrewmacpherson was present and voiced no objections to the markup generated inside the editor for a missing media item. Therefore, I'm removing the "needs accessibility review" tag.

phenaproxima’s picture

Crediting @andrewmacpherson for his input.

wim leers’s picture

  • #104: RE It should to be tied to the media embed filter, rather than the caption filter. +1
  • #109: Having granular asset libraries is good. I liked #106 better. It's fine to add it to stable as well.
  • #110: Ah, so that's how #81.3 had to be interpreted! I strongly disagree with this. This is not about your typical styling, this is specifically something that is a CKEditor Widget implementation detail. I'd even prefer hardcoding setStyle() calls in the CKEditor Widget JS and deleting the CSS file altogether than adding additional classes for nicer selectors. That'd make it more explicit that this is an implementation detail.
  • #113.13: 👎 This suggestion would result in a weird UX that we don't have anywhere else. See #87.5.
  • #113.14: 🤔 This is follows the pattern established in \Drupal\media\Controller\OEmbedIframeController. If we're changing it here, then let's also update that one.
  • #114++
oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new16.81 KB
new61.28 KB

This patch addresses feedback from #113. I'll address #118 separately.

#113.3:

In regards to this rule:

+.missing-media {
+  display: table;
+}

I'm not a FE dev. I pulled this from /core/modules/filter/css/filter.caption.css.

I tried changing it to other common display options and none allowed the element's width to shrink to fit its contents. I'll leave it until a FE dev comes up with something better.

This is how I was testing if you want to give it a look: Put a "sleep(500)" statement into CKEditorIntegrationTest::testLinkability() after the embed is rendered in the CKEditor, then press the "source" button and edit the uuid so it's invalid.

oknate’s picture

StatusFileSize
new40.13 KB

Here's a screenshot of #85.5. (#113.3)

error handing.

There's some disagreement about whether we should do this. If for some reason the embed (or missing entity indicator) fails to render, such as a fatal error in a preprocess function or a server becoming unresponsive, this will give feedback to the user. I see it as a minor change and a net positive as it will handle an edge case we haven't handled.

There's no test coverage for this. This is how I was testing if you want to give it a look:

  • Add throw new NotFoundHttpException(); at the beginning of MediaFilterController::preview()
  • Put a "sleep(500)" statement into CKEditorIntegrationTest::testLinkability() after the embed is rendered in the CKEditor.
oknate’s picture

StatusFileSize
new4.02 KB
new62.36 KB

Adding back separate css file for the .missing-media css per #118.2.

oknate’s picture

For #81.3, which was implemented in #110 and disagreed with in #118.

In #118 Wim writes:

Ah, so that's how #81.3 had to be interpreted! I strongly disagree with this. This is not about your typical styling, this is specifically something that is a CKEditor Widget implementation detail.

I can see your point. They are single use rules, not to be re-used, so there's no point adding reusable classes, and it's somewhat clearer to have it in a css file, but it does give the impression that these are rules. I think more comments could allow us to remove the classes and go back to the original. I think before I change or revert this, Wim and Lauriii should come to agreement as to what to do here.

I'd even prefer hardcoding setStyle() calls in the CKEditor Widget JS and deleting the CSS file altogether than adding additional classes for nicer selectors. That'd make it more explicit that this is an implementation detail.

I like it in a css file. It's easier to see the rules laid out and separated from the data manipulation. So I'd rather not use setStyle calls(). I'd rather either keep it as is now, or revert it over that.

lauriii’s picture

#118 related to #113.13: The problem I was trying to point out is that without explicitly handling the error scenario, there's no visual hint what so ever that something has gone wrong if the preview fails to load. Without explicitly handling errors, the UI would have a little bit of empty space instead of a preview whenever an error occurs. It's hard to predict how users would react to that, but it's hard to imagine that they would at least at first associate that to a system-level error. Instead, they might expect it to be something wrong with their content, and they would try to take action to solve that. This issue is related to this problem: #2987444: Ajax errors are not communicated through the UI.

ckrina’s picture

I totally agree with @lauriii with the need of giving more information/feedback to the user to improve the usability of the content authoring experience.

So for #82, this icon is not enough feedback. First, because if it will show up when the referenced media has been deleted, it means the source is missing. And actually this icon usually stands for “forbidden”. Maybe we could try to use a “broken link” or similar representation. Apart from the icon itself, some more info should be given, specially because we have it and we can point out the user to solve the issue.
So I’d add an explanatory text similar to “The referenced media source has been deleted.”

#81.5 (and answering Wim’s #87.5)

🤔 Yes … except pretty much nothing in Drupal core does this, so I chose to keep it simple and similar. The indication will be that it looks broken.

That is exactly one of the most frustrating experiences when editing content: ghost errors that you don’t know how to solve. So we should try to clearly communicate:
a. That something went wrong.
b. How to solve the current situation.
c. Warnings about what can go wrong so they have a chance to decide before doing anything.
d. Ideally, what has gone wrong. Probably difficult to know here.

So I’d suggest, as mentioned in earlier comments, that we show a message as implemented in #120 adding some instructions:
“An error occurred while trying to preview(load?) the media source. Save you content to avoid data loss and reload this page.”

oknate’s picture

StatusFileSize
new5.22 KB
new61.42 KB
new2.08 KB
new61.91 KB

Addressing #118.3, I present two options, the third being no change.

  • Option 1, use set style as proposed in #118.3.
  • Option 2, revert changes made to address #81.3.
  • Option 3: leave as is.

My concern with setStyle is that, if someone needs to override these settings for some reason, it is much harder to do that. My concern with reverting the changes is, Lauriii feedback that we should use classes instead of data attributes to attach css will not have been addressed.

Therefore my opinion is the best option here is option 3, followed by option 2.

The patch numbers are off due to another comment coming in as I was adding mine.

oknate’s picture

Updated patch for reverting BEM classes change, see #125. I had forgotten to remove the functional js test.

oknate’s picture

In regards to this in #124:

I’d add an explanatory text similar to “The referenced media source has been deleted.”

If the media to be embedded cannot be loaded based on the uuid, we can't tell if it's been deleted, or has never been added. Therefore, we have to use a more generic message "Missing media". I think the reason it's missing, while valuable, cannot be ascertained with confidence.

oknate’s picture

Addressing #118.5, updating @internal comment in OEmbedIframeController.

We still need to address #76.

lauriii’s picture

This is not about your typical styling, this is specifically something that is a CKEditor Widget implementation detail.

I agree with this. However, the reason I would prefer using CSS selectors for styling is that if the default solution doesn't work well with someone's custom code, they would be still able to override the styles with a reasonable amount of effort needed. #125.1 and 3 both seem like valid solutions to me. Do we have a prediction on the likely hood of someone having to override these styles?

If the media to be embedded cannot be loaded based on the uuid, we can't tell if it's been deleted, or has never been added. Therefore, we have to use a more generic message "Missing media". I think the reason it's missing, while valuable, cannot be ascertained with confidence.

Maybe we should then say something like "The referenced media source is missing. To solve this, the media has to be re-embedded from the media library."

oknate’s picture

Here's an attempt to address #76.
bartik theme, centered with caption
It looks good in the wysiwyg with Bartik. But I noticed after posting this, it is off after saving when using align-center.

This change was a personal thing. It doesn't make a difference for the test:

-      'uri' => $this->getTestFiles('image')[0]->uri,
+      'uri' => $this->getTestFiles('image', 39325)[0]->uri,

I was getting annoyed by how small the image was in the test.

Update, I think it did make a difference for the test. The image is now "image-1.png" instead of "image-test.png"! I'll revert that change.

oknate’s picture

StatusFileSize
new5.97 KB
new4.49 KB
new65.31 KB

Continuing to address #76:

  1. Fixing the center alignment when saved with a caption (as opposed to with a caption in the ckeditor)
  2. Renaming the wrapper element to reuse an existing class. Since it's ckeditor specific, I think drupal-media-wrapper is a bit confusing, since the wrapper element doesn't appear after you have saved the content.

The last submitted patch, 130: 2994696-130.patch, failed testing. View results

oknate’s picture

StatusFileSize
new748 bytes
new65.3 KB

Going back to original image in the test. It did make a difference.

Status: Needs review » Needs work

The last submitted patch, 133: 2994696-133.patch, failed testing. View results

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new3.82 KB
new9.06 KB
new68.82 KB

Fixing assertions around asset libraries in the Kernel test MediaEmbedFilterTest. Including an interdiff to before I started changing css (#128).

phenaproxima’s picture

Status: Needs review » Needs work

Boy, there really was not much to complain about in this patch. I think I only have two overriding concerns which block RTBC.

  1. +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js
    @@ -330,5 +357,6 @@
    +    registerLinkableWidget: registerLinkableWidget
    

    Nit: This should have a trailing comma (which is allowed by ES6).

  2. +++ b/core/modules/media/css/filter.caption.css
    @@ -8,3 +8,7 @@
    +.cke_widget_drupalmedia figure.caption-drupal-media {
    +  margin: 1em 1em 0 1em;
    +}
    

    I'm not sure we can rely on .cke_widget_* classes, since AFAIK they're generated by CKEditor and are internal to it.

    Overall, the organization of the CSS in this patch feels confusing to me. I think we might want to refactor some of it and move it into fewer style sheets. I will try to secure a dedicated front-end developer's time to handle that.

  3. +++ b/core/modules/media/css/filter.media_embed.css
    @@ -0,0 +1,38 @@
    +/* Use display table so width is only as wide as the contents */
    

    Nit: This comment should end with a period. :)

  4. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,253 @@
    +  /**
    +   * Gets the focused widget, if of the type specific for this plugin.
    +   *
    +   * @param {CKEDITOR.editor} editor
    +   *   A CKEditor instance.
    +   *
    +   * @return {?CKEDITOR.plugins.widget}
    +   *   The focused drupalmedia widget instance, or null.
    +   */
    +  function getFocusedWidget(editor) {
    +    const widget = editor.widgets.focused;
    +
    +    if (widget && widget.name === 'drupalmedia') {
    +      return widget;
    +    }
    +    return null;
    +  }
    

    Isn't this logic basically identical to getFocusedLinkableWidget() in the drupallink plugin? If so, can't we remove this function, call drupallink's registerLinkableWidget('drupalmedia'), and leave it at that?

  5. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,253 @@
    +    editor.getCommand('drupalunlink').on('exec', evt => {
    +      const widget = getFocusedWidget(editor);
    +
    +      if (!widget) {
    +        return;
    +      }
    +
    +      widget.setData('link', null);
    +
    +      this.refresh(editor, editor.elementPath());
    +
    +      evt.cancel();
    +    });
    +
    +    editor.getCommand('drupalunlink').on('refresh', evt => {
    +      const widget = getFocusedWidget(editor);
    +
    +      if (!widget) {
    +        return;
    +      }
    +
    +      this.setState(widget.data.link ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
    +
    +      evt.cancel();
    +    });
    

    I think we should add comments explaining what both of these event handlers are for.

  6. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,253 @@
    +            error: () => {
    +              this.element.setHtml(Drupal.t('An error occurred while trying to preview the media.'));
    +              callback(this);
    +            },
    

    I don't think this should invoke the callback. The callback is probably expecting the preview to have loaded successfully (per the documentation of _loadPreview()), which means that it will be thrown for a loop if, in fact, an error occurred.

  7. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,581 @@
    +    $this->assertSession()->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000);
    +    $this->assertSession()->elementNotExists('css', 'figure');
    

    Nit: For readability, let's change all calls of $this->assertSession() to a single $assert_session = $this->assertSession() at the top of each test method, then call methods on $assert_session in the rest of the test.

  8. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,581 @@
    +    $this->assertSession()->waitForElementVisible('css', 'img[src*="image-test.png"]');
    

    This, and all similar waitFor*() calls throughout the entire test, need to be wrapped by $this->assertNotEmpty(). (The reason being that they return NULL if they fail, but don't actually cause any sort of assertion error make the test fail.)

wim leers’s picture

#123: agreed that it's related to #2987444!

#124: Thanks for chiming in here! 🙏😊

  • Which parts of Lauri's feedback to you agree with? The textual information a missing media
  • RE #82: +1 for finding a better image! Note that this does NOT always mean that the media has been deleted. The reference may simply be wrong, or perhaps the referenced media has not yet been synced. Usually it will indeed have been deleted. I originally suggested exactly the same text as you just did 😀. But it was then pointed out to me that this is not always accurate. That's why the word "missing" was carefully chosen. (I see @oknate already explained this in #127 🙏)
  • There is no data loss risk! Fortunately 😓 The only thing that can fail is the preview itself. But I never opposed handling this better, I only said I matched the behavior of other things in Drupal core. If we're going to handle this (yay!), it needs to be better than #120's screenshot though, because there it looks like editable text that will be impossible to discern from text before and after it. At the very least this will need some styling to make it stand out, and ideally it'd convey that if all went well it'd have shown embedded media.

#129:

  • It's extremely unlikely they'll need to override these styles. Because they're specifically targeting the custom HTML element's contents, which is guaranteed to always have the same structure.
  • That suggested string seems reasonable — the only scenario where it is potentially inaccurate is where the referenced media is yet to be synced.

#130: This seems to be making many more changes to CSS than what's necessary to address the margin-related concerns in #76?

#136:

  1. We can rely on it. But this style rule and selector do not belong in the CSS targeted at the filter; this would need to live in the CSS specific to the CKEditor integration.
  1. No, we can't call drupallink's function because it's not exposed as a public API. But … you're totally on to something! Instead of having drupalimage and drupalmedia react to exec and refresh events on the drupalunlink command, we can just have the drupallink plugin do that generically now, thanks to registerLinkableWidget()! Well-spotted sir 🦅👁
  2. See previous point: these would disappear!
oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new24.29 KB
new68.69 KB
new153.62 KB

Updating based on feedback in #136:

  1. 👍✅ switched back to using new css selector .drupal-media-wrapper
  2. Wim writes:

    Instead of having drupalimage and drupalmedia react to exec and refresh events on the drupalunlink command, we can just have the drupallink plugin do that generically now, thanks to registerLinkableWidget()!

    I need a little more guidance on this. 🙏

  3. I still need to do this, I started on it then got distracted by the javascript bug I was mentioning today in the #media channel. It turns out the arrow functions shouldn't be arrow functions due to "this" needing to stay within the scope of the event handlers.
    error due to arrow functions
    If implement Wim suggestion in 4, we won't need to add comments here, as they'll be refactored away.
  4. ✅callback removed when displaying error message
  5. ✅updated tests to use $assert_session variable
  6. ✅wrapped waitFor*() calls

Also, @phenaproxima and I talked in the #media channel today, about the scope of the css:

we have several scopes:
media embed outside editor,
media embed outside editor with caption
media embed inside editor,
media embed insde editor with caption

During this conversation I realized that some of the styles that were in filter.media_embed.css could go into ckeditor.drupalmedia.css, since the later only affects the ckeditor, and the styles in question only needed to affect the ckeditor.

oknate’s picture

I added a @todo in #138:

           // Allow ckeditor.drupalmedia.css to respond to alignment changes.
+          // @todo: is this still necessary?  Now that css selectors have
+          // been updated to not use data attributes?
           this.element.setAttributes(this.data.attributes);

I haven't removed the line because it seems harmless, and it seems better to leave the attributes in sync. Maybe @wim could remember if this is only needed for the css, if so, we can drop this line.

oknate’s picture

Wim writes:

#130: This seems to be making many more changes to CSS than what's necessary to address the margin-related concerns in #76?

You're probably talking about this:

+.drupal-media-wrapper {
+  display: flex;
+  justify-content: flex-start;
+}
+
+.drupal-media-wrapper.drupal-media--align-left {
+  justify-content: flex-start;
+}
+
+.drupal-media-wrapper.drupal-media--align-right {
+  justify-content: flex-end;
+}
+
+.drupal-media-wrapper.drupal-media--align-center {
+  justify-content: center;
+}

The problem is that when the item was centered, it was using left-margin: auto and right-margin: auto to center the item. This meant that the margin and the alignment were coupled. In order to fix the margin showing inside the outline, we needed to switch to a new way of aligning the element, in order to get this effect:

centered
instead of the blue outline here having a lot of white margin around the centered item:
 auto

oknate’s picture

StatusFileSize
new703 bytes
new68.69 KB

Small coding style fix.

oknate’s picture

StatusFileSize
new9.09 KB
new71.54 KB

Here I'm trying to implement #138.4. It seems to work fine for drupalmedia for 'exec' function. I'm not sure how to test the 'refresh' function. I also need to test drupalimage changes.

wim leers’s picture

#138.2: -1 on this .drupal-media-wrapper, see my explanation in #137.

#138.4 + #142: That's exactly what I meant! 🥳 The refresh event is invoked whenever the cursor position changes, because that requires button states to be refreshed. You will notice that if you move the cursor onto a linked drupalimage or drupalmedia widget that the "unlink" button becomes triggerable instead of disabled. That's what this does :)

#139: Yes, that was added only for the styling: that's what the comment says. We did that in #3064287: [regression] Follow-up for #3037316: alignment doesn't display until source button pushed.

#140: Indeed, that's why. I'm okay with this if @lauriii signs off on it. I've never seen justify-content before and https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content indicates that browser support is not complete. But perhaps this subset does have sufficiently wide support. Thanks for the super clear explanation by the way! 👏


Great work here, @oknate! Just since last night, I think most feedback has been addressed. My primary remaining concern is #138.2 and the "AJAX error" UX as it currently works (see the screenshot in #120).

Next steps AFAICT:

  1. @effulgentsia to address his #70.1 concern has been addressed.
  2. @lauriii to agree or disagree with my #138.2 concern. I'm not vetoing. But I think we should follow CKEditor's example there, and rely on .cke_* classes instead of adding our own, because this functionality is CKEditor-powered, so it's silly and even misleading to add our own classes. If the functionality is CKEditor-coupled, the CSS should not make it seem as if it's not.
  3. @lauriii to confirm that his #76 concern (selection/focus outlines) has been addressed.
  4. @lauriii (and perhaps @ckrina) to confirm that the #82/#88 (missing media UX) concern has been addressed.
  5. Ah, AJAX errors still need to be addressed. #120 is not adequate. See my feedback in #137.
oknate’s picture

Addressing #137:

At the very least this will need some styling to make it stand out

classy error message

This looks pretty good, but it seems weird to include a theme file in the filter's css. Any suggestions on how to get this styling in the ckeditor without including the classy theme file in the DrupalMedia CKEditor's ::getCssFiles() method?

Also, restoring function getFocusedWidget to drupalimage plugin, due to error message triggered by this code:

  // Expose an API for other plugins to interact with drupalimage widgets.
  CKEDITOR.plugins.drupalimage = {
    getFocusedWidget,
  };

This also made me realize that it could break BC to remove that function. We don't offer the same public api for drupalmedia. Perhaps we should? I'm not sure what the rationale for drupalimage was.

oknate’s picture

Here's an effort to improve the visual display of the missing media indicator. I took inspiration from how the icons look in the media library. I also tried out a new icon (using photoshop and editing one of the existing media icons). But decided not to add it to the patch, because there's a separate issue for that: #2944631: Improve media icon that represents "No thumbnail available", and I don't want to derail the focus here.

With current icon:
missing media indicator styling with current icon

With new icon (not included in patch):
missing media indicator styling with new icon
See #2944631: Improve media icon that represents "No thumbnail available"

Also, I was looking into it a bit and I think "justify-content: flex-end" is unsupported in ie11, and Drupal 8 officially supports ie11. So we probably need to replace that way of doing right-align, or add an ie11 work-around. I will leave that to those who make things work in ie11. I don't have access to ie11 currently.

oknate’s picture

StatusFileSize
new74.39 KB
new4.53 KB
new38.66 KB

Adding test coverage for drupalunlink button integration.

oknate’s picture

StatusFileSize
new75.66 KB
new6.48 KB

Since we're refactoring the drupalimage plugin's js, I felt it was worthwhile to add some test coverage for the unlink functionality on that widget too. Also, I don't see any test coverage for it in core.

As I was adding this, I noticed that our test function testLinkabilityWhenDrupalImageIsAbsent was trying to remove the DrupalImage button from the editor, but we didn't have a DrupalImage button there! This means running testLinkability() running twice wasn't testing two different scenarios, and that the code block to remove the DrupalImage button was on a snipe hunt. I refactored this to use a data provider and I added the DrupalImage button by default to the test. It now runs the testLinkability test twice, once with DrupalImage button enabled and once without. It also, tests the enabling of the unlink when focused on the DrupalImage widget, and in that context clicking on the DrupalUnlink button will remove the link from the DrupalImage element.

I also removed the wait within assertEditorButtonDisabled() and assertEditorButtonEnabled(). The random failures I thought I was experiencing there are not reproducible today.

lauriii’s picture

Issue summary: View changes
Status: Needs review » Needs work
StatusFileSize
new8.02 KB

#143.2 I'm not a CKEditor expert so I'm likely missing something. I'm having a hard time understanding why you think that it's silly to provide our own CSS to the element. To me, it seems reasonable to customize the CSS of elements added by us based on our requirements 🤷‍♂️ I'm not outright against using inline styles but I don't understand why it would be worthwhile taking a risk that someone would be having a hard time overriding that CSS.

#143.3 This looks much better already. I haven't manually tested all variations but I noticed that the styles are broken when data-align is provided:

#143.4
That seems like a rare edge case (feel free to correct me if I'm wrong). Isn't that something that would only occur while the content sync is still in progress? Maybe we should get confirmation from UX or product but I think it might be acceptable for this text to not support that rare edge case. The reason I think this way is that we can help a big group of users by using a more specific message. If we used the generic message, it means that everyone suffers.

effulgentsia’s picture

@effulgentsia to address his #70.1 concern has been addressed.

Yep, it has, thanks!

#70.2 has not been yet. The response to that was:

This also was raised in #15.2. My answer then: This is just following an existing pattern in core, see \Drupal\editor\EditorController::filterXss().

However, that is incorrect. EditorController::filterXss() calls $request->request->get(), not $request->get(). So EditorController is opinionated on the data coming in via the POST body. For MediaFilterController, we need to decide whether we're expecting a GET or a POST, and change to either $request->query->get() or $request->request->get() accordingly. Or if there's a reason we want to support both and let the JS choose which one to use, then we need to document that reason.

I think it's ok for MediaFilterController to choose GET even if EditorController is choosing POST, because for EditorController, the input can be of arbitrary length, so can exceed the length limit of GET parameters. For MediaFilterController, the JS is currently choosing GET, which I think is the semantically correct choice, and since the expected string is just the <drupal-media> element, I think we can assume that we're safely within GET length limits. Unless we're concerned about attributes like alt and data-caption pushing us beyond GET length limits?

phenaproxima’s picture

I've become very confused by the last chunk of comments on this patch. It feels like the scope is wiggling a bit out of control and it's no longer clear to me where the path to committing this issue lies.

So I got on the horn with @oknate and we had a quick "state of the union" call to itemize everything we need. This is where we landed:

  • Styling for the embedded media: I'd rather do this this @lauriii's way, using BEM classes and stylesheets rather than inline styles. I can totally imagine people wanting to tweak or otherwise override those styles, and setting them inline with CKEditor's API seems needlessly complex and feels like it violates separation of concerns. BEM classes via stylesheets also is in line with an emerging pattern in core.
  • @oknate has rearranged the CSS changes/additions (addressing my concern in #136.2), and I need to review that.
  • AJAX error handling straight-up needs a test. We can do this by overriding the preview controller in a test module, then having it use a state value to determine whether it should return a successful response, or die with some sort of error.
  • The styling for the AJAX error is currently accomplished by including Classy's styles for the generic messages area. @oknate and I like the styling, but we agree that this feels like a bad code smell. It's not clear how else we could apply those styles except by copy-and-pasting some of Classy's styles into the stylesheet used within the editor. I'd like to consult @ckrina to decide if reusing the error message styles is appropriate, and if it is, I'd like to get input from @lauriii on how we might bring those styles in.
  • The text of the error message is a simple string, and strings can be changed all the way until RC. Let's not block this issue on that. Instead, let's go ahead with the generic text we have now, and open a follow-up to iterate on it later.
  • I know I suggested some cool refactoring in #136.4, but this is turning out to be causing scope creep. The drupallink and drupalimage plugins do not seem to have sufficient test coverage for us to be refactoring them yet, and changing drupalimage in this issue is out of scope. We should copy-and-paste the event handlers into drupalmedia and open a follow-up (with a @todo comment in the code) to add test coverage and unify the linkability stuff later.
  • Regarding #149:

    I think it's ok for MediaFilterController to choose GET even if EditorController is choosing POST

    Agreed. Let's use GET for now. It is trivial for us to allow POST as well later on if that proves to be necessary.

oknate’s picture

StatusFileSize
new6 KB
new80.72 KB

#150.3: Adding test for AJAX error handling.

One small thing after I posted this:

+   * Constructs an MediaFilterController instance.

should be

+   * Constructs a TestMediaFilterController instance.
oknate’s picture

StatusFileSize
new8.21 KB
new78.74 KB

#150.6: Reverting refactor suggested in #136.4. I have opened a follow-up issue for the refactor: #3069525: Refactor to remove drupalunlink event listeners for registered linked widgets.

ckrina’s picture

Waw, it’s almost impossible to follow you! You’ve been busy! 👏

1. #143.4 What about just saying something actionable like: "The referenced media source is missing and needs to be re-embedded from the media library." or "The referenced media source is missing and needs to be re-embedded.” I still think "Missing media" is not enough, sorry.
2.

I'd like to consult @ckrina to decide if reusing the error message styles is appropriate, and if it is, I'd like to get input from @lauriii on how we might bring those styles in.

#150. It would be great to have feedback that looks different from the regular text, so using messages styling would be ideal… but right now they are not prepared to work with icons (see message 4).
3.

The styling for the AJAX error is currently accomplished by including Classy's styles

#150. What happens if the admin theme has different styles from the Classy’s ones? Can we rely on admin themes following the needed pattern?
4. #150 Also about this message, as I said it'd be great to have a different styling for error messages that makes them stand out from the content. But it should be the same for all of them. As an editor I won't know if one is loaded via AJAX or not. They are both messages, so they should look the same.

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new82.1 KB
new82.71 KB
new5.35 KB

Addressing #148.2: Fixing

styles are broken when data-align is provided

It turns out there is a bug where attributes from drupal-media element are carried over to the embed. This process was removing the 'missing-media' class. I put an exception for 'class', because it seems destructive to overwrite the class of the embed. Another option would be to merge the classes. Since filter_align adds a class to the drupal-media element, it was getting carried over by overwriting the attribute. I put an exception for the 'class' attribute, and added test coverage that the missing media indicator has the 'missing-media' class.

The last submitted patch, 154: 2994696-152--FAIL.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 154: 2994696-152.patch, failed testing. View results

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new83.02 KB
new2.92 KB
new83.35 KB
new162.18 KB
new190.74 KB

Fixing the test failure, I noticed that there's test coverage for the align classes being added to the embedded media, which lead me to look into it further and sure enough. If you don't pass classes such as 'align-right' on to the embedded media, they won't align right outside the WYSIWYG after saving. So this patch restores those, and merges the classes on the embedded element with those from drupal-entity element.

I also noticed that the flex-box model doesn't work for displaying text to the right of an image (See screenshot). We're only using this in the CKEditor, but I think we'll need to go back to closer to what we had in #25 for the alignment in the CKEditor, in order to get the text on the left. I changed the styles to use flexbox to try to fix the outlines showing whitespace (margin) inside. So now we'll need to try to revert the flexbox stuff and fix the outlines another way.

text to the left, image floating right

Here's a quick test you can use to manually check the css styles, just throw this into CKEditorIntegrationTest.

  /**
   * Tests css.
   */
  public function testCss()
  {

    $this->host->body->value = '<drupal-media data-align="left" data-entity-type="media" data-entity-uuid="abc123-5e419ae6-a1b2-4a21-9021-83007ee8a9c0"></drupal-media><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec faucibus efficitur nibh, at semper risus consectetur ut. Aliquam ultricies, tortor vitae gravida congue, eros mauris suscipit eros, porttitor rhoncus nunc urna in dui. Vivamus malesuada pretium lacus at cursus. Nullam commodo dignissim elit non vulputate. Fusce euismod ipsum vitae purus accumsan suscipit. Aenean fringilla rutrum purus, vitae tempus massa euismod mollis. Etiam ullamcorper urna sit amet erat mollis sodales. Etiam non augue quis purus rhoncus dictum. Phasellus eu blandit nunc. Nunc magna nunc, euismod sit amet nulla non, placerat facilisis risus. Proin posuere mollis risus. Phasellus tempus sem in lectus rutrum, eu dapibus mi dignissim.</p><p>The pirate is irate.</p><p>&nbsp;</p>';
    $this->host->save();

    $this->drupalGet($this->host->toUrl('edit-form'));
    $this->waitForEditor();
    $this->assignNameToCkeditorIframe();
    $this->getSession()->switchToIFrame('ckeditor');

    sleep(5000);

  }

An unrelated change I threw in: I copied classes that were missing to the the stable theme:

@@ -6,8 +6,11 @@
 /* Use display table so width is only as wide as the contents. */
 .missing-media {
   display: table;
+  background-color: #ebebeb;
+  padding: 20px;
+  text-align: center;
 }
 
 .missing-media__message {
-  text-align: center;
+  font-weight: bold;
 }

The last submitted patch, 157: 2994696-157--FAIL.patch, failed testing. View results

oknate’s picture

StatusFileSize
new1.8 KB
new83.67 KB

This patch fixes the bug found in #157 with the css:

flex-box model doesn't work for displaying text to the right of an image

Also, it removes the flexbox css:
justify-content: flex-end;
and
justify-content: flex-start;

This should help make the styles more ie11 compatible.

oknate’s picture

Addressing #153.1:

What about just saying something actionable?

This required some additional styling too. I had to put a width constraint on missing media as it was shrinking to fit the image before.

missing media text update

oknate’s picture

oknate’s picture

oknate’s picture

oknate’s picture

oknate’s picture

StatusFileSize
new180.03 KB
new1.74 KB
new83.98 KB

Removing the label from the missing media, per request from @phenaproxima:
removed label

I don't know why drupal.org just said I commented three times or four times. Weird.

phenaproxima’s picture

Status: Needs review » Needs work

I just had a very pleasant call with @lauriii and @ckrina to figure out how we want to present the "I/O error" -- i.e., a generic AJAX error when the media preview fails to render for whatever reason.

The conclusion we reached is that, although it would be nice to be able to use the admin theme's error styling, there is no real way for us to do this reliably with current core mechanisms. So we will open a follow-up for that.

In the meantime, though, we need three things:

  1. The I/O error should be styled just like the "missing media" error. Same Ghostbusters logo, big grey box, all that jazz.
  2. The text of the error should be: "An error occurred while trying to preview the media. Please save your work and reload this page."
  3. A follow-up issue to investigate building a mechanism for reusing admin-facing error styling in any context.
oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new4.87 KB
new83.54 KB
new180.92 KB

Addressing #166: This patch simplifies the missing media indicator by removing the image and replacing it with a background image. This means the message doesn't need to be hidden from screen readers (as it did when it was duplicating the alt and title text).

Then, we're updating the i.o. error to use the missing media indicator's markup, which removes the issue where we were using the classy theme and adding it with the plugin.

The new i.o. error with ghostbusters logo

phenaproxima’s picture

Status: Needs review » Needs work

Oh man, this is looking so very, very good. First-class work from everyone all around!

  1. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,255 @@
    +              let error = Drupal.t('An error occurred while trying to preview the media.');
    +              let html = '<div class="missing-media">' + error + '</div>';
    

    Both of these should be const, since neither is reassigned. :) Also, shouldn't error include the phrasing "Please save your work and reload this page"?

  2. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,110 @@
    +   * Applies all of the given text format's filters, not just the `entity_embed`
    +   * filter, because for example `filter_align` and `filter_caption` may apply
    +   * to it as well.
    

    The filter is called media_embed, not entity_embed. :)

  3. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,110 @@
    +    $text = $request->get('text');
    +    if ($text == '') {
    +      throw new NotFoundHttpException();
    +    }
    

    As per @effulgentsia's feedback in #149 and mine in #150, let's use $request->query->get(), and update the doc comment to match ("Throws an exception if 'text' parameter is not found in the query string").

  4. +++ b/core/modules/media/src/Controller/OEmbedIframeController.php
    @@ -29,8 +29,9 @@
      * @internal
    - *   This is an internal part of the oEmbed system and should only be used by
    - *   oEmbed-related code in Drupal core.
    + *   This is an internal part of the media system in Drupal core and may be
    + *   subject to change in minor releases. This class should not be
    + *   instantiated or extended by external code.
    

    This is out of scope for this patch, but I don't especially object to it. So, whatevs :)

  5. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,140 @@
    +  /**
    +   * The theme extension list.
    +   *
    +   * @var \Drupal\Core\Extension\ThemeExtensionList
    +   */
    +  protected $themeExtensionList;
    

    This is no longer needed, so we can remove it :)

  6. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -73,6 +74,13 @@ class MediaEmbed extends FilterBase implements ContainerFactoryPluginInterface,
    +  /**
    +   * The module extension list.
    +   *
    +   * @var \Drupal\Core\Extension\ModuleExtensionList
    +   */
    +  protected $moduleExtensionList;
    

    Similarly, we no longer need this either :)

  7. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -213,12 +226,15 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    +      '#attributes' => [
    +        'class' => ['missing-media'],
    +      ],
    

    I think, for flexibility, we should alter the CSS classes we use. Both types of errors (missing media and I/O) should get a drupal-media-error class, or something similarly named. Then we should also have something like drupal-media-error--missing-source for missing media, and drupal-media-error--preview-error for I/O errors.

  8. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -270,7 +286,16 @@ public function process($text, $langcode) {
    +        if ($attribute->nodeName == 'class') {
    

    The contents of this if statement could use a comment explaining why we are merging all the classes together.

  9. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -270,7 +286,16 @@ public function process($text, $langcode) {
    +          if (empty($build['#attributes']['class'])) {
    +            $build['#attributes']['class'] = [];
    +          }
    

    This should happen outside the foreach() loop.

  10. +++ b/core/modules/media/tests/modules/media_test_ckeditor/src/Controller/TestMediaFilterController.php
    @@ -0,0 +1,58 @@
    +  /**
    +   * The Key/Value Store to use for state.
    +   *
    +   * @var \Drupal\Core\State\StateInterface
    +   */
    +  protected $state;
    +
    +  /**
    +   * Constructs a TestMediaFilterController instance.
    +   *
    +   * @param \Drupal\Core\Render\RendererInterface $renderer
    +   *   The renderer service.
    +   * @param \Drupal\Core\State\StateInterface $state
    +   *   The state keyvalue store.
    +   */
    +  public function __construct(RendererInterface $renderer, StateInterface $state) {
    +    $this->renderer = $renderer;
    +    $this->state = $state;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function create(ContainerInterface $container) {
    +    return new static(
    +      $container->get('renderer'),
    +      $container->get('state')
    +    );
    +  }
    

    It doesn't look like $this->state is used for anything. Also, since this is test code, we don't need to bother with proper dependency injection in this class. :) So let's just remove all this.

  11. +++ b/core/modules/media/tests/modules/media_test_ckeditor/src/Routing/RouteSubscriber.php
    @@ -0,0 +1,25 @@
    +      $route->setDefaults([
    +        '_controller' => '\Drupal\media_test_ckeditor\Controller\TestMediaFilterController::preview',
    +      ]);
    

    Pro-tip: it's preferable to use $route->setDefault('_controller', ...) here, so that we don't override any other relevant defaults that may be present.

  12. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +  /**
    +   * The state service.
    +   *
    +   * @var \Drupal\Core\State\StateInterface
    +   */
    +  protected $state;
    

    We should not do this in tests, lest the container change during the course of it (this happens a lot). Instead, just call $this->container->get('state') in the test as needed, rather than keeping a persistent property around.

  13. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +    $this->assertEquals('Caught in a landslide! No escape from reality!', $drupal_media->getAttribute('data-caption'));
    

    Generally we should use assertSame() rather than assertEquals().

  14. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +    $assert_session->assertWaitOnAjaxRequest();
    

    assertWaitOnAjaxRequest() is dangerously unreliable. Can we instead wait for a concrete condition, like some particular text appearing, or an element being visible or invisible?

  15. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +  public function testLinkability($drupalimage_enabled) {
    +
    +    if (!$drupalimage_enabled) {
    

    Nit: Extra blank line here that shouldn't be! :)

  16. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +      $editor = Editor::load('test_format');
    +      $editor->setSettings([
    +        'toolbar' => [
    +          'rows' => [
    +          [
    +            [
    +              'name' => 'All the things',
    +              'items' => [
    +                'Source',
    +                'Bold',
    +                'Italic',
    +                'DrupalLink',
    +                'DrupalUnlink',
    +              ],
    +            ],
    +          ],
    +          ],
    +        ],
    +      ]);
    +      $editor->save();
    

    Nit: All of these calls can be chained. Editor::load()->setSettings()->save().

  17. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +    $form->findField('attributes[href]')
    +      ->setValue('https://www.drupal.org');
    

    If findField() returns NULL, we'll call setValue() on NULL and this test will die with a fatal error. This should be $assert_session->fieldExists()->setValue() instead.

  18. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,746 @@
    +    $assert_session->assertWaitOnAjaxRequest();
    

    As I mentioned before, it's best for us to NOT use this method if we can avoid it. It's more reliable to simply assert the expected condition.

  19. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -258,26 +267,35 @@ public function providerOverridesAltAndTitle() {
    +  public function testMissingEntityIndicator($filter_ids, $uuid, $additional_attributes) {
    

    $additional_attributes should be type hinted as an array, and default to [].

  20. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -258,26 +267,35 @@ public function providerOverridesAltAndTitle() {
    -    $this->applyFilter($content);
    +    $result = $this->processText($content, 'en', $filter_ids);
    +    $this->setRawContent($result->getProcessedText());
    

    Why did this change?

  21. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -258,26 +267,35 @@ public function providerOverridesAltAndTitle() {
    +    $this->assertCount(1, $this->cssSelect('div.missing-media'));
    

    I think this could probably be $this->assertSession()->elementsCount().

  22. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -258,26 +267,35 @@ public function providerOverridesAltAndTitle() {
    +    if (in_array('filter_align', $filter_ids) && !empty($additional_attributes['data-align'])) {
    

    We should always pass TRUE as the third argument to in_array().

  23. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -258,26 +267,35 @@ public function providerOverridesAltAndTitle() {
    +      $missing_media = $this->cssSelect('div.missing-media')[0];
    +      $this->assertHasAttributes($missing_media, [
    +        'class' => 'missing-media align-' . $additional_attributes['data-align'],
    +      ]);
    

    This can be greatly simplified, I think:

    $assert_session->elementAttributeContains('css', '.missing-media', 'class', 'align-' . $additional_attributes['data-align']);

andrewmacpherson’s picture

Re: #115 -

I demoed this to the UX team yesterday as well. @andrewmacpherson was present and voiced no objections to the markup generated inside the editor for a missing media item. Therefore, I'm removing the "needs accessibility review" tag.

Removing the tag confused me, so I lost this issue. I can find issues which have the "needs accessibility review" tag, but I won't find issues where that tag has been removed by someone else.

There is indeed an accessibility issue with the markup - aria-hidden and title attributes are being used inappropriately here:

<div class="missing-media">
  <img src="..." alt="Missing media." title="Missing media.">
  <p aria-hidden="true" class="missing-media__message">Missing media.</p>
</div>

I think the aria-hidden="true" is being used to avoid repeating the phrase "Missing media" to screen reader users. That's fine, but this markup is over-optimizing for a blind person controlling their screen reader with a keyboard, at the expense of other scenarios.

The aria-hidden="true" attribute should be used very rarely. In general it should never be applied to text which is actually visible on the screen. Screen readers can be configured to read whatever is currently underneath the mouse pointer, so partially-sighted users can read the page by pointing at the text they are interested in. People with dyslexia sometimes resort to using a screen reader this way too. Screen readers can also be configured to read text by selecting it. If visible text has the aria-hidden="true" attribute, it can cause both of these methods to break.

The image title attribute is also getting in the way here. It duplicates the alt text, and some browser/screenreader combinations will read both of them. It also duplicates nearby visible text, so it's really not doing anything useful.

We should change it to this markup:

<div class="missing-media">
  <img src="..." alt="">
  <p class="missing-media__message">Missing media.</p>
</div>

Here, the image has no text alternative, but that's OK. This pattern is known as "image with adjacent text alternative".

andrewmacpherson’s picture

Oh wow, I don't think my concern in #169 applies anymore, after the changes in #167. That patch appeared while I was writing my a11y feedback.

andrewmacpherson’s picture

Patch #167 has CSS table properties:

diff --git a/core/themes/stable/css/media/filter.media_embed.css b/core/themes/stable/css/media/filter.media_embed.css
new file mode 100644
index 0000000000..1825af6087
--- /dev/null
+++ b/core/themes/stable/css/media/filter.media_embed.css
@@ -0,0 +1,30 @@
+/**
+ * @file
+ * Media Embed filter: default styling for displaying Media Embeds.
+ */
+
+/* Use display table so width is only as wide as the contents. */
+.missing-media {
+  display: table;

It's not a good idea to use CSS table display properties, if it isn't intended to be understood as a table. These have been found to have an affect on the semantics conveyed to assistive tech via the accessibility tree, in some browsers. See Using display:table has semantic effects in some screen readers.

Update: There are some conflicting reports too, in WCBuf: CSS Display Properties versus HTML Semantics and Tables, CSS Display Properties, and ARIA. So I'm not sure what the current situation is exactly.

It would be good if we can avoid using CSS table properties for this.

oknate’s picture

StatusFileSize
new26.76 KB
new82.9 KB

Addressing feedback in #168.
1. ✅changed let to const
2. ✅fixed two entity_embed references.
3. ✅

-    $text = $request->get('text');
+    $text = $request->query->get('text');

4. ✅See #118, this was a request by Wim:

This is follows the pattern established in \Drupal\media\Controller\OEmbedIframeController. If we're changing it here, then let's also update that one.

5. ✅ removed $themeExtensionList from DrupalMedia.
6. ✅ removed $moduleExtensionList from MediaEmbed.
7. ✅ Changed css names, although I modified the names to "media-embed-error". The reason why is that drupal-media is specific to CKEditor only, it's the name of the CKEditor plugin, while MediaEmbed is the name of the filter and it applies both in the CKEditor and after saving. We have a css class filter.media_embed.css that holds the css for the missing embed indicator's css. So for these reasons I think this name makes more sense.
8. ✅ added a comment about why we're merging the class attribute.
9. ✅ updated to move setting of attributes to empty array if not set outside for loop although, I don't think it matters in this context whether it's in the for loop. But I agree it's better stylistically.
10. ✅removed state.

It doesn't look like $this->state is used for anything.

I call \Drupal::state() lower on the page. I forgot to update that to $this->state, I guess. But I went ahead and removed it. If dependency injection isn't important here, it will make it less likely to break when the base class's dependencies are changed in the future.
11. ✅👍Good point.
12. ✅ Replaced $this->state with $this->container->get('state') in the test.
13. ✅Changed to assertSame() instead of assertEquals().
14. ✅ Replaced assertWaitOnAjaxRequest() calls.
15. ✅removed whitespace
16. ✅updated to use chaining.
17. ✅ Addressed in the next comment (#173).
18. ✅ Replaced assertWaitOnAjaxRequest() calls.
19. ✅fixed type hint for array.
20. In regards to this change:

+++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
@@ -258,26 +267,35 @@ public function providerOverridesAltAndTitle() {
-    $this->applyFilter($content);
+    $result = $this->processText($content, 'en', $filter_ids);
+    $this->setRawContent($result->getProcessedText());

$this->applyFilter() only applies the media_embed filter. The reason this changed is that processText allows you to choose which filters you are applying. I added a few tests in ::providerMissingEntityIndicator() including one where I don't use all of the filters. I removed filter_align for one test when I was working on fixing the issue where it was stripping the 'missing-media' class when filter_align was enabled. The test demonstrates how the attributes are added to the embed. In this patch I changed the assertion to this. the "no filter_align" test was a baseline for comparison when I was fixing the issue. The following assertion is where I test the class attribute is being merged correctly, preserving the missing media indicator's class and adding the align class from data-align property added by filter_align:

$this->assertContains('media-embed-error drupal-media-error--missing-source align-' . $additional_attributes['data-align'], $this->getRawContent());

Update: I improve this code below in #174.
21. 🤔 How do you use assertSession() in a Kernel test? It doesn't seem to be a method out of the box?
22. ✅passing TRUE as the third argument to in_array()
23. ✅fixed below in #174.

oknate’s picture

StatusFileSize
new4.03 KB
new82.81 KB

Addressing #168.17:

If findField() returns NULL, we'll call setValue() on NULL and this test will die with a fatal error. This should be $assert_session->fieldExists()->setValue() instead.

oknate’s picture

StatusFileSize
new1.18 KB
new82.95 KB

Addressing #168.23, simplifying assertion of alignment class in missing media indicator.

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new1.78 KB
new82.97 KB

Addressing #171: Removing "display: table" css that has led to some very interesting concerns about accessibility I didn't know about. This was something I added when I was trying to fix the outlines, which is no longer necessary now that we've simplified the markup of the missing embed indicator.

I also was missing a change for #168.7, where I'm updating the css style name for the missing media indicator.

-.missing-media.align-right {
+.media-embed-error.align-right {
oknate’s picture

StatusFileSize
new1.14 KB
new83.49 KB

This is a minor change, but something I've been wanting to do for a while. I've found in this thread it's more useful to follow the pattern Wim established with MediaEmbedFilterTest::testMissingEntityIndicator() and call the missing media method in the MediaEmbed filter an indicator.

-  protected function renderMissingMedia() {
+  protected function renderMissingMediaIndicator() {

I think the new version is more accurate and more useful. You can't actually render the missing media. You are rendering something to indicate that the media is missing. We have been using this terminology for a while to refer to it, so this just brings it in line.

phenaproxima’s picture

Status: Needs review » Needs work

Except for one CSS-related thing, we are down to supernits. This is really close to being ready. Amazing job with this, @oknate!

  1. +++ b/core/modules/media/css/filter.media_embed.css
    @@ -0,0 +1,32 @@
    +.media-embed-error {
    

    Question, looking at this CSS: I'm not clear on why we wouldn't simply have the embedded media always carry the drupal-media class, both inside and outside of the editor. If there is an error, it could also carry a class like drupal-media--error. I think this might simplify the CSS substantially.

  2. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,128 @@
    +  public function getLibraries(Editor $editor) {
    +    return [
    +      'core/jquery',
    +      'core/drupal',
    +      'core/drupal.ajax',
    +    ];
    +  }
    

    core/drupal.ajax already has explicit dependencies on core/drupal and core/jquery. Therefore, we only need to mention core/drupal.ajax here.

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -206,19 +207,25 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    +      '#attributes' => [
    +        'class' => [
    +          'media-embed-error',
    +          'drupal-media-error--missing-source',
    +        ],
    +      ],
    

    To elucidate my point about simplifying the CSS: if we make that change, I think the classes would look like this: ['drupal-media', 'drupal-media--error', 'drupal-media--missing-source'].

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -263,14 +270,27 @@ public function process($text, $langcode) {
    +          // We don't want to overwrite existing class of the embed or the
    +          // missing media indicator, but we need to merge in classes such as
    +          // those added by filter_align in order for those filters to function
    +          // properly.
    

    Minor but important phrasing thing: can we put parentheses around "or the missing media indicator"? This will make it clear that we're either dealing with an embed, or we're dealing with a missing media indicator.

  5. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -263,14 +270,27 @@ public function process($text, $langcode) {
    +          $merged_classes = array_unique(array_merge($build['#attributes']['class'], explode(' ', $attribute->nodeValue)));
    +          $build['#attributes']['class'] = $merged_classes;
    

    Nit: We don't really need $merged_classes as its own variable here.

  6. +++ b/core/modules/media/tests/modules/media_test_ckeditor/src/Controller/TestMediaFilterController.php
    @@ -0,0 +1,25 @@
    +/**
    + * Controller to allow testing of error handing in drupalmedia plugin.js.
    + */
    

    "handing" should be "handling" :)

  7. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,749 @@
    +    // Wait for ajax refresh.
    

    Supernit: 'ajax' should be 'AJAX', since it is technically an acronym :)

  8. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,749 @@
    +    $page->waitFor(10,
    +      function () use ($figcaption) {
    +        return $figcaption->find('xpath', '//a[@href="https://www.drupal.org"]');
    +      }
    +    );
    

    Nit: the callback function should not be on a new line after the wait time. So this should look like $page->waitFor(10, function () use ($figcaption) { ... });

  9. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
    @@ -0,0 +1,749 @@
    +    $a = $figcaption->find('css', 'a');
    +    $this->assertNotEmpty($a);
    +    $a->click();
    

    This can actually be simplified, because $assert_session->elementExists() can take a "container" element. So, you could do this instead: $assert_session->elementExists('css', 'a', $figcaption)->click();

  10. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
    @@ -339,7 +390,7 @@ public function testRecursionProtection() {
    +  public function testFilterIntegration(array $filter_ids, array $additional_attributes = [], $verification_selector, $expected_verification_success, array $expected_asset_libraries, $prefix = '', $suffix = '') {
    

    This is odd syntax; optional arguments, like $additional_attributes, all need to come after required arguments, like $verification_selector.

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new16.64 KB
new85.54 KB

Addressing feedback in #177. After I posted this, I got additional feedback on the classes. See below. I cancelled the test since I posted #179 a few minutes later.

oknate’s picture

StatusFileSize
new18.75 KB
new85.52 KB

Addressing feedback in #177, take two.
1. ✅ Updated the classes, see 3 for more info.
2. ✅ Removed:

+      'core/jquery',
+      'core/drupal',

3. ✅ Updated the classes, but with this additional feedback from you in the #media slack channel:

I think the error class should be `media-embed--error` (two dashes), because the idea is that “this is a media embed, but it’s in an error state”

Similarly, the I/O error should have `media-embed media-embed--error media-embed--preview-error`
The missing error should have `media-embed media-embed--error media-embed--missing-error`

-          'media-embed-error',
-          'drupal-media-error--missing-source',
+          'media-embed--error',
+          'media-embed--missing-source',

4. ✅Added parentheses.
5. ✅Removed variable, although I hate really long lines. Either way it's functionally the same. I'll take your suggestion.
6. ✅fixed typo, s/handing/handling, found two other places in core where there occurs, but left them be as they are out of scope.
7. ✅ fixed /s/ajax/AJAX
8. ✅fixed indentation
9. ✅fixed nit
10. ✅Updated the parameters to move $additional_attributes after required attributes.

oknate’s picture

I noticed that the drupalimage integration with drupallink adds a context menu, but the same integration for drupalmedia wasn't adding a context menu for the "Edit Link" and "Unlink" context menu items. So here's a fix for that:
context menu

Also, when I re-transpiled the drupalmedia plugin.js, I saw that I had missed one of the css changes from the last patch (#179) (see attached screenshot to see the bug)

oknate’s picture

StatusFileSize
new7.19 KB
new89.95 KB

This patch adds test coverage for the context menu items provided by drupallink plugin added in #180.

oknate’s picture

StatusFileSize
new1.4 KB
new89.93 KB

Fixing minor cut and paste issue: irrelevantly named variables.

oknate’s picture

Here's a minor @todo. There's a coding standard error found by the testbot:

/var/lib/drupalci/workspace/jenkins-drupal_patches-3069/source/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php ✗ 2 more
270	Arguments with default values must be at the end of the argument list
393	Arguments with default values must be at the end of the argument list
oknate’s picture

StatusFileSize
new3.27 KB
new89.93 KB

Fixing coding style issue in #183.

oknate’s picture

StatusFileSize
new12.03 KB
new211.74 KB

Ignore this patch, I forgot to pull the latest changes to my working branch, so it has a lot of extra garbage in it undoing commits.

oknate’s picture

StatusFileSize
new12.03 KB
new90.76 KB

We have added a lot of useful CKEditor testing helper methods, and now that I have started to work on test coverage for the next issue for media embedding, I'm realizing that allowing these methods to be reused in other tests would be valuable.

Therefore in this patch, I'm moving the CKEditor testing helper methods into a trait.

/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorTestTrait.php

Also, since the CKEditor might have a different instance name, I'm parameterizing it in the new methods added in #181, so it's not hardcoded like this:
- $open_context_menu = "var editor = CKEDITOR.instances['edit-body-0-value'];
Wim had already established that pattern in 25, I had just forgotten about this when adding the context menu test coverage in #181:
protected function waitForEditor($instance_id = 'edit-body-0-value', $timeout = 10000)

One minor @todo: "Constructs a new DrupalMedia filter plugin object." should be something like "Constructs a new DrupalMedia CKEditor plugin object."

oknate’s picture

StatusFileSize
new677 bytes
new90.76 KB

Addressing minor @todo in #186:

-   * Constructs a new DrupalMedia filter plugin object.
+   * Constructs a new DrupalMedia plugin object.

(This isn't a filter.)

oknate’s picture

Issue summary: View changes
starshaped’s picture

@phenaproxima and I paired to refactor the CSS for embedded media inside CKEditor. The end result is a little more verbose than what was there previously, but the decisions in there are documented and we were able to remove the !important, which should generally be avoided if possible. New patch attached, plus screenshots of how it looks with all combinations of alignment and captioning. This was tested on a Mac, in the latest version of Opera.

oknate’s picture

Thanks for the updated css.

+/**
+ * For center alignment, take advantage of drupal-media's inline-block
+ * display and center it as if it were text
+ */

One thing, line 47:
* display and center it as if it were text should end in a period.

phenaproxima’s picture

Status: Needs review » Needs work

Yeah, this is pretty much ready for RTBC in my opinion. I have a few nitpicks, and the only major change I think we need is to walk back what I now think is a premature abstraction introduced in #186. I think that having a CKEditor test trait is a good idea in general, but it may carry implications that are well outside the scope of this patch. So let's revert that for the time being, and move your good work into a new patch filed in a follow-up issue.

  1. +++ b/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorTestTrait.php
    

    Although copy-and-pasting code sucks, and I understand and fully agree with the rationale here, I think that abstracting all this common functionality into a trait may be premature and will raise committer eyebrows. It might be a smarter idea for us to keep the methods isolated to the relevant test class(es) for now, and then open a follow-up to move them into a trait. What say you?

  2. +++ b/core/modules/media/css/filter.media_embed.css
    @@ -0,0 +1,29 @@
    +  display: inline-block;
    

    Is there any particular reason for choosing inline-block, especially considering that filter_align adds floats in the .align-* classes? If so, we should probably document that rationale in a comment.

  3. +++ b/core/modules/media/css/filter.media_embed.css
    @@ -0,0 +1,29 @@
    +.media-embed.align-left {
    +  margin-right: 1em;
    +  margin-bottom: 1em;
    +}
    +
    +.media-embed.align-right {
    +  margin-left: 1em;
    +  margin-bottom: 1em;
    +}
    +
    +.media-embed.align-center {
    +  margin-top: 1em;
    +}
    

    I'm surprised that filter_align doesn't have margins already!

  4. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,277 @@
    +    // Register context menu items for editing link.
    +    if (editor.contextMenu) {
    +
    +      editor.contextMenu.addListener(function () {
    

    Nit: There's an extra blank line here that shouldn't be.

  5. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,277 @@
    +        let menu = {};
    +        if (widget.data.link) {
    +          menu = {
    +            link: CKEDITOR.TRISTATE_OFF,
    +            unlink: CKEDITOR.TRISTATE_OFF
    +          };
    +        }
    +        return menu;
    

    Rather than use let menu = {} and overwriting it, let's just return stuff as needed:

    if (widget.data.link) {
      return {...}
    }
    return {};
    
  6. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -187,14 +187,15 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
         // There are a few concerns when rendering an embedded media entity:
         // - entity access checking happens not during rendering but during routing,
    -    //   and therefore we have to do it explicitly here for the embedded entity;
    +    //   and therefore we have to do it explicitly here for the embedded entity.
         $build['#access'] = $media->access('view', NULL, TRUE);
         // - caching an embedded media entity separately is unnecessary; the host
    -    //   entity is already render cached;
    +    //   entity is already render cached.
         unset($build['#cache']['keys']);
         // - Contextual Links do not make sense for embedded entities; we only allow
    -    //   the host entity to be contextually managed;
    +    //   the host entity to be contextually managed.
    

    Not a big deal, but why did we change these? I thought the semicolons were perfectly clear...

  7. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -263,14 +270,31 @@ public function process($text, $langcode) {
    +      if (empty($build['#attributes']['class'])) {
    +        $build['#attributes']['class'] = ['media-embed'];
    +      }
    +      else {
    +        array_unshift($build['#attributes']['class'], 'media-embed');
    +      }
    

    Nit: This seems like it could be streamlined a bit. How about:

    if (empty($build['#attributes']['class'])) {
      $build['#attributes']['class'] = [];
    }
    $build['#attributes']['class'][] = 'media-embed';
    

RTBC once all these things are addressed.

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new15.08 KB
new91.03 KB

Addressing #191:
1. ✅Removed the added trait.
2. The rationale here is to mimic image2 plugin's behavior. Perhaps this: https://github.com/ckeditor/ckeditor-dev/blob/master/plugins/image2/dev/... The last style? When you remove the style on the drupal-media element, the outline breaks, either stretching to the full-width or not enclosing the contents.
3. ¯\_(ツ)_/¯ I guess enforcing a default could be seen as a negative. Personally, I'd rather it look ok out of the box.
4. ✅Removed extra space.
5. ✅Dropped variable.
6. I have a good reason for changing the semicolons to colons here. I've been running the code through coder module's sniffer:
phpcbf '--standard=vendor/drupal/coder/coder_sniffer/Drupal/'
The change to periods makes the sniffer happy and I don't think it changes the meaning.
7. The order of css classes matter. The 'media-embed' class should go first, I think. In the case of the ::renderMissingMediaIndicator() build array, it would lead to this order 'media-embed--error media-embed--missing-source media-embed' rather than 'media-embed media-embed--error media-embed--missing-source'. I could rework the code to add the class here:

     '#attributes' => [
        'class' => [
+          'media-embed',
          'media-embed--error',
          'media-embed--missing-source',
        ],
      ],

And then change the code adding the 'media-embed' class to:

      if (empty($build['#attributes']['class'])) {
        $build['#attributes']['class'] = ['media-embed'];
      }
      elseif (!in_array('media-embed', $build['#attributes']['class'])) {
        $build['#attributes']['class'][] = 'media-embed';
      }

That would put the 'media-embed' class at the beginning of the missing media indicator's classes, but after any existing classes on the media embed. What do you think?

Also, I'm adding credit for ckrina for her feedback.

Another small change:

-      $wrapper = $assert_session->waitForElementVisible('css', '.media-embed-widget', 1000);
+      $wrapper = $assert_session->waitForElementVisible('css', '.media-embed-widget', 2000);

This failed for me on a slower computer.

oknate’s picture

StatusFileSize
new681 bytes
new90.36 KB

I'm not sure how these unrelated changes crept in, removing:

diff --git a/core/themes/bartik/css/components/captions.css b/core/themes/bartik/css/components/captions.css
index 03ad4799e9..9dda3d2915 100644
--- a/core/themes/bartik/css/components/captions.css
+++ b/core/themes/bartik/css/components/captions.css
@@ -2,6 +2,7 @@
 .caption {
   margin-bottom: 1.2em;
 }
+
 .caption > * {
   padding: 0.5ex;
   border: 1px solid #ccc;
diff --git a/core/themes/stable/css/media/filter.caption.css b/core/themes/stable/css/media/filter.caption.css
index a92505c308..776d00b8ec 100644
--- a/core/themes/stable/css/media/filter.caption.css
+++ b/core/themes/stable/css/media/filter.caption.css
@@ -8,3 +8,4 @@
   float: none;
   margin: unset;
 }
+
oknate’s picture

StatusFileSize
new754 bytes
new89.63 KB

This is also a coding style change that is unrelated to the focus of the issue, so removing.

diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php
index bec8535167..7296d8dd88 100644
--- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php
+++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTranslationTest.php
@@ -33,10 +33,10 @@ protected function setUp() {
 
     $this->embeddedEntity->addTranslation('pt-br')
       ->set('field_media_image', [
-      'target_id' => $this->image->id(),
-      'alt' => 'pt-br alt',
-      'title' => 'pt-br title',
-    ])->save();
+        'target_id' => $this->image->id(),
+        'alt' => 'pt-br alt',
+        'title' => 'pt-br title',
+      ])->save();
   }
 
   /**
phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

I think we are pretty much done here. The patch has been thoroughly put through its paces; bugs have been found, fixed, and tested; the test coverage is very extensive; the UX team has reviewed the work, and their changes have been implemented; the front-end framework manager has reviewed this (although it might need a re-review, we'll see); and a backend framework manager has reviewed as well.

I don't think we need to do more iterating here; the resulting functionality is stable, attractive, and usable. Further tweaking can and should happen in follow-ups. On to the next stage. RTBC!

lauriii’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/media/css/filter.media_embed.css
    @@ -0,0 +1,29 @@
    +.media-embed.align-left {
    ...
    +.media-embed.align-right {
    ...
    +  margin-bottom: 1em;
    ...
    +.media-embed.align-center {
    
    +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    @@ -0,0 +1,63 @@
    +.media-embed-widget .media-embed.align-left {
    ...
    +  margin-bottom: 0;
    ...
    +.media-embed-widget .media-embed.align-center {
    ...
    +.media-embed-widget .media-embed.align-right {
    ...
    +  margin-bottom: 0;
    ...
    +.media-embed-widget.align-left {
    ...
    +  margin-bottom: 1em;
    ...
    +.media-embed-widget.align-center {
    ...
    +.media-embed-widget.align-right {
    ...
    +  margin-bottom: 1em;
    

    Why are we setting margin-bottom when aligned to left and right, but not when aligned to the center?

  2. +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    --- /dev/null
    +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    

    This file should be run prettier and eslint.

  3. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    @@ -0,0 +1,275 @@
    +              const html = '<div class="media-embed media-embed--error media-embed--preview-error">' + error + '</div>';
    

    Markup should be wrapped in a theme function.

  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -263,14 +270,31 @@ public function process($text, $langcode) {
    +        $build['#attributes']['class'] = ['media-embed'];
    ...
    +        array_unshift($build['#attributes']['class'], 'media-embed');
    

    I don't think we should be adding this class globally. This class is not essential to the functionality. We could create a new template for this, and add the classes in Classy.

phenaproxima’s picture

I discussed #196 with @lauriii and @oknate to clarify what's being requested in the fourth point. I was pretty unclear on the purpose of Classy versus Stable, so this really helped sort out my thinking:

  • Embedded media errors should only be styled in Classy (and therefore every theme that extends it) and inside CKEditor. Everywhere else, there should be no default styling on them. So, we should move the error styling into ckeditor.drupalmedia.css, and then copy it into a new style sheet in Classy.
  • We don't really need the media-embed class. At all. It doesn't truly add anything, so let's remove all mention of it from core.
  • Therefore, media-embed--error should become media-embed-error (note the removal of a hyphen); it should be, from a CSS perspective, its own distinct entity. The related "state" classes should be renamed accordingly.
  • Ideally, when rendering missing or erroneous media under Stable (or a subtheme thereof), the media-embed-error class(es) would not be applied at all (i.e., they wouldn't even be mentioned in the markup). However, making that happen would require us to do some weird backend acrobatics which are not worth the time or effort, so @lauriii said he would not block commit on this point.
effulgentsia’s picture

#194 is looking really good to me. I don't have any framework management concerns with it. Just the following nits:

  1. +++ b/core/modules/media/src/Controller/MediaFilterController.php
    @@ -0,0 +1,111 @@
    +      // Allow the end user to cache it for up to 5 minutes.
    +      ->setMaxAge(300);
    

    Why do we need to do this? Isn't the JS code already implementing a cache via _previewNeedsServerSideUpdate()? If this additional caching is needed on top of that, let's add code comments explaining it. If we retain this, then I'm also curious if we want to make _previewNeedsServerSideUpdate() respect this value, since currently it looks like it can return FALSE even if more than 5 minutes has passed.

  2. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,126 @@
    +      $this->moduleExtensionList->getPath('media') . '/css/filter.media_embed.css',
    +      $this->moduleExtensionList->getPath('system') . '/css/components/hidden.module.css',
    

    If a module or theme is overriding these, I'm assuming we want those overrides in the editor too? In which case, we should get these values from the library info.

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -206,19 +207,25 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
         return [
    -      '#theme' => 'image',
    -      '#uri' => file_url_transform_relative(file_create_url('core/modules/media/images/icons/no-thumbnail.png')),
    -      '#width' => 180,
    -      '#height' => 180,
    -      '#alt' => $this->t('Missing media.'),
    -      '#title' => $this->t('Missing media.'),
    +      '#type' => 'html_tag',
    +      '#tag' => 'div',
    +      '#value' => $this->t('The referenced media source is missing and needs to be re-embedded.'),
    +      '#attributes' => [
    +        'class' => [
    +          'media-embed--error',
    +          'media-embed--missing-source',
    +        ],
    +      ],
    +      '#attached' => [
    +        'library' => ['media/filter.media_embed'],
    +      ],
         ];
    

    This warrants a theme hook, so that themes can override the markup and classes.

phenaproxima’s picture

This warrants a theme hook, so that themes can override the markup and classes.

@effulgentsia agreed that this can be done in a follow-up: #3071713: Make error messages for embedded media themeable.

effulgentsia’s picture

In which case, we should get these values from the library info.

To clarify, here's what I mean...

Inject the 'library.discovery' service into DrupalMedia. Then in getCssFiles(), instead of $this->moduleExtensionList->getPath('system') . '/css/components/hidden.module.css', do this...

$library_definition = $this->libraryDiscovery->getLibraryByName('system', 'base');
// Find the item in $library_definition['css'] that contains 'hidden.module.css' in its 'data'. Return that item's 'data'.

That's for 'hidden.module.css', because we don't want the rest of system/base.

But for filter.media_embed.css, I think we should return all of the items that get returned in $this->libraryDiscovery->getLibraryByName('media', 'filter.media_embed')['css'].

oknate’s picture

StatusFileSize
new23.35 KB
new91.29 KB

See #206, this patch was missing a change.

oknate’s picture

Status: Needs work » Needs review

Triggering tests.

oknate’s picture

StatusFileSize
new23.67 KB
new91.34 KB
new704 bytes

See #206, this patch was missing a change.

The last submitted patch, 201: 2994696-198.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 203: 2994696-203.patch, failed testing. View results

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new24.1 KB
new1.58 KB
new91.36 KB

Addressing #196.1, adding margin bottom.
#200, #198.2 👍✅- Added $this->libraryDiscovery->getLibraryByName. Note, since we're dropping the 'media-embed' class (#196.4), I'm dropping the 'media/filter.media_embed' library. The only thing left was the .media-embed-error styles, and since those aren't going to applied in stable, only in classy, I created a library in classy for that, and left it unstyled in stable (per #197.1).
#196.2 I ran eslint and prettier. I'm more of a BE dev. So there were some es lint errors I wasn't sure what do with. I can research them, but it might be better if someone tells me which of these need fixing. Many I can figure out myself, such as "Missing JSDoc". I'll work on that tomorrow.

core/modules/media/js/plugins/drupalmedia/plugin.es6.js
    6:2   warning  Unexpected unnamed function                                                                                                                                            func-names
    7:3   error    'use strict' is unnecessary inside of modules                                                                                                                          strict
   40:50  warning  Unexpected unnamed function                                                                                                                                            func-names
   54:53  warning  Unexpected unnamed function                                                                                                                                            func-names
   70:38  warning  Unexpected unnamed function                                                                                                                                            func-names
   70:38  error    Unexpected function expression                                                                                                                                         prefer-arrow-callback
   94:34  warning  Unexpected unnamed function                                                                                                                                            func-names
   99:7   error    Unexpected string concatenation                                                                                                                                        prefer-template
  111:13  error    Use object destructuring                                                                                                                                               prefer-destructuring
  117:7   error    for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array  no-restricted-syntax
  122:11  error    ["a"] is better written in dot notation                                                                                                                                dot-notation
  141:17  error    Use object destructuring                                                                                                                                               prefer-destructuring
  169:14  warning  'event' is defined but never used                                                                                                                                      no-unused-vars
  188:25  error    Unexpected string concatenation                                                                                                                                        prefer-template
  197:59  error    A constructor name should not start with a lowercase letter                                                                                                            new-cap
  202:50  error    A constructor name should not start with a lowercase letter                                                                                                            new-cap
  244:9   warning  Missing JSDoc return description                                                                                                                                       valid-jsdoc
  247:12  warning  Use @return instead                                                                                                                                                    valid-jsdoc
  258:9   warning  Missing JSDoc return description                                                                                                                                       valid-jsdoc
  258:9   warning  Missing JSDoc for parameter 'data'                                                                                                                                     valid-jsdoc
  284:15  error    Unexpected string concatenation  

#196.3 - addressed in #199 and follow-up issue to add theme template for missing media indicator.
#197.1 ✅ - .media-embed-error replaces .media-embed--error, and it's only styled in classy, not stable.
#197.2 ✅ - dropped .media-embed class
#197.3 ✅ - .media-embed-error replaces .media-embed--error, .media-embed-error--preview-error replaces .media-embed--error--preview-error
#197.4 ¯\_(ツ)_/¯ - I'm not sure how we'd strip these classes in stable. There's no .theme file, and I'm not sure what hook we could use anyway.
#198.1 Here's the work Wim did to set this up, it may help answer the rationale behind it. #3064340: Make preview responses cacheable to accelerate previews. One example is when the "source" button is clicked. The previews go away, someone edits a small change and clicks the "source" button again, without the caching of the preview route, it would have to regenerate the preview.

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs frontend framework manager review

Code looks pretty good...a few minor things. Also, given the changes we've made here, I'm marking this for front-end framework manager review again.

  1. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
    index 686e774521..51e56e8f94 100644
    --- a/core/modules/media/media.libraries.yml
    
    --- a/core/modules/media/media.libraries.yml
    +++ b/core/modules/media/media.libraries.yml
    
    +++ b/core/modules/media/media.libraries.yml
    +++ b/core/modules/media/media.libraries.yml
    @@ -31,3 +31,4 @@ filter.caption:
    
    @@ -31,3 +31,4 @@ filter.caption:
           css/filter.caption.css: {}
       dependencies:
         - filter/caption
    +
    

    Superdupernit: We can remove this extra blank line, I think :)

  2. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,167 @@
    +  protected function getCssFilesFromLibraryByName($filename, $extension, $name) {
    +    $library = $this->libraryDiscovery->getLibraryByName($extension, $name);
    +    $strlen = strlen($filename);
    +    if (!$library || empty($library['css']) || $strlen === 0) {
    +      return [];
    +    }
    +    $files = [];
    +    foreach ($library['css'] as $item) {
    +      // If string ends in $filename, add it to the return array.
    +      if (substr($item['data'], -$strlen) === $filename) {
    +        $files[] = $item['data'];
    +      }
    +    }
    +    return $files;
    +  }
    

    Not a big deal, but I think having this method is a bit too "generic". I would propose we move all of this logic into getCssFiles(), and simply hard-code the name of the file we're trying to extract (hidden.module.css), and the extension (system) and name of the library (base) from which we're trying to extract it. We could potentially also take advantage of preg_grep() to locate the file, in the library's CSS list, rather than doing interesting substr() and strlen() acrobatics. :)

  3. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -206,19 +206,25 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    +  protected function renderMissingMediaIndicator() {
    

    We should add a @todo to the doc comment:

    @todo Make this themeable in https://www.drupal.org/project/drupal/issues/3071713
    
  4. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
    @@ -206,19 +206,25 @@ protected function renderMedia(MediaInterface $media, $view_mode, $langcode) {
    +      '#attached' => [
    +        'library' => ['media/filter.media_embed'],
    +      ],
    

    I thought this library no longer exists...??

  5. +++ b/core/themes/classy/classy.libraries.yml
    @@ -68,6 +68,12 @@ indented:
    +media_embed_error:
    +  version: VERSION
    +  css:
    +    component:
    +      css/components/media-embed-error.css: { weight: -10 }
    

    I think we should call this 'media_embed', just in case we later decide to add more stuff to it.

lauriii’s picture

Issue summary: View changes
StatusFileSize
new53.65 KB
  1. +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    @@ -0,0 +1,75 @@
    +.media-embed-widget .media-embed.align-left {
    +  margin-right: 0;
    +  margin-bottom: 0;
    +}
    +
    +.media-embed-widget .media-embed.align-center {
    +  margin-top: 0;
    +  margin-bottom: 0;
    +}
    +
    +.media-embed-widget .media-embed.align-right {
    +  margin-left: 0;
    +  margin-bottom: 0;
    +}
    

    Is there any downside if we apply this rule always to .media-embed when inside .media-embed-widget?

    Can .media-embed be rendered outside .media-embed-widget? Could we apply this rule more globally so that this is applied even when .media-embed isn't necessarily rendered inside .media-embed-widget?

  2. +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
    @@ -0,0 +1,75 @@
    +  padding: 100px 20px 20px 20px;
    

    Nit: you can remove the last 20px. CSS will shorthand it so that the value given for right side is also used for the left side if it doesn't have its own value.

  3. +++ b/core/themes/classy/classy.libraries.yml
    @@ -68,6 +68,12 @@ indented:
    +media_embed_error:
    

    I don't think this library is loaded at the moment because these styles aren't showing up when testing with Umami.


  4. What are these empty

    elements?

  5. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
    @@ -0,0 +1,167 @@
    +    $files = $this->getCssFilesFromLibraryByName('hidden.module.css', 'system', 'base');
    

    This is really strange. Could we just duplicate the rule needed for now and open a follow-up to come up with a better solution?

phenaproxima’s picture

Thanks, @lauriii! Let the kung-fu fighting begin :)

Can .media-embed be rendered outside .media-embed-widget? Could we apply this rule more globally so that this is applied even when .media-embed isn't necessarily rendered inside .media-embed-widget?

Whoops! Looks like we forgot to remove all mentions of .media-embed. :)

What are these empty elements?

@oknate discovered this while testing. It's a weird quirk of CKEditor and can be hacked around, but the hack itself would probably be kinda controversial and would drag this issue out even longer. I propose we open a follow-up to fix this there.

oknate’s picture

StatusFileSize
new92.48 KB
new7.75 KB
new109.97 KB

Addressing feedback in #207. I'll update this comment with notes in a bit.

lauriii’s picture

Related to #208.5, I was wondering if this is actually highlighting a bigger problem we might be having; what happens if there are libraries attached to the entity? Why do we have to manually add these CSS files? Shouldn't they be already a dependency to the markup depending on those assets?

I haven't done a detailed review on this yet, but overall I feel fine with the patch so I don't think this should be blocked by a review from me.

phenaproxima’s picture

Let's take a quick pulse check at this point.

  • @lauriii is not reachable for the next two weeks, and has signed off on this patch from a front-end framework manager perspective. Front-end concerns do not need to be considered commit-blocking from this point, once we have dealt with each point of review in #208.
  • I asked for clarification on #208.1, and got it. If it won't break the cascade (and I don't think it will), we should simplify media-embed-widget drupal-media > .align-center and its ilk to just drupal-media > .align-center in ckeditor.drupalmedia.css.
  • We need to either confirm or deny that #208.3 is a bug. If it IS a bug, we need to fix it and hopefully add test coverage.
  • @lauriii told me that #208.4 is okay to fix in a follow-up, so let's open one for that. (It is a known issue in Entity Embed too.)
  • The addition of System's hidden.module.css dates back to Wim's initial patches in this issue. The ability for it to be influenced by library overrides was added at @effulgentsia's request from #198.2 and #200. It would be helpful to get Wim's rationale, if there is one, for not allowing library overrides to influence the inclusion of hidden.module.css. Was it just an oversight? Or was there some deeper reason? If we allow library overrides, are we introducing potentially negative implications?
  • We need to know what happens if embedded entities have asset libraries attached. If it's a problem, we need a follow-up in which to address that. If it's not, we need a written rationale as to why not.

In other words, there aren't very many code changes needed here; we mostly just need to justify some decisions and (probably) open some follow-ups.

phenaproxima’s picture

Issue tags: +Needs followup

Just discussed things with @effulgentsia and @oknate. Here's what we need to do:

  • #208.1 and #208.3 need to be fixed and/or verified.
  • We need to open a follow-up for #208.4.
  • @effulgentsia agreed that, in this particular case, we should go back to hard-coding System's hidden.module.css in the DrupalMedia plugin -- so we can remove the library override stuff and related test coverage. However, we also need to open a follow-up to explore this idea more fully.
  • There is a very clear rationale for not allowing asset libraries to be attached to rendered embeds -- namely, it opens up a very complex can of worms. So, we need to open a follow-up to discuss that further. If it's going to be a thing, it does not need to be MVP, and it certainly should not block this patch.
  • Regarding the preview caching stuff raised by @effulgentsia in #198.1, there is a clear reason why we cache the server-side response. It has to do with needing frequent re-renders of media embeds, if certain attributes which necessitate server-side re-rendering (like data-align), change frequently, which they may do under certain uses. This calls for a comment in the JavaScript code of the drupalmedia plugin.

So, let's get these follow-ups filed, changes made, decisions documented, and things fixed. Then we are, by agreement of virtually everyone still involved, at RTBC.

wim leers’s picture

I was on vacation, catching up since #143! While I was working on this, more comments kept appearing 🙈

Overall observation while I was reading and responding to all the comments: the scope of this issue is becoming too big. I think it'd be helpful to first make filter changes in a separate issue.

#144:

  • RE: error message styling. Well, I didn't mean for it to be styled exactly like the "error" status messages. I agree that looks weird. But it is a possibility. I defer to the usability team on that.
  • RE: CKEDITOR.plugins.drupalimage.getFocusedWidget. That's unfortunate, but I agree it's the right thing to do.

#145:

#146: Looks good! Hilarious text once again :D Nit: please use assertSame() instead of assertEquals().

#147:

  • RE: Test coverage for DrupalImage. Indeed no test coverage exists for most existing CKEditor integration things. I think it's a nice win to add test coverage here even though it is out of scope. If core committers are fine with it being part of this patch, I'm fine with it too. But while you're waiting for this to be committed, you might as well extract this to a separate issue and patch so it can land independently :)
  • RE: testLinkabilityWhenDrupalImageIsAbsent. Great catch! 🦅👁 The changes look good, except for the // Remove the `drupalimage` plugin's `DrupalImage` button. logic should be using the same logic as the now-deleted testLinkabilityWhenDrupalImageIsAbsent() used to execute. On second thought … this is probably simpler and easier to understand. It's just more brittle, because you have to keep this in sync with what's in ::setUp(). And brittleness is important.

#148:

  • RE: I feel like I've already explained this several times. Putting this aside for now, because perhaps subsequent patches make this discussion obsolete. I think we're just misunderstanding each other :) I've left detailed comments with rationales on the two relevant hunks of the patch about this. (And this goes back all the way to #81.3 by the way!)
  • RE: #143.3. Yay!
  • RE: #143.4. (Checked with @lauriii, this was actually referring to the second bullet I wrote in #137.) I agree that content sync would be rare. But that's just an example. The point is that we cannot know why a particular media entity cannot be loaded: perhaps the UUID is wrong, perhaps the UUID is for yet to be synced media, perhaps the UUID is for now deleted media. I agree that the text should help the 95% scenario. So: +1!

I think it might be best to do a quick call about the first and last bullet.

#149:

  • RE: #70.1. Yay!
  • RE: #70.2. Darn! That is one hell of a subtle difference 🦅👁 This is one of those places where Symfony is genuinely terrible. I completely agree we should lock this down to only GET, since that's what the drupalmedia plugin uses (and yes, it's okay for us to assume GET suffices). I'm fine with changing MediaFilterController to match EditorController like you pointed out, but I think we should also restrict it at the routing level, by adding methods: [GET] to the route definition.

#150:

  • RE: AJAX error test. 👍
  • RE: #136.4. I am fine with either way, but I agree this keeps the scope more manageable.

#153

  • RE: 1. See my response to #148 about this same thing, how about you and @lauriii discuss this and whatever text you agree upon is what we do?
  • RE: 2+3+4. AFAICT we can solve this by using the JS Messages API that Drupal 8.7 introduced? See https://www.drupal.org/node/2930536.

#154 + #157: RE: class="missing-media". Nice find. Note that this is not a problem in HEAD because in HEAD, the missing media render array does not contain a class. Perhaps we can move the entire set of "improve missing media handling" changes to a separate issue? How do @lauriii and @ckrina feel about that?

#159: I'm super suspicious of CSS specifically for left alignment. What about right alignment? How does this hold up in RTL contexts?

#166: Thanks for organizing that call to expedite progress! I am fine with that. But I wonder what you think about my suggestion (see my response to #153).

#167: Oh, using a background image, very interesting! Could we do the same for the filter-side generated missing media message? Also, I object to using the same class="missing-media" here, because this is an AJAX error (either due to network failure or server failure), no media is actually missing.

#168:

  • 4. I think it is in scope since this patch was originally using the exact same comment, we had to change it, so we need to update the other comment too. Especially since it's in the same module.
  • 7. Sounds good, and that would address my concern in #167 too :)
  • 14. assertWaitOnAjaxRequest() is not unreliable if there aren't multiple AJAX requests happening.

#169: The title attribute was specifically added so that sighted users could see a helpful message when they're hovering over the image when looking at the filtered text (so not in the text editor, but when looking at /node/42 for example).

#177:
2. I don't think we should not deduplicate dependencies ourselves. Drupal handles that for us. We should have a dependency on every asset library whose symbol we import into the global closure of a JS file. Here, that's jQuery (hence core/jquery) and Drupal (hence core/drupal). And we also need the AJAX system to be available (hence also core/drupal.ajax).

#179: This is now changing the parameter order of \Drupal\Tests\media\Kernel\MediaEmbedFilterTest::testFilterIntegration() only to make $additional_attributes optional. But all test cases provide this anyway. It also causes significant changes in the data provider. Please revert this.

#180 + #181: RE: link context menu. Nice catch! 🥳

#186: 👎 This patch is plenty large as it is. We also generally avoid creating premature abstractions. Please create that trait in the next issue instead of this one. EDIT: Hah, @phenaproxima said the same in #191. EDIT: #192 addressed this!

#189: I don't understand why after lots of back-and-forth in the past we concluded that inline-block was not possible yet now it suddenly is. Will investigate.

#191:
4. Because the alignment filter does not and cannot make assumptions; it does only alignment. It's up to the UI/content component itself to have appropriate margins. Anyway, this dates back to >5 years ago, discussing this more is out of scope here for sure.

#196++

#197++

#198:

  1. I'm glad you asked 😄This is needed so that browsers perform client-side caching of the received AJAX response. To convince yourself, remove that line and observe how responses are no longer cached. Or run the explicit test coverage I wrote for it: testPreviewUsesDefaultThemeAndIsClientCacheable(). See the screencast demonstrating its impact: https://www.drupal.org/files/issues/2019-06-27/3064340%20before.mov versus https://www.drupal.org/files/issues/2019-06-27/3064340%20after.mov. This work was done in #3064340: Make preview responses cacheable to accelerate previews
  2. Interesting point. But … \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImageCaption::getCssFiles() and \Drupal\ckeditor\Plugin\CKEditorPlugin\Language::getCssFiles() are also not yet doing that. How about we create a follow-up issue to fix all of those at the same time? Right now, this is just following the existing pattern in Drupal core. Besides, this arguably should be handled in the caller of a plugin's getCssFiles(): \Drupal\ckeditor\CKEditorPluginManager::getCssFiles(). Then we don't burden each CKEditor plugin with this. Would you be okay with that? EDIT: #200 is a very helpful clarification! I still believe we should fix this one abstraction level higher. But … perhaps that's just not possible in the abstraction we have: if each plugin is supposed to return CSS files, then the caller (one abstraction level up) can't magically determine which asset libraries they originate from. So … I think that means I agree with you. 😁👍 I think we then do need a follow-up to fix other CKEditor plugins to do this too, as well as update the API docs to recommend this pattern. For CKEditor 5, we should not port this API design flaw in #2966864: Add optional support for CKEditor 5 in D9 so we can remove CKE 4 from Drupal 10.
  3. Why? In Drupal we tend to provide abstractions for every single thing even if nobody will ever use it. I propose we add that capability only when a number of people ask for it. Limiting our API surface and all that :) Let's at least defer this to a follow-up, because it merits discussion of its own. Plus, this is not being introduced here, it's only being enhanced. #2940029: Add an input filter to display embedded Media entities introduced it. EDIT: #199 did exactly that 👍

#206:

  • Wow, this interdiff is changing A LOT!
  • #208.4: CKEditor must be generating those automatically. As long as those empty tags don't disrupt anything, I'd like to not deal with that in this issue, since this has already become extremely long.

    #211:

    • We need to manually load CSS files into CKEditor iframe instances because they're separate from the rest of the page.
    • We specifically do not want to load asset libraries associated with a rendered entity because A) that would also load JS, and we don't want previews to become interactive, they're just previews, B) a rendered media entity's hypothetical attached CSS (because there isn't any in Drupal core) would be written with the assumption that basic theme CSS is already present, which is not true in CKEditor iframe instances. If a module wants to provide styling tweaks to media previews in CKEditor iframe instances, they can use hook_ckeditor_css_alter(). If a theme wants to do this, they can use ckeditor_stylesheets: […] in their *.info.yml file. This is not new. Look at bartik.info.yml for an example 🙂 hidden.module.css in particular was introduced in #3057578: .visually-hidden not respected in CKEditor preview to ensure that a field label with .visually-hidden does not get displayed in the CKEditor preview. There are clear before vs after screenshots in that issue summary.

    #212:
    RE: last bullet. Media entities rendered by Drupal don't have asset libraries attached by default. I explained above why we don't want this. I can expand on it more if you like. Loading attached CSS and JS — problematic as it is for the reasons I've outlined already — would require us to use the AJAX system. This would mean that requests would use the back-end theme instead of the front-end theme, thus loading both the wrong template (that of the admin theme) and the wrong asset libraries (those of the admin theme). It would also make the responses uncacheable (AJAX responses are uncacheable). If you want the nitty gritty background, see #2844822: The preview in CKEditor does not use the same Twig template as the one on the front end (default theme). Specifically, see #2844822-41: The preview in CKEditor does not use the same Twig template as the one on the front end (default theme), where I anticipated this would come up 🙉. Entity Embed prior to 1.0 was using the AJAX system. One early user with a super advanced use case needed it. They acknowledged it would not work for everyone always. Many people ran into the problem of the wrong Twig template being used. By now 23% of all Drupal 8 Entity Embed installs is using 8.x-1.0 where no AJAX request is used, and hence no CSS or JS is loaded. There have been no complaints. (I just triaged the Entity Embed issue queue again to make sure.)

    Patch review

    1. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
      @@ -0,0 +1,306 @@
      +          // Add a class to the .cke_widget_drupalmedia wrapper for styles in
      +          // ckeditor.drupalmedia.css.
      +          this.element.getParent().addClass('media-embed-widget');
      

      👎 I still think this is wrong. We shouldn't add class="media-embed-widget" just to allow us to write .media-embed-widget. Our CSS should just use . cke_widget_drupalmedia .

      The CSS is coupled to CKEditor. The file is even named ckeditor.drupalmedia.css. Adding an extra level of indirection is just confusing.

      I apparently already explained this before in #143 and in #137.

      This was triggered by @phenaproxima in #136.2, where he wrote:

      I'm not sure we can rely on .cke_widget_* classes, since AFAIK they're generated by CKEditor and are internal to it.

      This is a valid concern! :) I'm glad it was raised. Fortunately we can rely on these classes.

      ✅ Fixed.

    2. +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
      @@ -0,0 +1,306 @@
      +          // Convert data-align attribute to class so we're not applying styles
      +          // to data attributes.
      +          if (this.data.attributes.hasOwnProperty('data-align')) {
      +            this.element
      +              .getParent()
      +              .addClass('align-' + this.data.attributes['data-align']);
      +          }
      

      🤔 In #25, this used to be more abstract:

                // Allow entity_embed.editor.css to respond to changes (for example in alignment).
                this.element.setAttributes(this.data.attributes);
      

      It was more abstract for a reason: custom and contrib modules may layer additional behavior, styling or semantics on top of existing HTML elements by adding more data- attributes. For example, data-highlight, data-editor-comments, data-invert-colors, data-mirror , data-geolocation, and so on. That's exactly what FilterAlign does via data-align and FilterCaption via data-caption. This

      The current code hardcodes only behavior for data-align. Yes, I know this plugin has special behavior for data-caption. There's no other way for that one. But data-align was implemented generically, and at least some other data-somethings could achieve the same.

      In my opinion, this is a regression compared to earlier patch iterations. But I won't block the patch over this.

    3. +++ b/core/modules/media/media.routing.yml
      @@ -39,3 +39,11 @@ media.settings:
      +media.filter.preview:
      

      ✅ Added methods: [GET], as I recommended in response to #149.

    4. +++ b/core/modules/media/src/Controller/MediaFilterController.php
      @@ -0,0 +1,111 @@
      +  public function formatUsesMediaEmbedFilter(FilterFormatInterface $filter_format) {
      

      ✅ Nit: This can be static, since it doesn't rely on any service data. Less coupling. Fixed.

    5. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
      @@ -0,0 +1,144 @@
      +      'core/drupal.ajax',
      

      🤔 See my response to #177.

    6. +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
      @@ -0,0 +1,144 @@
      +  public function getCssFiles(Editor $editor) {
      +    $files = [
      +      $this->moduleExtensionList->getPath('media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css',
      +    ];
      +    // Get current version of 'hidden.module.css', taking into account module
      +    // and theme overrides.
      +    $library = $this->libraryDiscovery->getLibraryByName('system', 'base');
      +    foreach (preg_grep('/hidden\.module\.css$/', array_column($library['css'], 'data')) as $file) {
      +      $files[] = $file;
      +      break;
      +    }
      +    return $files;
      +  }
      

      🤔✅ Created an issue for generalizing this, or for at least establishing a pattern: #3072063: CKEditorPluginManager::getCssFiles() and CKEditorPluginCssInterface::getCssFiles() should be library-aware and override-aware. I'd personally prefer to not do this pretty complex thing in this issue, but I won't block this patch over it.

    7. +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
      @@ -263,14 +268,26 @@ public function process($text, $langcode) {
      +          // We don't want to overwrite existing class of the embed (or the
      

      🤓✅ Language nit.

    8. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
      @@ -0,0 +1,849 @@
      +  public function testDrupalMediaJsErrorHandling() {
      +    // Assert that an error thrown from the `media.filter.preview` route is
      +    // handled in the javascript by displaying the expected error message.
      ...
      +    // Now assert that the error doesn't appear when the override to force
      +    // an error is removed.
      

      🔎 Nit: this entire test is about the drupalmedia plugin.
      🔎 Nit: the route does not throw an error. The controller throws it. And only the test controller does that. That just simulates a failed response, due to either server error or network error.
      🔎 Übernit: s/javascript/JavaScript/.
      🔎 Übernit: 80 cols.
      🔎 … more nits.
      ✅Fixed.

    9. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
      @@ -0,0 +1,849 @@
      +      // Remove the `drupalimage` plugin's `DrupalImage` button.
      ...
      +              'items' => [
      +                'Source',
      +                'Bold',
      +                'Italic',
      +                'DrupalLink',
      +                'DrupalUnlink',
      +              ],
      

      👎 See my response to #147.2. Reverted this back to the old pattern to remove the brittleness. ✅

    10. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
      @@ -0,0 +1,849 @@
      +    // Select the CKEditor Widget and click the "link" button.
      +    $drupalmedia = $assert_session->waitForElementVisible('css', 'drupal-media');
      +    $this->assertNotEmpty($drupalmedia);
      +    $drupalmedia->click();
      

      The comment is still the same as in #25, but the test logic is very different.

      This was a problem elsewhere in this test too.

      Improved comments in several places, and also made them consistent with the other comments in this test class.

    11. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
      @@ -0,0 +1,849 @@
      +   * Tests that alignment classes are added to <drupal-media> element.
      

      This is inaccurate, they're not added to <drupal-media> by the CKEditor Widget's JS, but to the drupalmedia CKEditor Widget wrapper.

    12. +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
      @@ -0,0 +1,849 @@
      +   * @return bool
      +   *   Returns TRUE if the button is disabled.
      +   */
      +  protected function assertEditorButtonDisabled($name) {
      +    $button = $this->getEditorButton($name);
      +    $this->assertTrue($button->hasClass('cke_button_disabled'));
      +    $this->assertSame('true', $button->getAttribute('aria-disabled'));
      +  }
      +
      

      ⚠️ These don't actually return anything. Fixed. ✅

    13. +++ b/core/modules/media/tests/src/Kernel/DrupalMediaTest.php
      @@ -0,0 +1,57 @@
      +class DrupalMediaTest extends KernelTestBase {
      

      👏 Nicely done! 🥳

    14. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
      @@ -37,7 +37,10 @@ public function testBasics(array $embed_attributes, $expected_view_mode, array $
      +    $expected_libraries = [
      +      'media/filter.caption',
      +    ];
      +    $this->assertSame($expected_libraries, $result->getAttachments()['library']);
      

      👎 Out-of-scope change. (Plus two more, not quoted.) Reverted. ✅ (I think this was a leftover from a previous patch iteration where more libraries were being attached.)

    15. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
      @@ -258,30 +265,32 @@ public function providerOverridesAltAndTitle() {
      +  public function testMissingEntityIndicator($uuid, array $filter_ids = [], array $additional_attributes = []) {
      

      👎 Data providers should avoid making arguments optional unless there are very strong reasons not to. Tests should be as specific and predictable as possible. Removed defaults. ✅

    16. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
      @@ -339,7 +388,7 @@ public function testRecursionProtection() {
      -  public function testFilterIntegration(array $filter_ids, array $additional_attributes, $verification_selector, $expected_verification_success, array $expected_asset_libraries, $prefix = '', $suffix = '') {
      +  public function testFilterIntegration($verification_selector, $expected_verification_success, array $filter_ids, array $additional_attributes = [], array $expected_asset_libraries = [], $prefix = '', $suffix = '') {
      

      👎⚠️ This is causing a huge pile of change just because the arguments have been re-ordered. I also remarked on this in my review of #179. Reverted those changes. And apparently that means 100% of changes are reverted, there were zero functional changes. ✅

    17. +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTestBase.php
      @@ -174,7 +174,7 @@ protected function createEmbedCode(array $attributes) {
      -   * @see \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterTestBase::createEmbedCode()
      +   * @see \Drupal\Tests\media\Kernel\MediaEmbedFilterTestBase::createEmbedCode()
      

      HAH! Great catch :D

    18. +++ b/core/themes/classy/classy.libraries.yml
      --- /dev/null
      +++ b/core/themes/classy/css/components/media-embed.css
      
      +++ b/core/themes/classy/css/components/media-embed.css
      @@ -0,0 +1,30 @@
      + * Styles for media embeds.
      

      🤔 Shouldn't this be media-embed-error.css? And Styles for media embed errors.?

    19. +++ b/core/themes/stable/stable.info.yml
      @@ -148,7 +148,6 @@ libraries-override:
      -
      

      👎 Out-of-scope change. Reverted. ✅

    WHEW 🤯😴

wim leers’s picture

#213:

wim leers’s picture

StatusFileSize
new11.85 KB
new19.41 KB
new7.37 KB
new6.07 KB
new12.38 KB
new88.17 KB

@phenaproxima and I pair-programmed what we hope to be the last iteration in this impressively long issue 😁

  • #213's @effulgentsia agreed that, in this particular case, we should go back to
  • #213's This calls for a comment in the JavaScript code of the drupalmedia plugin.
  • #214's response to #153.2|3|4. We discussed potentially using JS messages (https://www.drupal.org/node/2930536) for an error occurring during loading the preview. @phenaproxima pointed out that using JS messages would result in the error message being visually divorced from the actual media preview that failed to load. I also realized that if the content author wants to edit information other than the embedded media, the current solution makes is not disruptive. Using JS messages would be disruptive. So we agreed that sticking to the current approach is best 👍
  • #214's response to #154 & #157. We agreed to keep this in this issue rather than move it to a separate issue since we already have consensus and because this issue made us realize we needed to improve this in the first place.
  • #214's response to #177. @phenaproxima agreed with my reasoning, so brought back those dependencies.
  • @phenaproxima is going to open an issue about asset library attaching to the media preview in CKEditor, just in case people do need this in the future. @phenaproxima was convinced by my explanations in #214's responses to #211 and #212.
  • #214's point 2 in the patch review: @phenaproxima and I discussed this in detail. We agreed to keep the .align-* code that exists in the current patch because it has gotten sign-off from the front-end maintainer @lauriii. But we also agreed that we should not make additional use cases impossible. So we settled on restoring the original code (which exposes data- attributes and makes them targetable from CSS) but keeping the .align-*. We created an issue to consider removing .align-*: #3072279: Stop generating align-* classes for preview in DrupalMedia CKEditor plugin.
  • ⚠️ This is the only remaining actual bug and not just a consensus thing. ⚠️@phenaproxima, @webchick and I talked about #208.3 and #214.18. We had to figure out a way to make this Classy-owned CSS actually load. @phenaproxima pointed out that that approach could only work when using a theme hook, which was deferred to a follow-up: #3071713: Make error messages for embedded media themeable. Furthermore, @webchick and I felt that we should follow the precedent set by the FilterCaption's use of the filter/caption asset library, which ensures that a sensible default styling is available in all themes, not just in Classy subthemes. So in this reroll we are adding a media/embed_error asset library.
    Before
    After
  • @phenaproxima and I went over everything else in #214 and agreed everything was okay to keep as-is. Some things could be extracted, but that seems to not be worth it.

Everything except for the last bullet is captured by interdiff-details.txt. The last bullet is captured by interdiff-css.txt. interdiff.txt contains everything.

Status: Needs review » Needs work

The last submitted patch, 216: 2994696-216.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new2.28 KB
new86 KB

Oh, we forgot to remove DrupalMediaTest in #216 (because we descoped the library-related things in #213 in favor of #3072063: CKEditorPluginManager::getCssFiles() and CKEditorPluginCssInterface::getCssFiles() should be library-aware and override-aware).

oknate’s picture

1. There is an extra space before bracket in two places:

diff --git a/core/modules/media/css/filter.media_embed.css b/core/modules/media/css/filter.media_embed.css
index c53ea56d9c..d0dd24898b 100644
--- a/core/modules/media/css/filter.media_embed.css
+++ b/core/modules/media/css/filter.media_embed.css
@@ -23,12 +23,12 @@
   margin-bottom: 1em;
 }
 
-.media-embed-error.align-right  {
+.media-embed-error.align-right {
   margin-left: 1em;
   margin-bottom: 1em;
 }
 
-.media-embed-error.align-center  {
+.media-embed-error.align-center {
   margin-top: 1em;
   margin-bottom: 1em;
 }

2: There's an extra line in this file:

diff --git a/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
index c0764a5ba1..fd0ad5d0ca 100644
--- a/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
+++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css
@@ -63,4 +63,3 @@ drupal-media .caption {
   margin-left: 1em;
   margin-bottom: 1em;
 }
-
wim leers’s picture

StatusFileSize
new430.99 KB
new3.03 KB
new84.42 KB

@phenaproxima pinged me about one thing he noticed while doing a final patch review. He was wondering why we were specifying margins in one CSS file but not another. And then he was wondering why we're even specifying them at all. I have been concerned about that too, but since it'd have been easy to fix in a follow-up, I didn't care too much. But I'm glad he brought it up :)

@phenaproxima and I suspect that this was added to improve the usability of the authoring experience for content with left- or right-aligned embedded media. And that's a noble cause, too! However, align.module.css does not specify this (which is what FilterAlign relies upon). Consequently, embedded aligned images (<img data-align="…">) are displayed without any margins in CKEditor by default. Unless the front end theme adds some (using ckeditor_stylesheets in *.info.yml).
It'd be a big WTF if aligned embedded media items have this margin but aligned images do not. See for yourself:

So let's keep this consistent, then add margins in a follow-up (and then target all CKEditor widgets (by using the selector .cke_widget_wrapper). Then things stay consistent :)

oknate’s picture

StatusFileSize
new621 bytes
new84.44 KB

Fixing the path on the missing media indicator image in core/themes/stable/css/media/filter.media_embed.css.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Nice catch, @oknate -- thanks!

At @effulgentsia's request, I did some light testing to figure out if I could get the _previewNeedsServerSideUpdate() function (in the CKEditor plugin's JavaScript) to ever return false. The answer is no, I couldn't, but I think that is because the only way to make changes right now is to go into source mode (which downcasts the embedded media widget), make the changes, and then back into visual mode (which upcasts the widget by re-instantiating it, causing it to fetch the preview again). This seems correct to me; I'm guessing that once we implement #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library, and changes can be made in the UI, the preview will become substantially smarter.

Beyond that, I think we're done here. Wim and I have thoroughly reviewed the patch, scaled back the scope a bit, removed some things that were questionable, and clarified many decisions. All requested follow-ups have been filed. I think we can land it.

effulgentsia’s picture

Also, I'm adding credit for ckrina for her feedback.

Somehow that didn't stick, so setting that checkbox again.

effulgentsia’s picture

StatusFileSize
new84.7 KB
new6.42 KB

I had to make the following coding standards fixes in order for the patch to pass the pre-commit hook. Leaving at RTBC, but waiting for tests to come back green.

effulgentsia’s picture

StatusFileSize
new84.72 KB
new577 bytes
+++ b/core/themes/stable/css/media/filter.media_embed.css
@@ -6,14 +6,15 @@
-  background-image: url(../../../../modules/media/images/icons/no-thumbnail.png);
+  background-image: url(../images/icons/no-thumbnail.png);

That was an unintended consequence of copy/paste. This patch reverts it.

  • effulgentsia committed f5b124a on 8.8.x
    Issue #2994696 by oknate, Wim Leers, phenaproxima, starshaped, lauriii,...
effulgentsia’s picture

Status: Reviewed & tested by the community » Fixed

Pushed to 8.8.x. Thank you for everyone's great work on this. See you in #2994699: Create a CKEditor plugin to select and embed a media item from the Media Library for the next step!

wim leers’s picture

#226: hah, I was gonna point out exactly that :)

Also: 🥳🥳🥳🥳

wim leers’s picture

#2994699: Create a CKEditor plugin to select and embed a media item from the Media Library has been rerolled — that next step should be much easier to land 😀🤞

oknate’s picture

redacted

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

wim leers’s picture

pameeela’s picture

Issue tags: -8.8.0 release notes

Removed release notes tag, this is already tagged for highlights, which I think is correct.

Please comment if I'm wrong and there is any potential for disruption from this that should go in release notes.

wim leers’s picture

You're correct, I didn't realize release notes required something to be disruptive. Thanks!