Changes are coming to Rich Text (html_notes and html_text) in Asana

Updated Mar 31, 2021: We have finalized the tags and attributes for all new rich text formats and created guidelines for handling new tags in the future

tl;dr; Starting in about 3 months, we will begin adding rich text formats to various objects in Asana and expect this will break many existing apps. If you use either of the html_notes or html_text fields, you should immediately check if your app is affected and, if it is, allocate time to make it compatible within the next 3 months.

Hi there!

My name is Yujian Yao, and I am an Engineering Program Lead here at Asana. We have some exciting (and hopefully not terrifying) news for you.

Important Changes

We’re planning to add additional formats to our rich text content in many common objects such as tasks and comments to enable a richer experience for our users. From the API perspective, you can expect to read and write new HTML tags in the html_notes or html_text field. We have not decided on the exact format yet, but to give you some possible examples:

  • Headers: e.g. <h1>My header text</h1>
  • Horizontal rules: <hr>
  • Table: <table><tr><td>Hello </td><td>World</td></tr><table>
  • Milestone embeds: <div data-asana-type=”embed-milestone-list” data-asana-gid=”123” data-asana-project=”123”/>. This could be a div, which points to a project. You optionally fetch to display a list of milestones (we’d create a route to make this easy).

More importantly, we plan to make continuous improvements to our rich text features in the coming years, so it will be really important to make sure that your code is forward compatible.

To better understand what new rich text features we may introduce, you can try out some of the new rich text features in our new project briefs.

Watch out for more updates

We will be giving constant updates in the coming months. You can expect more details on:

  1. The launch timeline
  2. The exact HTML schema
  3. Resources to help you make your HTML parser forward compatible

Questions for you

Meanwhile, to make sure that we provide the best rich text API, we are also curious to hear from you:

  1. What are your use cases regarding rich text in asana?
  2. Do you parse html from asana? If so, what languages or libraries do you use?
9 Likes

Hi,

It would break if you display what’s inside the notes without properly cleaning up? Worst case scenarios: you get some <table> code displayed on your app, right?

Well, no, it depends on how an app is specifically parsing the rich text. Worst case scenario is that the app crashes if it encounters an unknown html element. Just depends on how the code is written.

(FYI I’m speaking generally, not about my app!)

1 Like

Yep @Phil_Seeman 's comment is spot on. Your app shouldn’t crash if it can handle arbitrary html tags correctly.

1 Like

Good point, thanks to both of you.

Well, it will affect in some way. I’m close to releasing Prana, my lightweight Mac client for Asana. It’s mostly a native Mac app, but for HTML editing I use an embedded web view with the Quill editor, configured as possibly close to how it was configured in Asana (I think Asana used to use Quill in the past). I couldn’t get the official configuration, of course, but I limited it to available options, more or less. And then there’s this little bit of processing related to everything being wrapped in body tags. So, the effect on Prana will depend on what it’ll require from the editor. But I mostly don’t parse the HTML directly in Swift.

Please don’t forget about <img> !!

One inline image worth 1000 words, so please allow it. Almost every modern text editor supports this.

1 Like

We’re definitely considering those! Project briefs support them, and we’re investigating that as part of our rollout :slight_smile:

2 Likes

Update: We have finalized the tags and attributes for all new rich text formats and created guidelines for handling new tags in the future. Please feel free to leave questions or comments below, including how this may affect your apps. Before launch, we’ll be publishing this content on our developer docs.

New HTML Tags in Asana Rich Text

For examples in this post, we’ve added indentation and newlines to make it easier to read. When implemented, rich text routes won’t include these.

Headers: <h1></h1> and <h2></h2>

Attributes: None

Inner content: Only <strong> , <em> , <u> , <s> , <code> , <a> allowed inside header tags, and no new line character allowed in the inner content.

Example: <h1>This is a header with some <strong>Strong<strong> and <em>Emphasized</em> words</h1>

Notes:

Note that we do not support h3+.

These are rendered like a heading element if possible, otherwise should be rendered as bold.

Horizontal Rule: <hr>

Attributes: None

Inner content: None

Example: <hr>

Notes:

This is an empty element, i.e. no closing tag.

