Comments

Illustrated drawing of Amadeus Maxmimilian Stadler

Amadeus

Last updated July 4, 2023

In this article, you’ll learn all about the optional comments feature for Mattrbld real-time previews. It allows you and your collaborators to add comments to content in the preview to facilitate review processes before content gets published.

Enabling the Feature

To enable this feature, your preview page needs to signal that it supports it by passing commments in the supports-array of the initial handshake:

// in a postMessage handler
if (data && data.handshake) {
  source.postMessage({
    handshake: data.handshake,
    supports: ['comments'],
  });
}

// you'll also need to cache the source to easily send more messages, so cachedSource is defined outside the handlerFunction

cachedSource = source;

With this handshake, Mattrbld will show a “Toggle Comments” button in the preview toolbar, which can be used to show existing comments and add new ones. For this to work, you’ll have to implement a few more functions in your preview that will handle the additional data Mattrbld will send.

This data will have the following format:

{
  feature: 'comments',
  type: <Type of Event>,
  payload: <Event Payload>,
}

Implementation Details

Your preview page is responsible for adding and deleting comment markers, as well as drawing them onto the page. Mattrbld handles the commenting and storage logic itself.

Toggling Comment Mode

Whenever the “Toggle Comments” button is clicked in the Mattrbld UI, Mattrbld will send a modechange event to the preview page:

{
  feature: 'comments',
  type: 'modechange',
  payload: {
    active: true, // or false when deactivating
    canComment: true, // or false when not allowed
  },
}

The payload contains two properties:

  • active which signals whether the comment mode was activated or deactivated

  • canComment which contains information on whether the current user is allowed to comment the content item being edited

You should use this event to register or remove event listeners on your preview page that message Mattrbld where the page was clicked:

// in a postMessage handler
if (data && data.feature === 'comments') {
  switch (data.type) {
    case: 'modechange':
      const { active, canComment } = data.payload;
      if (active) {
        window.addEventListener(
          'click',
          // a function that handles page clicks
          handlePageClick,
          false
        );
        // you can use canComment here to show 
        // that the comment mode is enabled for 
        // your preview page, e.g. by changing the 
        // cursor
      } else {
        window.removeEventListener(
          'click',
          handlePageClick,
          false
        );
        // a function that removes all comment 
        // markers on the page
        removeCommentMarkers();
        // use canComment to determine whether you
        // need to undo anything else here
      }
      break;       
    default:
      break;
  }
}

Initial Data

On top of the modechange event, Mattrbld also sends the comment data to the preview page whenever the comment mode was enabled:

{
  feature: 'comments',
  type: 'initialData',
  payload: {
    // an array of all the top-level comments for
    // the content item
    comments: <Array of Comment>,
  },
}

The payload consists of an array of top-level comments, i.e. comments that aren’t replies to other comments. A comment-object has the following properties:

{
  id: '89x27mzfls', // random id
  author: <Name>, // name of the comment author
  parent: null, // optional parent id if is reply
  content: <CommentContent>, // HTML string
  position: { x: <Int>, y: <Int> }, // or null
  status: null, // status like "resolved"
  created: <Date>,
  updated: null, // <Date> if edited
}

This event should be used to add comment markers on the page wherever they were added. The position property contains the position on the preview page where the marker was added by the comment author. Beware that this may be out of bounds depending on the size of the viewport, so you should make sure to handle such values appropriately.

// in the switch-case within the postMessage
// handler
case 'initialData':
  const { comments } = data.payload;
  comments.forEach((comment) => {
    // a function that places comment markers on 
    // the preview page
    createCommentMarker(comment)
  });
  break;

Handling Page Clicks

Whenever the user clicks on the preview page while comment mode is active, Mattrbld needs to know where that click happened and whether it was on the page itself or on a comment marker.

The way you implement this varies depending on how you have set up your preview page, but here’s a simple example you can use for reference:

function handlePageClick(e) {
  const {
    target, clientX, clientY, pageX, pageY,
  } = e;
  const isCommentMarker = 
    target.classList.contains('comment-marker');

  const data = {
    feature: 'comments',
    payload: {
      clientX,
      clientY,
    },
  };

  if (isCommentMarker) {
    data.type = 'commentClick';
    data.payload.id = target.dataset.id;
  } else {
    data.type = 'pageClick';
    data.payload.pageX = pageX;
    data.payload.pageY = pageY;
  }

  cachedSource.postMessage(
    data, MATTRBLD_INSTANCE
  );
}

This is an example of the bidirectional communication between Mattrbld and the preview page. Just like the preview page, Mattrbld listens to messages sent by the preview page and handles those messages accordingly.

As you can see in the example above, the messages the preview page sends are structured just the same as the messages Mattrbld sends to the preview page, containing the feature, type and payload properties.

When a comment marker is clicked, Mattrbld needs to be notified about where that click happened on the client (i.e. the iFrame the preview is shown within or the browser window / tab that’s displaying it) and what the ID of the clicked marker is. The event type for this kind of event is commentClick.

