Dynamic Favicons for WordPress

Typically, a single favicon is used across a whole domain. But there are times you wanna step it up with different favicons depending on context. A website might change the favicon to match the content being viewed. Or a site might allow users to personalize their theme colors, and those preferences are reflected in the favicon. Maybe you’ve seen favicons that attempt to alert the user of some event.

Multiple favicons can technically be managed by hand — Chris has shown us how he uses two different favicons for development and production. But when you reach the scale of dozens or hundreds of variations, it’s time to dynamically generate them.

This was the situation I encountered on a recent WordPress project for a directory of colleges and universities. (I previously wrote about querying nearby locations for the same project.) When viewing a school’s profile, we wanted the favicon to use a school’s colors rather than our default blue, to give it that extra touch.

With over 200 schools in the directory and climbing, we needed to go dynamic. Fortunately, we already had custom meta fields storing data on each school’s visual identity. This included school colors, which were being used in the stylesheet. We just needed a way to apply these custom meta colors to a dynamic favicon.

In this article, I’ll walk you through our approach and some things to watch out for. You can see the results in action by viewing different schools.

Each favicon is a different color in the tabs based on the school that is selected.

SVG is key

Thanks to improved browser support for SVG favicons, implementing dynamic favicons is much easier than days past. In contrast to PNG (or the antiquated ICO format), SVG relies on markup to define vector shapes. This makes them lightweight, scaleable, and best of all, receptive to all kinds of fun.

The first step is to create your favicon in SVG format. It doesn’t hurt to also run it through an SVG optimizer to get rid of the cruft afterwards. This is what we used in the school directory:

Hooking into WordPress

Next, we want to add the favicon link markup in the HTML head. How to do this is totally up to you. In the case of WordPress, it could be added it to the header template of a child theme or echo’d through a wp_head() action.

function ca_favicon() {
  if ( is_singular( 'school' ) ) {
    $post_id = get_the_ID();
    $color = get_post_meta( $post_id, 'color', true );

    if ( isset( $color ) ) {
      $color = ltrim( $color, '#' ); // remove the hash
      echo '<link rel="icon" href="' . plugins_url( 'images/favicon.php?color=' . $color, __FILE__ ) . '" type="image/svg+xml" sizes="any">';
    }
  }
}
add_filter( 'wp_head' , 'ca_favicon' );

Here we’re checking that the post type is school, and grabbing the school’s color metadata we’ve previously stored using get_post_meta(). If we do have a color, we pass it into favicon.php through the query string.

From PHP to SVG

In a favicon.php file, we start by setting the content type to SVG. Next, we save the color value that’s been passed in, or use the default color if there isn’t one.

Then we echo the large, multiline chunk of SVG markup using PHP’s heredoc syntax (useful for templating). Variables such as $color are expanded when using this syntax.

Finally, we make a couple modifications to the SVG markup. First, classes are assigned to the color-changing elements. Second, a style element is added just inside the SVG element, declaring the appropriate CSS rules and echo-ing the $color.

Instead of a <style> element, we could alternatively replace the default color with $color wherever it appears if it’s not used in too many places.

<?php
header( 'Content-Type: image/svg+xml' );

$color = $_GET[ 'color' ] ?? '065281';
$color = sanitize_hex_color_no_hash( $color );

echo <<<EOL
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
  <style type="text/css">
  <![CDATA[
  .primary {
    fill: #$color;
  }
  .shield {
    stroke: #$color;
  }
  ]]>
  </style>
  <defs>
    <path id="a" d="M0 34L318 0l316 34v417.196a97 97 0 01-14.433 50.909C483.553 722.702 382.697 833 317 833S150.447 722.702 14.433 502.105A97 97 0 010 451.196V34z"/>
  </defs>
  <g fill="none" fill-rule="evenodd">
    <g transform="translate(183 65)">
      <mask id="b" fill="#fff">
        <use xlink:href="#a"/>
      </mask>
      <use fill="#FFF" xlink:href="#a"/>
      <path class="primary" mask="url(#b)" d="M317-37h317v871H317z"/>
      <path class="primary" mask="url(#b)" d="M0 480l317 30 317-30v157l-317-90L0 517z"/>
      <path fill="#FFF" mask="url(#b)" d="M317 510l317-30v37l-317 30z"/>
    </g>
    <g fill-rule="nonzero">
      <path class="primary" d="M358.2 455.2c11.9 0 22.633-.992 32.2-2.975 9.567-1.983 18.375-4.9 26.425-8.75 8.05-3.85 15.458-8.458 22.225-13.825 6.767-5.367 13.3-11.433 19.6-18.2l-34.3-34.65c-9.567 8.867-19.192 15.867-28.875 21-9.683 5.133-21.525 7.7-35.525 7.7-10.5 0-20.125-2.042-28.875-6.125s-16.217-9.625-22.4-16.625-11.025-15.167-14.525-24.5-5.25-19.25-5.25-29.75v-.7c0-10.5 1.75-20.358 5.25-29.575 3.5-9.217 8.4-17.325 14.7-24.325 6.3-7 13.825-12.483 22.575-16.45 8.75-3.967 18.258-5.95 28.525-5.95 12.367 0 23.508 2.45 33.425 7.35 9.917 4.9 19.658 11.667 29.225 20.3l34.3-39.55a144.285 144.285 0 00-18.2-15.4c-6.533-4.667-13.65-8.633-21.35-11.9-7.7-3.267-16.275-5.833-25.725-7.7-9.45-1.867-19.892-2.8-31.325-2.8-18.9 0-36.167 3.325-51.8 9.975-15.633 6.65-29.05 15.75-40.25 27.3s-19.95 24.967-26.25 40.25c-6.3 15.283-9.45 31.675-9.45 49.175v.7c0 17.5 3.15 33.95 9.45 49.35 6.3 15.4 15.05 28.758 26.25 40.075 11.2 11.317 24.5 20.242 39.9 26.775 15.4 6.533 32.083 9.8 50.05 9.8z"/>
      <path fill="#FFF" d="M582.35 451l22.4-54.95h103.6l22.4 54.95h56.35l-105-246.75h-49.7L527.4 451h54.95zM689.1 348.45H624L656.55 269l32.55 79.45z"/>
    </g>
    <path class="shield" stroke-width="30" d="M183 99l318-34 316 34v417.196a97 97 0 01-14.433 50.909C666.553 787.702 565.697 898 500 898S333.447 787.702 197.433 567.105A97 97 0 01183 516.196V99h0z"/>
  </g>
</svg>
EOL;
?>

With that, you’ve got a dynamic favicon working on your site.

Security considerations

Of course, blindly echo-ing URL parameters opens you up to hacks. To mitigate these, we should sanitize all of our inputs.

In this case, we‘re only interested in values that match the 3-digit or 6-digit hex color format. We can include a function like WordPress’s own sanitize_hex_color_no_hash() to ensure only colors are passed in.

function sanitize_hex_color( $color ) {
  if ( '' === $color ) {
    return '';
  }

  // 3 or 6 hex digits, or the empty string.
  if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
    return $color;
  }
}

function sanitize_hex_color_no_hash( $color ) {
  $color = ltrim( $color, '#' );

  if ( '' === $color ) {
    return '';
  }

  return sanitize_hex_color( '#' . $color ) ? $color : null;
}

You’ll want to add your own checks based on the values you want passed in.

Caching for better performance

Browsers cache SVGs, but this benefit is lost for PHP files by default. This means each time the favicon is loaded, your server’s being hit.

To reduce server strain and improve performance, it’s essential that you explicitly cache this file. You can configure your server, set up a page rule through your CDN, or add a cache control header to the very top of favicon.php like so:

header( 'Cache-Control: public, max-age=604800' );  // 604,800 seconds or 1 week

In our tests, with no caching, our 1.5 KB SVG file took about 300 milliseconds to process on the first load, and about 100 milliseconds on subsequent loads. That’s pretty lousy. But with caching, we brought this down to 25 ms from CDN on first load, and 1 ms from browser cache on later loads — as good as a plain old SVG.

Browser support

If we were done there, this wouldn’t be web development. We still have to talk browser support.

