Automatic dark mode for embedded comments as well?

Automatic Dark Mode color scheme switching is a fantastic addition, thanks for integrating it!

With (automatic) color scheme switching available, I’m wondering if there might also be some way to switch back and forth between light and dark Discourse modes when embedded as comments in a blog. Specifically, my Ghost blog has a toggle that users can click on to manually switch back and forth between light and dark modes. I know that my blog’s CSS can’t effect the CSS within Discourse’s iframe, but with this new addition might there be another way that the toggle could also switch the Discourse color scheme? You can see an example post with comments at the bottom of this post.

11 Likes

This is potentially doable but a little tricky. At the moment, we haven’t added dark mode auto switching to the embedded comments endpoint. We can add that, and it would work if your Ghost site was auto-switching to dark mode when the browser switches to dark mode. But from what I can tell, your Ghost site uses a dark mode button, which won’t work with the Discourse implementation.

What you can do in your specific case, is toggle a class in the Discourse embedded comments iframe when clicking the button, and then use that class to toggle colors in your embedded stylesheet.

13 Likes

Sounds good. While I am currently putting together several dark mode utilizing (Ghost) themes for colleagues of mine, all of which will share a single Discourse instance, that’s still a bit further down the road.

I unfortunately don’t quite understand what you’re suggesting here. Currently, any elements that I want to utilize dark mode styling within my blog have their CSS doubled up by preceding such items with body.dark. As in

p {
    color: #000;
}
body.dark p {
    color: #FFF;
}

Might you be suggesting that I use that body.dark code to change things in the Discourse iframe? Because I tried inserting the following into Discourse’s Embedded CSS field, which unfortunately had no effect:

.FF2F-discourse p {
    color: #000;
}
body.dark .FF2F-discourse p {
    color: #FFF;
}

That being so, are you instead suggesting that I have some separate JS written so that the toggle will affect a separate change via specific targeting of a class in the iframe? Because like I stated in my previous comment, I didn’t think that external coding could affect CSS within an iframe, hence my confusion. But I’m very much solely an HTML/CSS amateur, so there’s no doubt that you know better than me and/or that I’m misunderstanding something else.

Thanks again for any hints.

6 Likes

Yes, that’s what I am suggesting. Your blog’s stylesheets cannot apply to the iframe, but you can use JS to toggle the dark class in the iframe’s html or body element, and then update your Discourse embedded stylesheet accordingly.

10 Likes

I’ve found two pages explaining how toggling between light and dark modes for Discourse’s embed might be done, but the guy who writes my JS is wondering if the method you’re suggesting entails using postMessage (which the pages I found state) or something else.

Here’s a page explaining how to implement cross-window messaging with postMessage

While this one is actually a tutorial on how to change the CSS within an iframe via postMessage, specifically for switching between light and dark modes:

Might I be on the right track with all this?

8 Likes

My apologies, I forgot to respond here, yes, I think postMessage can work for your use case.

11 Likes

Hi,

Sorry to dig such an old topic, but are there any plans to implement dark mode auto switching to embedded comments endpoint ?

1 Like

We currently don’t have plans to add this feature. Sites can add dark-mode styles to their embedded stylesheet as a workaround (or use the postMessage technique discussed above.)

7 Likes

postMessage need embedded iframe to handle message event.
Where do I add code to an iframe to handle the event?

2 Likes

I have implemented it by using postMessage. The codes and its execution logic are as blow:

Code block ① adds an event listener in embedded Discourse iframe which will post message to my website that contains embedded Discourse iframe once embedded Discourse is loaded.

When my website receives message from embedded Discourse, it will make a validation, as shown in code block ②, and if passed, call the setIframeStyle function to set embedded Discourse.

The setIframeStyle function, just shown in code block ③, pass color mode, “dark” or “light”, to iframe by calling postMessage. In addition, once the dark mode switched, the function could be called to keep embedded Discourse same color mode with my website.

Code block ④ allows embedded Discourse to be able to process the color mode message sent from my website. Here I toggle the color mode by switching class name to the body tag.

Additionally, code block ① and ④ are added by Discourse admin page, as blow:

And custom css classes should be added as blow:

12 Likes

Does anybody know where to edit if we can’t edit “Embedded CSS” (because not official theme)?

2 Likes

You just create a new component and use it’s Embedded tab?

3 Likes

Thanks for the outline above @mikeguo, that is wonderfully explained!

Also, note that you need to be on latest Discourse to accomplish this, the Embedded Header capability was only added a few days ago.

8 Likes

EDIT: That was I need to do Structure of themes and theme components

That seems to be wrong?

const handleMessageListener = (event:MessageEvent<any>)

2 Likes

I still can’t enable this code (!) Could someone please, kindly revise it?

I see that nobody can enable this and almost everyone are using light forced, that’s very bad for our eyes :frowning:

1 Like

I can confirm that the above method still works, but it’d have been nice not to have had to type in code from a screenshot :slight_smile: So, here it is, slightly updated:

Steps

  1. The comments iframe finishes rendering and sends a message to the main browser window, telling it so.
  2. The browser queries its dark/light mode setting and sends back the value into the iframe.
  3. The iframe receives the message, and sets a data-attribute, class, or similar, based on the dark/light mode setting.

Code

  1. Once iframe is loaded, send a notification to the parent window. This needs to be entered on Discourse, under Admin -> Customize -> (select theme) -> Edit CSS/HTML -> Embedded Header.
<script type="text/javascript">
    window.addEventListener("load", (event) => {
        window.parent.postMessage("iframe loaded", "*");
    }, false);
</script>
  1. Handle this incoming trigger in the main window. This code lives in your blog site:
<script type="text/javascript">
  const discourse_url = "https://your.discourse-instance.org";

  // Here, we determine the theme, and send a message to the iframe to let it know what the theme is
  // See below for how we hook up notifyFrameStyle 
  const notifyIFrameOfTheme = () => {
    const iframe = document.getElementById("discourse-embed-frame");
    if (iframe && iframe.contentWindow) {
      iframe.contentWindow.postMessage(
        {
          // Modify the line below to grab dark mode setting, depending on how you store it
          theme: document.documentElement.getAttribute("data-theme")
        },
        discourse_url
      );
    }
  };

  // Call setFrameStyle when we receive the "iframe loaded" message
  const handleMessageListener = (event) => {
    var origin = event.origin;
    if ((origin === discourse_url) && (event.data == "iframe loaded")) {
      notifyIFrameOfTheme();
    }
  };
</script>
  1. In the <script> block from (1), add a listener for the theme message sent by notifyFrameStyle:
    window.addEventListener("message", (event) => {
        const payload = event.data;
        if (payload.theme) {
          // Do something with the theme setting; I set the `data-theme` attribute on the iframe's <html>,
          // but you may want to set a classattribute or similar 
          document.documentElement.setAttribute("data-theme", payload.theme);
        }
    }, false);

Styling

Under Admin -> Customize -> (select theme) -> Edit CSS/HTML -> Embedded CSS, you can now provide CSS for each mode. E.g., you can override the Discourse styling variables:

html[data-theme="dark"] {
  --primary: #ced6dd;
  --primary-low: #48566b;
  --secondary: #14181e;
  --tertiary: #2b7e8d;
}

I hope that helps!

6 Likes

Above copy’n’pasteble code is missing the addition of the event listener in the embedding window:

window.addEventListener("message", handleMessageListener);

And a note: No special CORS settings are not needed for this.

Thanks for catching that! I can’t edit the above post any more, but the window.addEventListener goes at the bottom of the code snippet under (2).

See this real-world example.

4 Likes