When any other place on the page is clicked, Mattrbld needs to be notified where that click happened on the client and the page itself, so a new comment can be added at that position. The event type for this kind of event is pageClick.

Adding Comment Markers

When Mattrbld receives a pageClick event and there is no comment-thread already opened, and the current user is allowed to add comments, it will show a UI for adding a new comment. Once the user saves their comment, Mattrbld will send the addCommentMarker event to the preview:

{
  feature: 'comments',
  type: 'addCommentMarker',
  payload: {
    comment: <Comment>, // the newly added comment
  },
}

The payload contains a copy of the newly added comment in the same format as all other comment-objects, outlined above.

This event should be used to add a single new comment marker to the page at the position specified in the comment, just like the existing comment markers were added to the page when the initialData event was handled:

// in the switch-case within the postMessage
// handler
case 'addCommentMarker':
  const { comment } = data.payload;
  // a function that places comment markers on 
  // the preview page
  createCommentMarker(comment);
  break;

Deleting Comment Markers

Similarly, when a comment is removed from within Mattrbld, it will send a deleteCommentMarker event to the preview page:

{
  feature: 'comments',
  type: 'deleteCommentMarker',
  payload: {
    comment: <Comment>, // the deleted comment
  },
}

Use this event to implement the removal of comment markers on the preview page:

// in the switch-case within the postMessage
// handler
case 'deleteCommentMarker':
  const { comment } = data.payload;
  // a function that removes comment markers from 
  // the preview page
  deleteCommentMarker(comment);
  break;

If the deletion is undone by the user in Mattrbld, it will send out another addCommentMarker event to recreate the marker on the preview page, so you don’t have to implement any logic to handle those cases.

Moving Comment Markers

If you would like to make comment markers movable once they were added to the preview page, you can send the following event to Mattrbld in order to update their position after you have moved the marker on the page using your own logic:

{
  feature: 'comments',
  type: 'commentMoved',
  payload: {
    comment: <CommentID>,
    position: { x: <Number>, y: <Number> },
  },
}

The payload consists of two properties:

  • comment which is the ID of the comment that was moved

  • position which is the new position of the comment, specified in page coordinates (i.e. event.pageX). You always have to supply both top and left as numbers lower than 999,999 or the movement will be rejected

Mattrbld will then save the new position for the comment accordingly if the current user is the author of the comment, since users can only modify their own comments.

If the comment cannot be moved due to missing permissions, or an error, Mattrbld will respond with a moveFailed event, alongside showing a warning to the user:

{
  feature: 'comments',
  type: 'moveFailed',
  payload: {
    comment: <Comment> or null,
  }
}

The payload will contain one property:

  • comment which is the comment which could not be moved, including its original position, unless it wasn’t found in Mattrbld

You should use this event in order to reset the position of the comment marker on the preview page:

// in the switch-case within the postMessage
// handler
case 'moveFailed':
  const { comment } = data.payload;
  // could also be a single function that resets
  // the position of the comment marker
  deleteCommentMarker(comment);
  if (comment && comment.position) {
    addCommentMarker(comment);
  }
  break;

Users which do not have the canComment permission will not be able to move comments. You should prevent them from attempting to move a comment in the first place by referencing the canComment property sent in the modechange event.

Updating Comment Markers

Sometimes, like when the status of a comment changes, Mattrbld will send an updateCommentMarker event to the preview page:

{
  feature: 'comments',
  type: 'updateCommentMarker',
  payload: {
   comment: <Comment>, // the updated comment
  },
}

You may use this event to update the appearance of the marker, for example when the status of a comment changes:

// in the switch-case within the postMessage
// handler
case 'updateCommentMarker':
  const { comment } = data.payload;
  // a function that updates comment markers on
  // the preview page
  updateCommentMarker(comment);
  break;

And that’s it! With handling these events, you and your users can now leave comments on real-time previews and enhance your feedback workflows without having to leave Mattrbld.

Disabling Comments for Certain Collections

Similarly to how you can disable real-time previews for entire Collections, you can also disable comments for them from the Collection Settings. There, you can also assign which roles are allowed to comment at all.

A screenshot of the "Edit Collection" view in Mattrbld, showing the options for disabling comments and setting the “can leave comments” permission on a per-role basis.
Options for disabling comments and managing permissions per-Collection

Storage

Comments are stored in the .mattrbld directory of your project, in a folder called comments that mirrors your project’s directory structure. To minimise the potential for synchronisation conflicts, all comments by one user are stored in a separate file from comments by another user. This is also the reason why users can only modify their own comments.

When a user deletes their top-level comment, only their own replies (if there are any) will be removed immediately. Replies by other users will remain as “orphans” and cleaned up the next time that user views comments on that particular content item.

If the comments-file for a single user is empty, Mattrbld will delete it. It will be re-created once that user leaves another comment.


This concludes the article on real-time preview comments and how to implement them. The next one will teach you strategies to deal with some common issues that can come up while implementing the preview protocol.

Take full control over your content.

Get started