As mentioned before, modern browser support for SVG favicons is solid, and fully-supported in current versions of Chrome, Firefox, and Edge.

SVG favicons have arrived… except in Safari.

One caveat is that Firefox requires the attribute type="image/svg+xml" in the favicon declaration for it to work. The other browsers are more forgiving, but it‘s just good practice. You should include sizes="any" while you’re at it.

<link rel="icon" href="path/to/favicon.svg" type="image/svg+xml" sizes="any">

Safari

Safari doesn‘t support SVG favicons as of yet, outside of the mask icon feature intended for pinned tabs. In my experimentation, this was buggy and inconsistent. It didn’t handle complex shapes or colors well, and cached the same icon across the whole domain. Ultimately we decided not to bother and just use a fallback with the default blue fill until Safari improves support.

Fallbacks

As solid as SVG favicon support is, it‘s still not 100%. So be sure to add fallbacks. We can set an additional favicon for when SVG icons aren’t supported with the rel="alternative icon" attribute:

<link rel="icon" href="path/to/favicon.svg" type="image/svg+xml" sizes="any">
<link rel="alternate icon" href="path/to/favicon.png" type="image/png">

To make the site even more bulletproof, you can also drop the eternal favicon.ico in your root.

Going further

We took a relatively simple favicon and swapped one color for another. But taking this dynamic approach opens the door to so much more: modifying multiple colors, other properties like position, entirely different shapes, and animations.

For instance, here’s a demo I’ve dubbed Favicoin. It plots cryptocurrency prices in the favicon as a sparkline.

Implementing dynamic favicons like this isn’t limited to WordPress or PHP; whatever your stack, the same principles apply. Heck, you could even achieve this client-side with data URLs and JavaScript… not that I recommend it for production.

But one thing‘s for sure: we’re bound to see creative applications of SVG favicons in the future. Have you seen or created your own dynamic favicons? Do you have any tips or tricks to share?


The post Dynamic Favicons for WordPress appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

miss rose makeup

please told me the technique that create higher rank of mine website through key words just miss rose makeup is cosmetic website

7+ Best WordPress Accordion Plugins (2024)

Are you looking for the best WordPress accordion plugins?

An accordion is a helpful web design element that allows you to display content in a collapsible tab layout. It’s often used to display frequently asked questions or product details without taking up so much of the page’s space.

In this article, we will share the best WordPress accordion plugins that you can use on your website.

Best WordPress Accordion Plugins

What Is an Accordion on a Website?

An accordion is a list of panels that can hide and show information when you click on each panel. It lets users choose which detail they want to learn more about, while the rest of the tabbed content remains closed.

An example of a FAQ section using an accordion

Many website owners use accordions when they want to cover tons of information while saving some space on their web pages.

You may have seen accordions used as FAQs (Frequently Asked Questions) in online documentation. Users can click on the question to see the answer, and if they don’t want to read it, then they can just close it again. FAQ content has also become more common for WordPress SEO since it can show up in the search results.

WooCommerce product pages also often use accordions to organize a lot of information. Customers can click on a detail they want more explanation for.

By default, WordPress doesn’t have a Gutenberg block for accordions. That’s why many users install a plugin for this purpose.

Let’s take a look at some of the best WordPress accordion plugins that you can use on your WordPress website.

Disclaimer: When choosing the plugins for this showcase, we installed each tool on our test site to explore its pros, cons, and features. This allowed us to recommend only the best WordPress accordion plugins.

1. Heroic FAQs

If you want to use an accordion plugin specifically for frequently asked questions, then check out Heroic FAQs. It lets you easily generate collapsible tabbed content and add it anywhere on your WordPress blog or site.

It comes with a drag-and-drop editor so that you can create questions and answers, group FAQs, and reorder items with ease. There are 5 pre-defined accordion styles and many customization options to match your website design perfectly.

For example, you can add rich content to your accordions, including images, blockquotes, videos, lists, and so on.

Heroic FAQs also offers 15 FAQ icon choices, accordion or toggle style options, CSS3 animations, and more to make your FAQs more personalized.

Pros of Heroic FAQs:

  • Easy-to-customize accordions for FAQ content.
  • User-friendly drag-and-drop editor to build your accordions.
  • Offers rich content elements to be inserted into your accordions so you can make them much more engaging.
  • Has built-in FAQ schema support so that your FAQ content can show up on search engines.

Cons of Heroic FAQs:

  • No free plugin is available.
  • Must be purchased with Heroic KB, where the price starts at $149.50 per year. So, this plugin is kind of like an addon.

Why we chose Heroic FAQs: It is perfect if you are looking for an easy-to-use accordion plugin that is specifically designed for FAQ content. We also recommend it if you want to build a knowledge base, as you have to purchase it with Heroic KB.

2. SeedProd

SeedProd Website and Theme Builder

As the best drag-and-drop WordPress page builder in the market, SeedProd offers various blocks to create a user-friendly site. These include an accordion block, which lets you add text sections that expand and collapse on your pages.

All you have to do is drag and drop the accordion block onto your page and start customizing it. You can create as many text sections as you need and even use a unique dropdown icon from Font Awesome for a personal touch.

Creating an accordion in SeedProd

Feel free to customize the block’s typography, colors, animated effects, and more to make the accordion more interactive.

What’s great about SeedProd is that it makes it easy to make a responsive accordion. You can adjust the block’s spacing for desktop and mobile devices, and even opt to hide the accordion if the page is viewed on a certain device.

Making the SeedProd accordion mobile-friendly

Pros of SeedProd:

  • Easy-to-use drag-and-drop editor for users of all levels.
  • The customization options allow full control over the accordion’s design, from the header and dropdown icon to the text section’s shadows.
  • Provides settings to adjust the accordion’s spacing and visibility on mobile, which is great for improving the user experience.
  • Offers various ready-to-use FAQ section layouts to speed up your process.
  • Allows custom CSS to make your accordion design more personalized.

Cons of SeedProd:

Why we chose SeedProd: While the accordion block is only available in the Pro version, we still think SeedProd is worth recommending. Besides a customizable accordion, you will also get access to a user-friendly page builder that makes creating a unique website easy.

For more details, you can see our complete SeedProd review.

3. Accordions

Accordions is a slightly more advanced accordion plugin, but it’s a pretty powerful one, too.

Unlike the previous plugins, it doesn’t come with a WYSIWYG or drag-and-drop editor. The interface is much simpler, with no live preview, but you can see a lot of settings to add and customize unlimited accordions.

The Accordions plugin interface in WordPress

One of the most notable features of this plugin is its lazy load setting. This delays the loading of the accordion until the user’s screen reaches it on the page. This way, you can maintain a fast website performance.

Additionally, if you get the Pro version, you can create nested accordions. This feature will be handy if you have plenty of information to explain and want to make it more organized.

Pros of Accordions:

  • Has a lazy loading feature to optimize the accordions.
  • Offers an import feature for importing accordions from other plugins.
  • Provides two types of accordions to choose from: the traditional accordion or vertical tabs.
  • Offers integration with Font Awesome icons to customize the icons for expanding or collapsing the accordion.
  • The Pro plan includes advanced features like nested accordions and a built-in search function.

Cons of Accordions:

  • The interface is not the most user-friendly.
  • You can only display the accordion using shortcodes (there’s no built-in block for it).

Why we chose Accordions: Despite its slight lack of user-friendliness, Accordions is a powerful plugin for creating beautiful accordions. Its Pro plan also includes features that you may not find in other plugins, like nested accordions.

4. Thrive Architect

Thrive Architect

Thrive Architect is another great page builder with an accordion/toggle block. It has a drag-and-drop editor, so designing a FAQ or product information section with this plugin is easy.

What makes Thrive Architect unique is you can customize the toggle block’s animation style and speed. This offers you more control over how the accordion behaves.

Editing the toggle block in Thrive Architect

Other than that, you can create columns in the toggle block, which can be beneficial if you have a lot of accordion content. Plus, you can customize the toggle block’s background color, typography, shadows, and so on.

If you want to make the toggle expanded by default to catch visitors’ attention, you can do that too. You don’t have to worry about the space becoming limited due to the expansion, as only the first accordion’s content will be shown.