These are rendered as a horizontal rule if possible, otherwise should be rendered as a newline.

Images: <img>

Attributes:

  • Read:
    • data-gid: attachment id
    • src: s3 link to download the asset
    • alt: alternate text, used for fallback / accessibility
    • style: used to help render this in a webview, see notes.
  • Required for write: data-gid (see note below)

Inner content: None

Example:

<img
    data-gid=”12345”
    src=”https://s3.amazonaws.com/assets/123/Screenshot.png”
    alt=”\nhttps://s3.amazonaws.com/assets/123/Screenshot.png”
    style=”display:block;max-width: 100%; margin-left: auto;
    margin-right: auto;”
>

Notes:

This is also an empty element, so no closing tag supported.

This represents an image block. Even though <img> is not a block level element , we render them as blocks in our product. Therefore we also supply a style attribute with “display: block” . The style attribute also contains other styles to make the image display in a similar fashion as the ones in our project. We will always supply this style attribute.

Attributes required for writes: data-gid . All other attributes are ignored on write. Note that we do not support referencing external images via the src attribute - all image blocks have to reference images uploaded to Asana.

We currently have no way to create image blocks through the API, but do plan to provide a route to do this. We will update the forum when this is available.

Embeds: <object></object>

As part of rich text work, we’ve started adding the ability to embed rich content into text. These include both embedding views for Asana objects, like milestones, as well as external media. When exposing this in the API, we want to both support developers who want to richly display this content in their apps, as well as support a graceful fallback.

To do this, we’re using an <object> tag. Each tag will have:

  • A type attribute: This is MIME type. You can use this to determine the type of embed, and decide how you want to display it. This is required for writes.
  • data attributes: these will contain the data you need to richly display the embed.
  • HTML fallback in the inner contents: the content inside the object tag will be Asana HTML, representing a “less rich” version of the contents. If your app is not going to support these as rich embeds, you can render the contents of an <object> tag to display a less rich version of the content. This content will always be constructed and supplied by Asana. Developers will not have control over it - it will be ignored if you create or edit a <object> tag.

<object> tags are blocks, and we similarly supply a style attribute with “display: block” . If clients use the inner fallback content, they must render the content as it is and not try to transform it, because we may update the fallback content in the future.

For this launch, we will introduce 3 new types of embeds: external, project milestone, and project goals.

External media embed

Attributes:

  • Read:
    • style : used to help render in a webview.
    • type : see above.
    • data : URL to the external media.
  • Write: required type and data

Example:

<object
    style=”display:block”
    type=”application/vnd.asana.external_media”
    data=”https://www.youtube.com/embed/VqnMA3K6-e0”
>
    <a href=”https://www.youtube.com/embed/VqnMA3K6-e0”>
        https://www.youtube.com/embed/VqnMA3K6-e0
    </a>
</object>

Notes: We recommend rendering these as either links or rich previews of the content.

Project Milestones Embed

Attributes:

  • Read:
    • style : used to help render in a webview.
    • type : see above.
    • data-project-gid : the id of the project we want to render milestones for.
  • Write: required type and data-project-gid

Example:

<object
    style=”display:block”
    type=”application/vnd.asana.project_milestone_list”
    data-project-gid=”12345”
>
    <a href=”https://app.asana.com/0/12345/list”>project name</a>
    <ul>
        <li><a href=”https://app.asana.com/0/12345/23456”>milestone name 1</a></li>
        <li><a href=”https://app.asana.com/0/12345/23457”>milestone name 2</a></li>
    </ul>
</object>

Notes: We recommend rendering this as a list of milestones in the project.

Project Goals Embed

Attributes:

  • Read:
    • style : used to help render in a webview.
    • type : see above.
    • data-project-gid : the id of the project we want to render goals for.
  • Write: required type and data-project-gid

Example:

<object
    style=”display:block”
    type=”application/vnd.asana.project_goal_list”
    data-project-gid=”12345”
>
    <a href=”https://app.asana.com/0/12345/list”>project name</a>
    <ul>
        <li><a href=”https://app.asana.com/0/goal/23456”>goal name</a></li>
        <li><a href=”https://app.asana.com/0/goal/23457”>goal name</a></li>
    </ul>