Pros of Thrive Architect:

  • The toggle block comes with all Thrive Architect and Thrive Suite plans.
  • User-friendly drag-and-drop editor.
  • Plenty of ways to customize how the accordion block looks.
  • Advanced settings to configure the accordion block’s animation to control how it behaves when clicked.
  • Settings to make the toggle visible or invisible on desktop, mobile, and tablet to make the website more responsive.

Cons of Thrive Architect:

  • No free version is available.
  • No auto-save feature, so you have to manually save your work.

Why we chose Thrive Architect: If you are on the hunt for a page builder with a toggle function, then try Thrive Architect. The customization settings to create accordions are comprehensive, and you can easily build a unique web design and attractive landing pages with the plugin.

See our Thrive Architect review for more information.

5. Ultimate Blocks

Ultimate Blocks

Ultimate Blocks is a Gutenberg block plugin with an accordion feature. Using it, you can add a content toggle block right in the block editor rather than in a separate interface.

This way, you can create a FAQ section and easily place it wherever you want on your page or post.

The cool thing about Ultimate Blocks is you can select the heading tag for the accordion title. As a result, you can make your accordion content SEO-friendly and align it with the logical structure of your page or post.

If you upgrade to the Pro version, then you can also add a search bar on top of the accordion so that users can search for answers quickly.

Pros of Ultimate Blocks:

  • Offers responsive design controls to make the accordion hidden on desktop, tablet, or mobile devices.
  • SEO-friendly, with FAQ schema support and customizable heading tags.
  • Easy to use as you can add the content toggle block right in the drag-and-drop Gutenberg block editor.
  • Provides other blocks for presenting information while minimizing space, like tabbed and expandable content blocks.

Cons of Ultimate Blocks:

  • Limited accordion icon options compared to the other plugins on the list.

Why we chose Ultimate Blocks: Most WordPress users are already pretty familiar with the block editor, so Ultimate Blocks is great for adding an accordion right in the editor. The content toggle block is also available in the free plugin version.

To learn more, see our detailed Ultimate Blocks review.

6. Easy Accordion

Easy Accordion

If you are looking for an accordion plugin for your WooCommerce product pages, then check out Easy Accordion. The Pro version includes support for creating detailed FAQ sections for product pages.

If you have the same FAQ for multiple product categories, you can easily implement them with this plugin. As a result, you won’t have to create different FAQs for different pages.

Easy Accordion is also translation-ready, as it is compatible with multilingual plugins like WPML and PolyLang. This is great if you run a multilingual business.

Another great feature is the custom shortcode generator. This lets you easily differentiate your accordion shortcodes for when you need to embed them later.

Pros of Easy Accordion Pro:

  • Supports Gutenberg and most major WordPress page builder plugins.
  • You can create accordions from existing pages, posts, custom post types, and even taxonomies to save time on inserting the accordion content.
  • Multiple accordion layouts, from vertical tabs and multicolumn sections to horizontal toggles.
  • Includes SEO features like schema markup and nofollow links.

Cons of Easy Accordion Pro:

  • The plugin comes with a free version, but most of the features that make it great are locked in the Pro version.

Why we chose Easy Accordion: The built-in support for WooCommerce product pages and translation plugins makes Easy Accordion great for online businesses.

7. Accordion FAQ

WPShopSmart's Accordion plugin

If you are looking for a no-frills accordion plugin with a drag-and-drop builder, then look no further than Accordion FAQ. With it, you can easily create multiple collapsible content sections and move them however you likef from the backend.

The free Accordion FAQ plugin only has one template, but it’s quite user-friendly and versatile. For more design choices, you can upgrade to the Pro version and get 18+ templates.

WPShopSmart's Accordion plugin interface in the admin area

The plugin also offers many ways to customize the accordion. Feel free to use its 30+ animation styles, dozens of Font Awesome Icons, and 500+ Google Fonts to make the accordion more unique.

Pros of Accordion FAQ:

  • User-friendly drag-and-drop accordion editor.
  • Many customization options with unlimited color options and tons of animation styles, icons, and Google Fonts.
  • The pricing for the Pro plans is quite affordable, starting from $9 for 6 months.
  • Based on the Bootstrap framework, which creates a responsive design for the accordions.

Cons of Accordion FAQ:

  • The free plugin version displays multiple ads that can make for a poor user experience.

Why we chose Accordion FAQ: If you are looking for a simple accordion plugin with a drag-and-drop functionality, then Accordion FAQ is a great option, so long as you don’t mind the ads on the plugin page.

Bonus: Shortcodes Ultimate

The Shortcodes Ultimate plugin

Shortcodes Ultimate is a WordPress plugin that comes with a set of shortcodes to add various content elements, including accordions.

Since it uses shortcodes, you can add the accordion shortcode to any part of your WordPress site, from pages and posts to widget-ready areas. You also won’t have to worry about theme compatibility, as it functions smoothly with most modern themes.

The downside is the shortcode and HTML for the accordion are pretty long, and you have to be careful when inserting your content. That’s why we didn’t put this option in the same list as above.

For more details, see our complete Shortcodes Ultimate review.

What Is the Best WordPress Accordion Plugin?

The best WordPress accordion plugin depends on your needs.

If you are looking for an accordion plugin for FAQ content, then the best option is Heroic FAQs. The drag-and-drop editor is easy to use and includes various customization features for creating accordions out of the box.

If you are looking for a page builder with an accordion block, then SeedProd is your best option. SeedProd offers various controls and settings for how the accordion looks and behaves. Plus, your accordion design will look great with other elements on your page.

Finally, for people looking for a free WordPress accordion plugin, consider Accordions. This plugin includes everything needed to create simple accordions or vertical tabbed content. Its lazy loading will also ensure the accordion won’t slow down your site.

FAQs About Accordions in WordPress

Let’s go over some frequently asked questions about adding accordions in WordPress.

How do I add an accordion to WordPress?

The easiest way to add an accordion in WordPress is with a plugin like Heroic FAQs, SeedProd, or Accordions. With these tools, you can insert a ready-to-use accordion into your pages and posts and customize it to your liking.

Which WordPress accordion plugin offers the best customization options?

Heroic FAQs offers the best customization options for creating accordions. It comes with a drag-and-drop editor, pre-defined accordion styles, 15 FAQ icon choices, CSS3 animations, toggle style options, and more.

SeedProd also offers comprehensive customization options to modify how an accordion looks and behaves. Besides changing the color and typography, you can adjust the accordion’s size specifically for mobile devices.

Which WordPress accordion plugin is best for creating FAQ sections?

Heroic FAQs is an excellent WordPress accordion plugin for creating FAQ sections. With this plugin, you can create accordions using various styles and insert different rich content to make your information easier to understand.

Best Guides for Using Accordions in WordPress

We hope this article helped you find the best WordPress accordion plugins. You may also want to see our picks of the best email marketing services, and our expert list of the must-have WordPress plugins for business websites.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post 7+ Best WordPress Accordion Plugins (2024) first appeared on WPBeginner.

Adding A Commenting System To A WYSIWYG Editor

In recent years, we’ve seen Collaboration penetrate a lot of digital workflows and use-cases across many professions. Just within the Design and Software Engineering community, we see designers collaborate on design artifacts using tools like Figma, teams doing Sprint and Project Planning using tools like Mural and interviews being conducted using CoderPad. All these tools are constantly aiming to bridge the gap between an online and a physical world experience of executing these workflows and making the collaboration experience as rich and seamless as possible.

For the majority of the Collaboration Tools like these, the ability to share opinions with one another and have discussions about the same content is a must-have. A Commenting System that enables collaborators to annotate parts of a document and have conversations about them is at the heart of this concept. Along with building one for text in a WYSIWYG Editor, the article tries to engage the readers into how we try to weigh the pros and cons and attempt to find a balance between application complexity and user experience when it comes to building features for WYSIWYG Editors or Word Processors in general.

Representing Comments In Document Structure

In order to find a way to represent comments in a rich text document’s data structure, let’s look at a few scenarios under which comments could be created inside an editor.

  • Comments created over text that has no styles on it (basic scenario);
  • Comments created over text that may be bold/italic/underlined, and so on;
  • Comments that overlap each other in some way (partial overlap where two comments share only a few words or fully-contained where one comment’s text is fully contained within text of another comment);
  • Comments created over text inside a link (special because links are nodes themselves in our document structure);
  • Comments that span multiple paragraphs (special because paragraphs are nodes in our document structure and comments are applied to text nodes which are paragraph’s children).

Looking at the above use-cases, it seems like comments in the way they can come up in a rich text document are very similar to character styles (bold, italics etc). They can overlap with each other, go over text in other types of nodes like links and even span multiple parent nodes like paragraphs.

For this reason, we use the same method to represent comments as we do for character styles, i.e. “Marks” (as they are so called in SlateJS terminology). Marks are just regular properties on nodes — speciality being that Slate’s API around marks (Editor.addMark and Editor.removeMark) handles changing of the node hierarchy as multiple marks get applied to the same range of text. This is extremely useful to us as we deal with a lot of different combinations of overlapping comments.

Comment Threads As Marks

Whenever a user selects a range of text and tries to insert a comment, technically, they’re starting a new comment thread for that text range. Because we would allow them to insert a comment and later replies to that comment, we treat this event as a new comment thread insertion in the document.

The way we represent comment threads as marks is that each comment thread is represented by a mark named as commentThread_threadID where threadID is a unique ID we assign to each comment thread. So, if the same range of text has two comment threads over it, it would have two properties set to the truecommentThread_thread1 and commentThread_thread2. This is where comment threads are very similar to character styles since if the same text was bold and italic, it would have both the properties set to truebold and italic.

Before we dive into actually setting this structure up, it’s worth looking at how the text nodes change as comment threads get applied to them. The way this works (as it does with any mark) is that when a mark property is being set on the selected text, Slate’s Editor.addMark API would split the text node(s) if needed such that in the resulting structure, text nodes are set up in a way that each text node has the exact same value of the mark.

To understand this better, take a look at the following three examples that show the before-and-after state of the text nodes once a comment thread is inserted on the selected text:

Highlighting Commented Text

Now that we know how we are going to represent comments in the document structure, let’s go ahead and add a few to the example document from the first article and configure the editor to actually show them as highlighted. Since we will have a lot of utility functions to deal with comments in this article, we create a EditorCommentUtils module that will house all these utils. To start with, we create a function that creates a mark for a given comment thread ID. We then use that to insert a few comment threads in our ExampleDocument.

# src/utils/EditorCommentUtils.js

const COMMENT_THREAD_PREFIX = "commentThread_";

export function getMarkForCommentThreadID(threadID) {
  return `${COMMENT_THREAD_PREFIX}${threadID}`;
}

Below image underlines in red the ranges of text that we have as example comment threads added in the next code snippet. Note that the text ‘Richard McClintock’ has two comment threads that overlap each other. Specifically, this is a case of one comment thread being fully contained inside another.

# src/utils/ExampleDocument.js
import { getMarkForCommentThreadID } from "../utils/EditorCommentUtils";
import { v4 as uuid } from "uuid";

const exampleOverlappingCommentThreadID = uuid();

const ExampleDocument = [
   ...
   {
        text: "Lorem ipsum",
        [getMarkForCommentThreadID(uuid())]: true,
   },
   ...
   {
        text: "Richard McClintock",
        // note the two comment threads here.
        [getMarkForCommentThreadID(uuid())]: true,
        [getMarkForCommentThreadID(exampleOverlappingCommentThreadID)]: true,
   },
   {
        text: ", a Latin scholar",
        [getMarkForCommentThreadID(exampleOverlappingCommentThreadID)]: true,
   },
   ...
];

We focus on the UI side of things of a Commenting System in this article so we assign them IDs in the example document directly using the npm package uuid. Very likely that in a production version of an editor, these IDs are created by a backend service.

We now focus on tweaking the editor to show these text nodes as highlighted. In order to do that, when rendering text nodes, we need a way to tell if it has comment threads on it. We add a util getCommentThreadsOnTextNode for that. We build on the StyledText component that we created in the first article to handle the case where it may be trying to render a text node with comments on. Since we have some more functionality coming that would be added to commented text nodes later, we create a component CommentedText that renders the commented text. StyledText will check if the text node it’s trying to render has any comments on it. If it does, it renders CommentedText. It uses a util getCommentThreadsOnTextNode to deduce that.

# src/utils/EditorCommentUtils.js

export function getCommentThreadsOnTextNode(textNode) {
  return new Set(
     // Because marks are just properties on nodes,
    // we can simply use Object.keys() here.
    Object.keys(textNode)
      .filter(isCommentThreadIDMark)
      .map(getCommentThreadIDFromMark)
  );
}

export function getCommentThreadIDFromMark(mark) {
  if (!isCommentThreadIDMark(mark)) {
    throw new Error("Expected mark to be of a comment thread");
  }
  return mark.replace(COMMENT_THREAD_PREFIX, "");
}

function isCommentThreadIDMark(mayBeCommentThread) {
  return mayBeCommentThread.indexOf(COMMENT_THREAD_PREFIX) === 0;
}

The first article built a component StyledText that renders text nodes (handling character styles and so on). We extend that component to use the above util and render a CommentedText component if the node has comments on it.

# src/components/StyledText.js

import { getCommentThreadsOnTextNode } from "../utils/EditorCommentUtils";

export default function StyledText({ attributes, children, leaf }) {
  ...

  const commentThreads = getCommentThreadsOnTextNode(leaf);

  if (commentThreads.size > 0) {
    return (
      <CommentedText
      {...attributes}
     // We use commentThreads and textNode props later in the article.
      commentThreads={commentThreads}
      textNode={leaf}
      >
        {children}
      </CommentedText>
    );
  }

  return <span {...attributes}>{children}</span>;
}

Below is the implementation of CommentedText that renders the text node and attaches the CSS that shows it as highlighted.

# src/components/CommentedText.js

import "./CommentedText.css";

import classNames from "classnames";

export default function CommentedText(props) {
  const { commentThreads, ...otherProps } = props;
  return (
    <span
      {...otherProps}
      className={classNames({
        comment: true,
      })}
    >
      {props.children}
    </span>
  );
}

# src/components/CommentedText.css

.comment {
  background-color: #feeab5;
}

With all of the above code coming together, we now see text nodes with comment threads highlighted in the editor.

Note: The users currently cannot tell if certain text has overlapping comments on it. The entire highlighted text range looks like a single comment thread. We address that later in the article where we introduce the concept of active comment thread which lets users select a specific comment thread and be able to see its range in the editor.

UI Storage For Comments

Before we add the functionality that enables a user to insert new comments, we first setup a UI state to hold our comment threads. In this article, we use RecoilJS as our state management library to store comment threads, comments contained inside the threads and other metadata like creation time, status, comment author etc. Let’s add Recoil to our application:

> yarn add recoil

We use Recoil atoms to store these two data structures. If you’re not familiar with Recoil, atoms are what hold the application state. For different pieces of application state, you’d usually want to set up different atoms. Atom Family is a collection of atoms — it can be thought to be a Map from a unique key identifying the atom to the atoms themselves. It’s worth going through core concepts of Recoil at this point and familiarizing ourselves with them.

For our use case, we store comment threads as an Atom family and then wrap our application in a RecoilRoot component. RecoilRoot is applied to provide the context in which the atom values are going to be used. We create a separate module CommentState that holds our Recoil atom definitions as we add more atom definitions later in the article.

# src/utils/CommentState.js

import { atom, atomFamily } from "recoil";

export const commentThreadsState = atomFamily({
  key: "commentThreads",
  default: [],
});

export const commentThreadIDsState = atom({
  key: "commentThreadIDs",
  default: new Set([]),
});

Worth calling out few things about these atom definitions:

  • Each atom/atom family is uniquely identified by a key and can be set up with a default value.
  • As we build further in this article, we are going to need a way to iterate over all the comment threads which would basically mean needing a way to iterate over commentThreadsState atom family. At the time of writing this article, the way to do that with Recoil is to set up another atom that holds all the IDs of the atom family. We do that with commentThreadIDsState above. Both these atoms would have to be kept in sync whenever we add/delete comment threads.