</object>

Notes: We recommend rendering this as a list of goals associated with the project.

Tables: <table></table> , <tr></tr> and <td></td>

Tags:

  • <table> for the table,
  • <tr> for each table row
  • <td> for each table cell.

Attributes:

  • No attributes on <table> or <tr>
  • <td> can have these optional attributes:
    • colspan , rowspan - these denote how much the cell spans in the row and column direction, the same as html.
    • data-cell-widths - this denotes the width of the cell in pixels. It is a list of comma separated numbers. The length of the list is the same as colspan.
  • Attributes that will be ignored for writing:
    • width : this will be the sum of widths in data-cell-widths . We supply this to make sure that the table cell renders as expected. To set the width, however, use data-cell-widths

Inner content:

  • Only <tr> allowed in <table>
  • Only <td> allowed in <tr>
  • Only <strong> , <em> , <u> , <s> , <code> , <a> , <img> , <ul> , <ol> , <li> allowed in <td> . New line characters also allowed in <td>

Example:

<table>
    <tr>
        <td data-colwidth="320" width="320">
            Heading 1
        </td>
        <td data-colwidth="180" width="180">
            Heading 2
        </td>
        <td data-colwidth="120" width="120">
            Heading 3
        </td>
    </tr>
    <tr>
        <td data-colwidth="320" width="320">
            text
            <img
                data-gid=”12345”
                src=”https://s3.amazonaws.com/assets/123/Screenshot.png”
                alt=”\nhttps://s3.amazonaws.com/assets/123/Screenshot.png”
                style=”display:block;max-width: 100%; margin-left: auto;
                margin-right: auto;”
             >
        </td>
        <td colspan="2" data-colwidth="180,120" width="300">
            text
            <ol>
                <li>List iem</li>
                <li>List item</li>
            </ol>
        </td>
    </tr>
</table>

Guidelines for handling new tags in the future

We plan to add new rich text elements going forward, and we want you to be able to design parsers and display logic for our rich text in a forward compatible way. To do this, we’re providing two mechanisms for you to be able to parse and display new tags that you don’t explicitly support: giving sane defaults to render it in a webview, and guidelines for how to handle new tags.

Forwards compatibility option one: Render tags in a webview

You can expect the rich text HTML to render reasonably in a web view if you apply the following css style to the wrapping DOM node:

overflow-wrap: break-word;
white-space: pre-wrap;

Note that this won’t look like it does in Asana necessarily, and you may find that you want to add your own styles.

Forwards compatibility option two: graceful fallbacks

If you aren’t using a webview, in order to avoid missing or mangled text, here is how you should render tags you don’t explicitly handle:

<object> with an unhandled type

Render the <object> tag as a block and render the contained HTML with the same behavior as if it weren’t inside an <object> . We will never send an <object> tag nested inside another <object> tag.

<img>

Fall back to the alt text if the image can’t be displayed. Render \n<alt text>\n since <img> are blocks

Empty elements except <img>

It is ok to omit them. Render as a new line if the tag is a block.

Other semantic non-terminal tags

Ignore the tag and render whatever is inside. Follow the html convention for whether it is a block or not.

Other rich text guidelines

How to determine if a tag is block or inline

  1. If the tag and its attributes have API documentation, follow the documentation (e.g. milestone list embeds are blocks), otherwise…
  2. If the tag is <object> or <img> , render it as a block
  3. Check if it falls under the html block level element list

When processing rich text and sending it back

It’s ok to ignore tags or attributes on tags that are unknown for rendering/processing. But it’s very important to send everything back (attributes and inner content) to avoid data loss. <object> is an exception where it’s ok to not send any inner content back (all inner content in <object> will be ignored).

If you plan to write an editor

For editing, even if the tag and attributes are known, if it contains any unknown attributes, it must be treated as unknown.

If a tag is unknown, first determine if the tag is block or inline and render it as a block or inline atomic and non-copiable (and non-cut&paste-able) editor node (all inner content is non-editable). This is because we don’t know if the unknown node has constraints on inner content or where it can appear. The node must also keep track of all attributes and inner content to be serialized back.

5 Likes