We add a RecoilRoot wrapper in our root App component so we can use these atoms later. Recoil’s documentation also provides a helpful Debugger component that we take as it is and drop into our editor. This component will leave console.debug logs to our Dev console as Recoil atoms are updated in real-time.

# src/components/App.js

import { RecoilRoot } from "recoil";

export default function App() {
  ...

  return (
    <RecoilRoot>
      >
         ...
        <Editor document={document} onChange={updateDocument} />

    </RecoilRoot>
  );
}
# src/components/Editor.js

export default function Editor({ ... }): JSX.Element {
  .....

  return (
    <>
      <Slate>
         .....
      </Slate>
      <DebugObserver />
   </>
);

function DebugObserver(): React.Node {
   // see API link above for implementation.
}

We also need to need to add code that initializes our atoms with the comment threads that already exist on the document (the ones we added to our example document in the previous section, for instance). We do that at a later point when we build the Comments Sidebar that needs to read all the comment threads in a document.

At this point, we load our application, make sure there are no errors pointing to our Recoil setup and move forward.

Adding New Comments

In this section, we add a button to the toolbar that lets the user add comments (viz. create a new comment thread) for the selected text range. When the user selects a text range and clicks on this button, we need to do the below:

  1. Assign a unique ID to the new comment thread being inserted.
  2. Add a new mark to Slate document structure with the ID so the user sees that text highlighted.
  3. Add the new comment thread to Recoil atoms we created in the previous section.

Let’s add a util function to EditorCommentUtils that does #1 and #2.

# src/utils/EditorCommentUtils.js

import { Editor } from "slate";
import { v4 as uuidv4 } from "uuid";

export function insertCommentThread(editor, addCommentThreadToState) {
    const threadID = uuidv4();
    const newCommentThread = {
        // comments as added would be appended to the thread here.
        comments: [],
        creationTime: new Date(),
        // Newly created comment threads are OPEN. We deal with statuses
        // later in the article.
        status: "open",
    };
    addCommentThreadToState(threadID, newCommentThread);
    Editor.addMark(editor, getMarkForCommentThreadID(threadID), true);
    return threadID;
}

By using the concept of marks to store each comment thread as its own mark, we’re able to simply use the Editor.addMark API to add a new comment thread on the text range selected. This call alone handles all the different cases of adding comments — some of which we described in the earlier section — partially overlapping comments, comments inside/overlapping links, comments over bold/italic text, comments spanning paragraphs and so on. This API call adjusts the node hierarchy to create as many new text nodes as needed to handle these cases.

addCommentThreadToState is a callback function that handles step #3 — adding the new comment thread to Recoil atom . We implement that next as a custom callback hook so that it’s re-usable. This callback needs to add the new comment thread to both the atoms — commentThreadsState and commentThreadIDsState. To be able to do this, we use the useRecoilCallback hook. This hook can be used to construct a callback which gets a few things that can be used to read/set atom data. The one we’re interested in right now is the set function which can be used to update an atom value as set(atom, newValueOrUpdaterFunction).

# src/hooks/useAddCommentThreadToState.js

import {
  commentThreadIDsState,
  commentThreadsState,
} from "../utils/CommentState";

import { useRecoilCallback } from "recoil";

export default function useAddCommentThreadToState() {
  return useRecoilCallback(
    ({ set }) => (id, threadData) => {
      set(commentThreadIDsState, (ids) => new Set([...Array.from(ids), id]));
      set(commentThreadsState(id), threadData);
    },
    []
  );
}

The first call to set adds the new ID to the existing set of comment thread IDs and returns the new Set(which becomes the new value of the atom).

In the second call, we get the atom for the ID from the atom family — commentThreadsState as commentThreadsState(id) and then set the threadData to be its value. atomFamilyName(atomID) is how Recoil lets us access an atom from its atom family using the unique key. Loosely speaking, we could say that if commentThreadsState was a javascript Map, this call is basically — commentThreadsState.set(id, threadData).

Now that we have all this code setup to handle insertion of a new comment thread to the document and Recoil atoms, lets add a button to our toolbar and wire it up with the call to these functions.

# src/components/Toolbar.js

import { insertCommentThread } from "../utils/EditorCommentUtils";
import useAddCommentThreadToState from "../hooks/useAddCommentThreadToState";

export default function Toolbar({ selection, previousSelection }) {
  const editor = useEditor();
  ...

  const addCommentThread = useAddCommentThreadToState();

  const onInsertComment = useCallback(() => {
    const newCommentThreadID = insertCommentThread(editor, addCommentThread);
  }, [editor, addCommentThread]);

return (
    <div className="toolbar">
       ...
      <ToolBarButton
        isActive={false}
        label={<i className={bi ${getIconForButton("comment")}} />}
        onMouseDown={onInsertComment}
      />
    </div>
  );
}

Note: We use onMouseDown and not onClick which would have made the editor lose focus and selection to become null. We’ve discussed that in a little more detail in the link insertion section of the first article.

In the below example, we see the insertion in action for a simple comment thread and an overlapping comment thread with links. Notice how we get updates from Recoil Debugger confirming our state is getting updated correctly. We also verify that new text nodes are created as threads are being added to the document.

In the above example, the user inserts the following comment threads in that order:

  1. Comment Thread #1 over character ‘B’ (length = 1).
  2. Comment Thread #2 over ‘AB’ (length = 2).
  3. Comment Thread #3 over ‘BC’ (length = 2).

At the end of these insertions, because of the way Slate splits the text nodes with marks, we will have three text nodes — one for each character. Now, if the user clicks on ‘B’, going by the shortest length rule, we select thread #1 as it is the shortest of the three in length. If we don’t do that, we wouldn’t have a way to select Comment Thread #1 ever since it is only one-character in length and also a part of two other threads.

Although this rule makes it easy to surface shorter-length comment threads, we could run into situations where longer comment threads become inaccessible since all the characters contained in them are part of some other shorter comment thread. Let’s look at an example for that.

Let’s assume we have 100 characters (say, character ‘A’ typed 100 times that is) and the user inserts comment threads in the following order:

  1. Comment Thread # 1 of range 20,80
  2. Comment Thread # 2 of range 0,50
  3. Comment Thread # 3 of range 51,100

As you can see in the above example, if we follow the rule we just described here, clicking on any character between #20 and #80, would always select threads #2 or #3 since they are shorter than #1 and hence #1 would not be selectable. Another scenario where this rule can leave us undecided as to which comment thread to select is when there are more than one comment threads of the same shortest length on a text node.

For such combination of overlapping comments and many other such combinations that one could think of where following this rule makes a certain comment thread inaccessible by clicking on text, we build a Comments Sidebar later in this article which gives user a view of all the comment threads present in the document so they can click on those threads in the sidebar and activate them in the editor to see the range of the comment. We still would want to have this rule and implement it as it should cover a lot of overlap scenarios except for the less-likely examples we cited above. We put in all this effort around this rule primarily because seeing highlighted text in the editor and clicking on it to comment is a more intuitive way of accessing a comment on text than merely using a list of comments in the sidebar.

Insertion Rule

The rule is:

“If the text user has selected and is trying to comment on is already fully covered by comment thread(s), don’t allow that insertion.”

This is so because if we did allow this insertion, each character in that range would end up having at least two comment threads (one existing and another the new one we just allowed) making it difficult for us to determine which one to select when the user clicks on that character later.

Looking at this rule, one might wonder why we need it in the first place if we already have the Shortest Comment Range Rule that allows us to select the smallest text range. Why not allow all combinations of overlaps if we can use the first rule to deduce the right comment thread to show? As some of the examples we’ve discussed earlier, the first rule works for a lot of scenarios but not all of them. With the Insertion Rule, we try to minimize the number of scenarios where the first rule cannot help us and we have to fallback on the Sidebar as the only way for the user to access that comment thread. Insertion Rule also prevents exact-overlaps of comment threads. This rule is commonly implemented by a lot of popular editors.

Below is an example where if this rule didn’t exist, we would allow the Comment Thread #3 and then as a result of the first rule, #3 would not be accessible since it would become the longest in length.

In this example, let’s assume we don’t wait for intersection to become 0 and just stop when we reach the edge of a comment thread. Now, if the user clicked on #2 and we start traversal in reverse direction, we’d stop at the start of text node #2 itself since that’s the start of the comment thread A. As a result, we might not compute the comment thread lengths correctly for A & B. With the implementation above traversing the farthest edges (text nodes 1,2, and 3), we should get B as the shortest comment thread as expected.

To see the implementation visually, below is a walkthrough with a slideshow of the iterations. We have two comment threads A and B that overlap each other over text node #3 and the user clicks on the overlapping text node #3.

Now that we have all the code in to make selection of comment threads work, let’s see it in action. To test our traversal code well, we test some straightforward cases of overlap and some edge cases like:

  • Clicking on a commented text node at the start/end of the editor.
  • Clicking on a commented text node with comment threads spanning multiple paragraphs.
  • Clicking on a commented text node right before an image node.
  • Clicking on a commented text node overlapping links.

Now that our state is correctly initialized, we can start implementing the sidebar. All our comment threads in the UI are stored in the Recoil atom family — commentThreadsState. As highlighted earlier, the way we iterate through all the items in a Recoil atom family is by tracking the atom keys/ids in another atom. We’ve been doing that with commentThreadIDsState. Let’s add the CommentSidebar component that iterates through the set of ids in this atom and renders a CommentThread component for each.

# src/components/CommentsSidebar.js

import "./CommentSidebar.css";

import {commentThreadIDsState,} from "../utils/CommentState";
import { useRecoilValue } from "recoil";

export default function CommentsSidebar(params) {
  const allCommentThreadIDs = useRecoilValue(commentThreadIDsState);

  return (
    <Card className={"comments-sidebar"}>
      <Card.Header>Comments</Card.Header>
      <Card.Body>
        {Array.from(allCommentThreadIDs).map((id) => (
          <Row key={id}>
            <Col>
              <CommentThread id={id} />
            </Col>
          </Row>
        ))}
      </Card.Body>
    </Card>
  );
}

Now, we implement the CommentThread component that listens to the Recoil atom in the family corresponding to the comment thread it is rendering. This way, as the user adds more comments on the thread in the editor or changes any other metadata, we can update the sidebar to reflect that.

As the sidebar could grow to be really big for a document with a lot of comments, we hide all comments but the first one when we render the sidebar. The user can use the ‘Show/Hide Replies’ button to show/hide the entire thread of comments.

# src/components/CommentSidebar.js

function CommentThread({ id }) {
  const { comments } = useRecoilValue(commentThreadsState(id));

  const [shouldShowReplies, setShouldShowReplies] = useState(false);
  const onBtnClick = useCallback(() => {
    setShouldShowReplies(!shouldShowReplies);
  }, [shouldShowReplies, setShouldShowReplies]);

  if (comments.length === 0) {
    return null;
  }

  const [firstComment, ...otherComments] = comments;
  return (
    <Card
      body={true}
      className={classNames({
        "comment-thread-container": true,
      })}
    >
      <CommentRow comment={firstComment} showConnector={false} />
      {shouldShowReplies
        ? otherComments.map((comment, index) => (
            <CommentRow key={comment-${index}} comment={comment} showConnector={true} />
          ))
        : null}
      {comments.length > 1 ? (
        <Button
          className={"show-replies-btn"}
          size="sm"
          variant="outline-primary"
          onClick={onBtnClick}
        >
          {shouldShowReplies ? "Hide Replies" : "Show Replies"}
        </Button>
      ) : null}
    </Card>
  );
}

We’ve reused the CommentRow component from the popover although we added a design treatment using showConnector prop that basically makes all the comments look connected with a thread in the sidebar.

Now, we render the CommentSidebar in the Editor and verify that it shows all the threads we have in the document and correctly updates as we add new threads or new comments to existing threads.

# src/components/Editor.js

return (
    <>
      <Slate ... >
       .....
        <div className={"sidebar-wrapper"}>
          <CommentsSidebar />
            </div>
      </Slate>
    </>
);

We now move on to implementing a popular Comments Sidebar interaction found in editors:

Clicking on a comment thread in the sidebar should select/activate that comment thread. We also add a differential design treatment to highlight a comment thread in the sidebar if it’s active in the editor. To be able to do so, we use the Recoil atom — activeCommentThreadIDAtom. Let’s update the CommentThread component to support this.

# src/components/CommentsSidebar.js

function CommentThread({ id }) {

const [activeCommentThreadID, setActiveCommentThreadID] = useRecoilState(
    activeCommentThreadIDAtom
  );

const onClick = useCallback(() => {
setActiveCommentThreadID(id); }, [id, setActiveCommentThreadID]); ... return ( <Card body={true} className={classNames({ "comment-thread-container": true, "is-active": activeCommentThreadID === id,
})} onClick={onClick} > .... </Card> );

If we look closely, we have a bug in our implementation of sync-ing the active comment thread with the sidebar. As we click on different comment threads in the sidebar, the correct comment thread is indeed highlighted in the editor. However, the Comment Popover doesn’t actually move to the changed active comment thread. It stays where it was first rendered. If we look at the implementation of the Comment Popover, it renders itself against the first text node in the editor’s selection. At that point in the implementation, the only way to select a comment thread was to click on a text node so we could conveniently rely on the editor's selection since it was updated by Slate as a result of the click event. In the above onClick event, we don’t update the selection but merely update the Recoil atom value causing Slate’s selection to remain unchanged and hence the Comment Popover doesn’t move.

A solution to this problem is to update the editor’s selection along with updating the Recoil atom when the user clicks on the comment thread in the sidebar. The steps do this are:

  1. Find all text nodes that have this comment thread on them that we are going to set as the new active thread.
  2. Sort these text nodes in the order in which they appear in the document (We use Slate’s Path.compare API for this).
  3. Compute a selection range that spans from the start of the first text node to the end of the last text node.
  4. Set the selection range to be the editor’s new selection (using Slate’s Transforms.select API).

If we just wanted to fix the bug, we could just find the first text node in Step #1 that has the comment thread and set that to be the editor’s selection. However, it feels like a cleaner approach to select the entire comment range as we really are selecting the comment thread.

Let’s update the onClick callback implementation to include the steps above.

const onClick = useCallback(() => {

    const textNodesWithThread = Editor.nodes(editor, {
      at: [],
      mode: "lowest",
      match: (n) => Text.isText(n) && getCommentThreadsOnTextNode(n).has(id),
    });

    let textNodeEntry = textNodesWithThread.next().value;
    const allTextNodePaths = [];

    while (textNodeEntry != null) {
      allTextNodePaths.push(textNodeEntry[1]);
      textNodeEntry = textNodesWithThread.next().value;
    }

    // sort the text nodes
    allTextNodePaths.sort((p1, p2) => Path.compare(p1, p2));

    // set the selection on the editor
    Transforms.select(editor, {
      anchor: Editor.point(editor, allTextNodePaths[0], { edge: "start" }),
      focus: Editor.point(
        editor,
        allTextNodePaths[allTextNodePaths.length - 1],
        { edge: "end" }
      ),
    });

   // Update the Recoil atom value.
    setActiveCommentThreadID(id);
  }, [editor, id, setActiveCommentThreadID]);

Note: allTextNodePaths contains the path to all the text nodes. We use the Editor.point API to get the start and end points at that path. The first article goes through Slate’s Location concepts. They’re also well-documented on Slate’s documentation.

Let’s verify that this implementation does fix the bug and the Comment Popover moves to the active comment thread correctly. This time, we also test with a case of overlapping threads to make sure it doesn’t break there.

With the bug fix, we’ve enabled another sidebar interaction that we haven’t discussed yet. If we have a really long document and the user clicks on a comment thread in the sidebar that’s outside the viewport, we’d want to scroll to that part of the document so the user can focus on the comment thread in the editor. By setting the selection above using Slate’s API, we get that for free. Let’s see it in action below.

With that, we wrap our implementation of the sidebar. Towards the end of the article, we list out some nice feature additions and enhancements we can do to the Comments Sidebar that help elevate the Commenting and Review experience on the editor.

Resolving And Re-Opening Comments

In this section, we focus on enabling users to mark comment threads as ‘Resolved’ or be able to re-open them for discussion if needed. From an implementation detail perspective, this is the status metadata on a comment thread that we change as the user performs this action. From a user’s perspective, this is a very useful feature as it gives them a way to affirm that the discussion about something on the document has concluded or needs to be re-opened because there are some updates/new perspectives, and so on.

To enable toggling the status, we add a button to the CommentPopover that allows the user to toggle between the two statuses: open and resolved.

# src/components/CommentThreadPopover.js

export default function CommentThreadPopover({
  editorOffsets,
  selection,
  threadID,
}) {
  …
  const [threadData, setCommentThreadData] = useRecoilState(
    commentThreadsState(threadID)
  );

  ...

  const onToggleStatus = useCallback(() => {
    const currentStatus = threadData.status;
    setCommentThreadData((threadData) => ({
      ...threadData,
      status: currentStatus === "open" ? "resolved" : "open",
    }));
  }, [setCommentThreadData, threadData.status]);

  return (
    <NodePopover
      ...
      header={
        <Header
          status={threadData.status}
          shouldAllowStatusChange={threadData.comments.length > 0}
          onToggleStatus={onToggleStatus}
        />
      }
    >
      <div className={"comment-list"}>
          ...
      </div>
    </NodePopover>
  );
}

function Header({ onToggleStatus, shouldAllowStatusChange, status }) {
  return (
    <div className={"comment-thread-popover-header"}>
      {shouldAllowStatusChange && status != null ? (
        <Button size="sm" variant="primary" onClick={onToggleStatus}>
          {status === "open" ? "Resolve" : "Re-Open"}
        </Button>
      ) : null}
    </div>
  );
}

Before we test this, let’s also give the Comments Sidebar a differential design treatment for resolved comments so that the user can easily detect which comment threads are un-resolved or open and focus on those if they want to.

# src/components/CommentsSidebar.js

function CommentThread({ id }) {
  ...
  const { comments, status } = useRecoilValue(commentThreadsState(id));

 ...
  return (
    <Card
      body={true}
      className={classNames({
        "comment-thread-container": true,
        "is-resolved": status === "resolved",
        "is-active": activeCommentThreadID === id,
      })}
      onClick={onClick}
    >
       ...
</Card> ); }

Conclusion

In this article, we built the core UI infrastructure for a Commenting System on a Rich Text Editor. The set of functionalities we add here act as a foundation to build a richer Collaboration Experience on an editor where collaborators could annotate parts of the document and have conversations about them. Adding a Comments Sidebar gives us a space to have more conversational or review-based functionalities to be enabled on the product.

Along those lines, here are some of features that a Rich Text Editor could consider adding on top of what we built in this article:

  • Support for @ mentions so collaborators could tag one another in comments;
  • Support for media types like images and videos to be added to comment threads;
  • Suggestion Mode at the document level that allows reviewers to make edits to the document that appear as suggestions for changes. One could refer to this feature in Google Docs or Change Tracking in Microsoft Word as examples;
  • Enhancements to the sidebar to search conversations by keyword, filter threads by status or comment author(s), and so on.

Hi everyone, I’m blackroyal321

The Bespoke Group of Entrepreneurs
The Bespoke Group of Entrepreneurs (Bespoke-G-Ent) is a community of like-minded people that interact over startups, networking and trends. Our products are there to serve the needs that an every day entrepreneur may incur. Be it networking, style, productivity or health. We also have an eco-friendly selection.

How is Orchestration Fixing Big Data Management Challenges For Digital Enterprises?

If global companies manage their data efficiently, they would never have to lock horns with deploying data science strategies. It does sound simple until you know that ‘efficiently’ is an ocean of complexities and beyond the expertise of most businesses. Capturing and orchestrating data is a rough path but a necessity to undo data silos and provide immediate results to on-demand search queries. And that is why we need data orchestration that is purposefully meant to enable quick access and yet protect it from widespread fragmentation.

How Does Big Data Orchestration Work? 

Orchestration tools are a middleware layer between the data warehouse (source points) and the business applications such as CRM, BI Analytics, etc. Through pre-built connectors and API adapters, these tools perform rapid integration of all source points with the main silos. It simplifies data monitoring from a single point while enabling the teams to perform seamless fetching. All of this is achieved without having to develop custom scripts or devoting additional resources.

Happy 18th Birthday, WordPress

WordPress is celebrating 18 years today since the first release of the software to the general public. That release post, titled, “WordPress Now Available,” kicked off an exciting era in the history of the blogosphere where WordPress emerged as an unofficial successor to the abandoned b2/cafelog software. Reading the comments on the first release, you can feel the energy of that time when loyal b2 users were thrilled to have a smooth migration to a new blogging engine that would be maintained.

One line in that post may have had even more impact on WordPress’ trajectory than the features contained in the first release:

WordPress is available completely free of charge under the GPL license.

The license it inherited from b2 empowered the WordPress community to make it the powerful CMS that it is today. WordPress now underpins a multi-billion dollar economy of creators, publishers, and merchants who can build just about anything with the world of GPL-licensed extensions available. WordPress’ vibrant ecosystem has grown to include more than 58,000 free plugins to extend core, and thousands of commercial plugins and services, including hosting companies that cater specifically to its users.

WordPress was instrumental in making publishing software accessible to those with no coding experience, and it is now doing the same for e-commerce, facilitating billions of dollars in sales for businesses and independent stores. The pandemic created unprecedented opportunities for WordPress-powered stores to succeed as traditional brick-and-mortar businesses were forced to close their doors.

Despite weathering some profound, once-in-a-lifetime challenges over the past year, WordPress has continued to grow at a phenomenal rate. In February, the CMS passed 40% market share of all websites, up from 35.4% in January 2020, as measured by W3Techs. WordPress’ project lead and co-founder Matt Mullenweg noted this milestone in a post on his blog, marking the 18th anniversary:

Who could have imagined that our nights and weekends hacking on blogging software, a fork of b2/cafelog, could turn into something powering over 40% of the web? Or that nearly twenty years in, it would be getting better faster than it ever has been?

WordPress.org is celebrating with a new history timeline that logs 40 major milestones along the path to 40% market share. Scrolling through it reads like a story, highlighting some of the most salient moments in WordPress’ history, like the launch of the plugin repository, major interface improvements, and the first WordCamp. There are some interesting notes about how WordPress was able to outpace its contemporaries in the early days, doubling its downloads after Moveable Type 3.0 introduced licensing restrictions. The software had several turns of fortune along the way that have bolstered its market dominance.

For many who have contributed to WordPress’ success, it’s the smaller points that don’t make this official timeline which have been the glue for this community. Friendships are forged in working together and celebrating small wins but also in weathering the friction, conflict, and human failures that come part and parcel with working on an open source project.

18 years of growth is a milestone worth recognizing on the web. Congratulations to the code contributors, translators, community organizers, WordPress leadership, and everyone else who has helped make this beloved publishing platform that continues to surprise the world.

Toshiba charger light on but wont turn on

So today i went on a long trip to san diego, so i stopped at a hotel and i decided to turn on my toshiba to play some games, the toshiba was dead so i got out my charger and plugged it in but it still didnt turn on. I noticed it was exposed to the sun when i was driving and some blankets were on it. (my toshiba model is the satellite C855D-S5340). Ive tried the tutorials on my ipad to try to get it working but none of them worked. Could someone please help me?

Angular vs React SEO: The Basics

The world of web development has changed profoundly in a short period of time.

One of the most significant developments to happen in the world of Web Dev is the adoption of JavaScript as a front-end framework used to build websites. Two of the most popular JavaScript frameworks popularly in use today are Angular and React.

Tackling Gender Bias Using Analytics

Introduction

Chances are when you hear "0.77 cents on the dollar," an increasingly contentious subject — the gender pay gap — comes to mind.  Today, we will explore a US Bureau of Labor Statistics dataset containing information about America's workforce (as of Jan. 2015) to answer the following questions:

  • Which industries are most and least affected?
  • Is the gender wage gap the same across all industries?
  • How much does an average American salary vary by gender?

To answer these questions, we'll be importing the dataset mentioned above (available on Kaggle) into an Arctype SQLite database, where we'll use queries and dashboards to manipulate and interpret the data.

Why Lambda Architecture in Big Data Processing?

Due to the exponential growth of digitalization, the entire globe is creating a minimum of 2.5 quintillion (2500000000000 Million) bytes of data every day and that we can denote as Big Data. Data generation is happening from everywhere starting from social media sites, various sensors, satellites, purchase transactions, Mobile, GPS signals, and much more. With the advancement of technology, there is no sign of the slowing down of data generation, instead, it will grow in a massive volume. All the major organizations, retailers, different vertical companies, and enterprise products have started focusing on leveraging big data technologies to produce actionable insights, business expansion, growth, etc. 

Overview

Lambda Architecture is an excellent design framework for the huge volume of data processing using both streaming as well as batch processing methods.  The streaming processing method stands for analyzing the data on the fly when it is in motion without persisting on storage area whereas the batch processing method is applied when data already in rest, means persisted in storage area like databases, data warehousing systems, etc. Lambda Architecture can be utilized effectively to balance latency, throughput, scaling, and fault-tolerance to achieve comprehensive and accurate views from the batch and real-time stream processing simultaneously.

Perl Can Do That Now!

Last week saw the release of Perl 5.34.0 (you can get it here), and with it comes a year's worth of new features, performance enhancements, bug fixes, and other improvements. It seems like a good time to highlight some of my favorite changes over the past decade and a half, especially for those with more dated knowledge of Perl. You can always click on the headers below for the full releases' perldelta pages.

Perl 5.10 (2007)

This was a big release, coming as it did over five years after the previous major 5.8 release. Not that Perl developers were idle—but it wouldn't be until version 5.14 that the language would adopt a steady yearly release cadence.

Gutenberg 10.7 Integrates With the Pattern Directory, Introduces New Block Design Controls

Gutenberg 10.7 landed yesterday. Within a few hours, the development team also released version 10.7.1 of the plugin with a few bug fixes. The latest update primarily focuses on work expected to land in WordPress 5.8 this July.

The feature freeze deadline for the current WordPress development cycle passed on Tuesday. This should mean that no new features beyond Gutenberg 10.7 will make it into the core platform. WordPress 5.8 Beta 1 is slated for June 8.

This release feels like the team has polished the interface and experience for the better. Users can also look forward to several enhancements, such as additional block design options.

Block Pattern Directory Integration

Gutenberg now serves its default block patterns via the pattern directory on WordPress.org. This moves their development outside of the plugin and core WordPress, which means designers can iterate on them without a user needing to update. They will always have the latest version available from the editor.

WordPress editor with pattern inserter.
Inserting a pattern into the content canvas.

The pattern directory will be a handy tool for end-users. For many, it will likely serve as a path toward building more complex layouts in the WordPress editor. At the moment, it only houses 10 patterns. This will change after it is opened to community submissions.

Shaun Andrews shared some of the ongoing design work for the directory, and it is beautiful. I am eager to see the final result when it launches.

Pattern directory with large hero header and a grid of patterns.
Recent work on the block pattern directory.

A side note about block patterns: there is currently a bug that may cause some of those bundled with themes to not appear in the inserter.

New Block Design Controls

Version 10.7 introduces several new design controls for blocks. The most exciting feature for many will be margin controls for more precise control over spacing. Theme authors must set the settings.spacing.customMargin key to true in their theme.json files to enable this.

Currently, only the Site Title and Site Tagline blocks support margin controls. However, now that the initial feature has landed, we should expect others to follow suit in future versions.

Editing the Site Title and Tagline blocks in the WordPress editor.
Spacing and typography controls for title and tagline

The Site Title block also has letter case typography controls.

The development team upgraded the Column block with a couple of new options. Users can now customize the padding and colors for individual columns.

Editing Column block spacing and colors in the WordPress editor.
Customizing individual columns.

One of the more low-key enhancements happens to be one of my favorite changes. The Media & Text block received a new “media width” block option in the sidebar. This makes getting the correct width a far better experience than using the drag handle in the content canvas.

In the WordPress editor, adjusting the media width for the Media & Text block.
Adjusting the media width in the Media & Text block

I may begin using this block more now. I had been shying away from it for any use case beyond the default width settings.

Another welcome enhancement is Cover being added as a transform option for the Group block. It will only appear if the block has a background.

Template Editor Welcome Guide

Overlaid welcome message to the new template editor feature.
Welcome message for the template editor.

Template-editing mode will be one of the most highlighted features for WordPress 5.8. It allows users to switch from content to template editing while never leaving the post screen. Therefore, users will need a welcome guide to explain the new feature.

Currently, the message reads:

Welcome to the template editor

Templates express the layout of the site. Customize all aspects of your posts and pages using the tools of blocks and patterns.

It is a solid starting point, but it does not fully explain what this mode is about. I expect the development team to smooth it out a bit, maybe add an extra slide or two, or even link to a dedicated documentation page on WordPress.org. Helping users set off on the right foot with this new feature should mitigate confusion and lower the support burden.

There is currently an open call for volunteers to provide feedback on the template editor while building a portfolio-type landing page (see my results). Make sure to get involved if you can spare half an hour or more.

Buttons Block Uses column-gap

Four Button blocks aligned in the WordPress editor with correct spacing.
Gap between buttons.

This is more of a theme developer note, but some users may have noticed lines of buttons not extending to the edge of their container. In certain situations, at least.

Essentially, the space between individual Button blocks used a bit of a hacky, old-school CSS margin solution to create the gutter space between each. In the modern world of flex and grid layouts, it is something most designers dread seeing. It overcomplicates things and makes for more bloated CSS, particularly if you want to make adjustments based on screen size.

I am highlighting this change because it is one of those stages where the block system is becoming more polished under the hood. And, it could be the start of more exciting things to come for theme authors.

“This is great, this is beautiful, themers are going to love it,” wrote Joen Asmussen in the ticket. “At some point once the dust settles, we should see if this gap could become a global styles property; since it’s so easy to change and resilient, it would be nice to handle in such a neat way.”

It is a welcome sight to see the use of column-gap land in the plugin. Of course, it could use row-gap for vertical spacing instead of margins in those cases where Buttons extend beyond a single row.

Now, can we do the same for the Columns, Gallery, and Query Loop blocks? Normalizing the system for gutters/gaps between flex items can save dozens upon dozens of lines of code in the long term.

How to Create Google Web Stories in WordPress

Create Google Web Stories in WordPressRecently, Google updated its official Web Stories WordPress plugin. This plugin allows website owners to embed their content into Google search results, an important thing to consider when managing how your company website is presented online. The Web Stories plugin makes robust content creation tools accessible for WordPress website owners. You can now generate Web […]

The post How to Create Google Web Stories in WordPress appeared first on WPExplorer.

DORA Metrics to Measure DevOps Performance

Look, we know the software development process is not an easy one to measure and manage, particularly as it becomes more complex and more decentralized. In many companies, there are multiple teams working on smaller parts of a big project-and these teams are spread all over the world. It's challenging to tell who is doing what and when, where the blockers are and what kind of waste has delayed the process. Without a reliable set of data points to track across teams, it's virtually impossible to see how each piece of the application development process puzzle fits together. DORA metrics can help shed light on how your teams are performing in DevOps.

What Are DORA Metrics?

Well, these metrics didn't just come out of thin air. DORA metrics are a result of six years' worth of surveys conducted by the DORA (DevOps Research and Assessments) team, that, among other data points, specifically measure deployment frequency (DF), mean lead time for changes (MLT), mean time to recover (MTTR) and change failure rate (CFR). These metrics serve as a guide to how well the engineering teams are performing and how successful a company is at DevOps, ranging from "low performers" to "elite performers." They help answer the question: Are we better at DevOps now than we were a year ago?