Skip to content

Instantly share code, notes, and snippets.

@rdsaunders
Last active August 25, 2021 09:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rdsaunders/85415a12aa163663683ed17153510c1f to your computer and use it in GitHub Desktop.
Save rdsaunders/85415a12aa163663683ed17153510c1f to your computer and use it in GitHub Desktop.

Specification for the Contensis Composer Format

WORKING DRAFT


Canvas/composer JSON structure

Each composer item in a composer/canvas is made up of a type a value array which contains fragments that can have their own properties and decorators.

_types

Supported _types

Paragraphs

Basic paragraph

Example: This is a paragraph

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This is a paragraph"
    }
  ]
}

Paragraph with inline styles

Example: This is some **bold** _text_

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This is some "
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["strong"]
      },
      "value": "bold"
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["emphasis"]
      },
      "value": " text"
    }
  ]
}

Paragraph with inline entry reference

Example: Check out our latest [Boston Fern](contensis:///0123-0123-01547-7894) plant

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "Check out our latest "
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["entry"],
        "entry": {
          "entryId": "0123-0123-01547-7894",
          "contentType": "plantProduct",
          "nodeId": "0123-0123-01547-7894"
         },
      },
      "value": "{{entryTitle}}" 
    },
    {
      "type": "_fragment",
      "value": " plant"
    }
  ]
}

Paragraph with separated inline styles

Example: This is some **bold** **text**

Note: This feels odd to include the blank space between two decorators as blank fragment

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This is some "
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["strong"]
      },
      "value": "bold "
    },
    {
      "type": "_fragment",
      "value": " "
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["strong"]
      },
      "value": "text"
    }
  ]
}

Nested fragment example

Example: `This is a sentence that has a link that is bold

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This is a sentence that "
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["strong"]
      },
      "value": [
        {
          "type": "_fragment",
          "value": "has a ",
          
        },
        {
          "type": "_link",
          "value": {
              "text": "link",
              "url": "http://www.bbc.co.uk",
              "target": "blank"
          }
        }
        ,{
          "type": "_fragment",
          "value": "that is bold"            
          }
        }
      ]
    }
  ]
}

Lede

Represents a lede introductory paragraph.

Example: This is a lede paragraph

Option 1: Lede is considered a visual styling of the paragraph for the introductory opening of a story.

{
  "type": "_paragraph",
  "properties": {
    "type": "lede"
  },
  "value": [
    {
      "type": "_fragment",
      "value": "This is a paragraph"
    }
  ]
}

Option 2: Lede is considered a core part of the content and is decorated with a first class decorator.

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["lede"]
      },
      "value": "This is a paragraph"
    }
  ]
}

Option 3: Lede is considered a first class block?

{
  "type": "_lede",
  "value": [
    {
      "type": "_fragment",
      "value": "This is a paragraph"
    }
  ]
}

Heading

No inline styles

Example: # This is my heading

{
  "type": "_heading",
  "properties": {
    "level": 1
  },
  "value": {
    "type": "_fragment",
    "value": "This is my heading "
  }
}

With inline styles

This is my heading

Example: # This **is** my heading

{
  "type": "_heading",
  "properties": {
    "level": 1
  },
  "value": [
    {
      "type": "_fragment",
      "value": "This "
    },
    {
      "type": "_fragment",
      "value": "is",
      "properties": {
        "decorators": ["strong"]
      }
    },
    {
      "type": "_fragment",
      "value": " my heading"
    }
  ]
}

Lists

Unordered list

- **_List item one_**
- List item two
- **List item three**
{
  "type": "_list",
  "properties": {
    "listType": "unordered"
  },
  "value": [
    {
      "type": "_listItem",
      "value": [
        {
          "type": "_fragment",
          "properties": {
            "decorators": ["strong, italic"]
          },
          "value": "List item one"
        }
      ]
    },
    {
      "type": "_listItem",
      "value": [
        {
          "type": "_fragment",
          "value": "List item two"
        }
      ]
    },
    {
      "type": "_listItem",
      "value": [
        {
          "type": "_fragment",
          "properties": {
            "decorators": ["strong"]
          },
          "value": "List item three"
        }
      ]
    }
  ]
}

Ordered list

1. **_List item one_**
2. List item two
3. **List item three**
{
  "type": "_list",
  "properties": {
    "_list": {
        "listType": "ordered",
        "start": 1
    }
  },
  "value": [
    {
      "type": "_listItem",
      "value": [
        {
          "type": "_fragment",
          "properties": {
            "decorators": ["strong, italic"]
          },
          "value": "List item one"
        }
      ]
    },
    {
      "type": "_listItem",
      "value": [
        {
          "type": "_fragment",
          "value": "List item two"
        }
      ]
    },
    {
      "type": "_listItem",
      "value": [
        {
          "type": "_fragment",
          "properties": {
            "decorators": ["strong"]
          },
          "value": "List item three"
        }
      ]
    }
  ]
}

todo: Nested list example

Divider

---

{
  "type": "_divider",
  "properties": {
    "style": "dotted",
  }
}

Location

Selecting a latitude and longitude on a map.

{
  "type": "_location",
  "value": [
    {
      "lon": -2.997664,
      "lat": 51.584151
    }
  ]
}

Image

  1. Images feel as though they go against the grain of the new proposed _type format.
  2. We could duplicate certain properties of an image such as caption, alt-text and transforms.

Image URL

{
  "type": "_image",
  "value": "https://unsplash.com/photos/TYQ6fyF3Amc"
}

Image from Contensis

{
  "type": "_image",
  "properties": {
    "caption": "",
    "altText": "A photo of Richard Saunders.",
    "transformations": null
  },
  "value": {
      "altText": "A photo of Richard Saunders.",
      "sys": {
        "projectId": "contensis",
        "metadata": {
          "includeInSearch": true,
          "includeInAToZ": false,
          "includeInMenu": false,
          "includeInSiteMap": false,
          "nodeId": "bc6435eb-c2e3-4cef-801f-b6061f9cdad6"
        },
        "properties": {
          "filename": "richard-saunders-blog-image.png",
          "fileSize": 611073,
          "fileId": "bc6435eb-c2e3-4cef-801f-b6061f9cdad6",
          "width": 600,
          "height": 600
        },
        "version": {
          "createdBy": "ServicesUser",
          "created": "2019-11-04T16:57:43.7464974Z",
          "modifiedBy": "ServicesUser",
          "modified": "2019-11-04T16:57:43.7464974Z",
          "publishedBy": "ServicesUser",
          "published": "2019-11-04T16:57:54.6463551Z",
          "versionNo": "1.0"
        },
        "owner": "r.bromley",
        "isPublished": false,
        "availableLanguages": ["en-GB"],
        "unavailableLanguages": ["en-GB"],
        "translationState": "none",
        "id": "bc6435eb-c2e3-4cef-801f-b6061f9cdad6",
        "dataFormat": "asset",
        "language": "en-GB",
        "uri": "/image-library/people-images/richard-saunders-blog-image.png",
        "contentTypeId": "image"
      },
      "entryTitle": "richard-saunders-blog-image",
      "entryDescription": null
  }
}

Below based on composer item

{
  "type": "_image",
  "value": {
    "asset": {
      "altText": "A photo of Richard Saunders.",
      "sys": {
        "projectId": "contensis",
        "metadata": {
          "includeInSearch": true,
          "includeInAToZ": false,
          "includeInMenu": false,
          "includeInSiteMap": false,
          "nodeId": "bc6435eb-c2e3-4cef-801f-b6061f9cdad6"
        },
        "properties": {
          "filename": "richard-saunders-blog-image.png",
          "fileSize": 611073,
          "fileId": "bc6435eb-c2e3-4cef-801f-b6061f9cdad6",
          "width": 600,
          "height": 600
        },
        "version": {
          "createdBy": "ServicesUser",
          "created": "2019-11-04T16:57:43.7464974Z",
          "modifiedBy": "ServicesUser",
          "modified": "2019-11-04T16:57:43.7464974Z",
          "publishedBy": "ServicesUser",
          "published": "2019-11-04T16:57:54.6463551Z",
          "versionNo": "1.0"
        },
        "owner": "r.bromley",
        "isPublished": false,
        "availableLanguages": ["en-GB"],
        "unavailableLanguages": ["en-GB"],
        "translationState": "none",
        "id": "bc6435eb-c2e3-4cef-801f-b6061f9cdad6",
        "dataFormat": "asset",
        "language": "en-GB",
        "uri": "/image-library/people-images/richard-saunders-blog-image.png",
        "contentTypeId": "image"
      },
      "entryTitle": "richard-saunders-blog-image",
      "entryDescription": null
    },
    "caption": "",
    "altText": "A photo of Richard Saunders.",
    "transformations": null
  }
}

Image Array

{
  "type": "_imageArray",
  "properties": {
    "layout": "gallery, carousel"
  },
  "value": [
    {
      "type": "_image",
      "properties": {
        "caption": "",
        "altText": "A photo of Jon Maskrey.",
        "transformations": null
      },
      "value": {
        "altText": "A photo of Jon Maskrey.",
        "sys": {},
        "entryTitle": "jon-maskrey-blog-image",
        "entryDescription": null
      }
    },
    {
      "type": "_image",
      "properties": {
        "caption": "",
        "altText": "A photo of Ryan Bromley.",
        "transformations": null
      },
      "value": {
        "altText": "A photo of Ryan Bromley.",
        "sys": {},
        "entryTitle": "ryan-bromley-blog-image",
        "entryDescription": null
      }
    },
    {
      "type": "_image",
      "properties": {
        "caption": "",
        "altText": "A photo of Richard Saunders.",
        "transformations": null
      },
      "value": {
        "altText": "A photo of Richard Saunders.",
        "sys": {},
        "entryTitle": "richard-saunders-blog-image",
        "entryDescription": null
      }
    }
  ]
}

Code

{
  "type": "_code",
  "properties": {
    "language": "javascript"
  },
  "value": "console.log(\"hello world\");"
}
{
  "type": "_code",
  "properties": {
    "display": "split" // This feels like an editor property
  },
  "language": "javascript",
  "value": "console.log(\"hello world\");"
}

Number

{
  "type": "_number",
  "value": 123456
}

Decimal

{
  "type": "_decimal",
  "value": 10.534353453453453453425345345345
}

Quote

Example: Stay hungry, stay foolish – Steve Jobs

Original format

{
  "type": "_quote",
  "value": {
      "text": "Stay hungry stay foolish",
      "source": "Steve Jobs"
    }
  ]
}

Possible extension is to add citation?

{
  "type": "_quote",
  "value": {
      "text": "Stay hungry stay foolish",
      "source": "Steve Jobs",
      "citation": "https://www.apple.com/stevejobs/"
    }
  ]
}

#### Support for fragments?

```json
{
  "type": "_quote",
  "properties": {
    "source": "Steve Jobs",
    "cite": "https://www.apple.com/stevejobs/"
  },
  "value": [
    {
      "type": "_fragment",
      "value": "Stay",
      "properties": {
        "decorators": ["strong"]
      },
    },
    {
      "type": "_fragment",
      "value": " hungry"
    },
    {
      "type": "_fragment",
      "value": "Stay",
      "properties": {
        "decorators": ["strong"]
      },
    },
    {
      "type": "_fragment",
      "value": " foolish"
    }
  ]
}

Markup

To support existing TinyMCE / Markdown data

{
  "type": "_markup",
  "properties": {
    "format": "html"
  },
  "value": "<p>This is rich <em>text</em> with some <strong>styling</strong></p>"
}

The format is part of the content as it describes it.

{
  "type": "_markup",
  "value": {
    "format": "html",
    "value": "<p>This is rich <em>text</em> with some <strong>styling</strong></p>"
  },
}

Strongly define the type, no need for extra properties.

{
  "type": "_html",
  "value": "<p>This is rich <em>text</em> with some <strong>styling</strong></p>"
}

{
  "type": "_markup",
  "properties": {
    "format": "markdown"
  },
  "value": "This is rich _text_ with some **styling**"
}

The format is part of the content as it describes it.

{
  "type": "_markup",
  "value": {
    "format": "markdown",
    "value": "This is rich _text_ with some **styling**"
  },
}

Strongly define the type, no need for extra properties.

{
  "type": "_markdown",
  "value": "This is rich _text_ with some **styling**"
}

Call out box

{
  "type": "_callout",
  "properties": {
    "type": "note",
    "icon": "notepad",
  },
  "value": {
    "title": "Heading",
    "message": "This is a call out message"
  }
}

or

{
  "type": "_callout",
  "value": {
    "title": "Heading", // optional
    "message": "This is a call out message",
    "type": "note",
    "icon": "notepad"
  }
}

Embed

{
  "type": "_embed",
  "properties": {
    "provider_name": "youtube",
    "provider_url": "https://www.youtube.com"
  },
  "value": "https://www.youtube.com/watch?v=ArOMXELHiLw"
}

Link

{
  "type": "_link",
  "value": {
    "url": "https://www.bbc.co.uk",
    "newWindow": true,
    "rel": ["noopener", "external"]
  }
}

Component

{
  "type": "_component",
  "properties": {
    "componentID": "iconWithText",
  },
  "value": {
          "icon": {
            "sys": {
              "projectId": "contensis",
              "workflow": {
                "id": "contensisEntryBasic",
                "state": "versionComplete",
                "allowedEvents": [
                  "versionComplete.sysUpdate",
                  "versionComplete.sysDelete",
                  "sysUnpublish"
                ],
                "transition": {
                  "invoked": "2020-04-28T15:45:21.1250724Z",
                  "invokedBy": "m.white",
                  "from": "draft",
                  "event": "publish",
                  "data": null
                }
              },
              "metadata": {},
              "version": {},
              "owner": "r.bromley",
              "isPublished": true,
              "slug": "icon-caching-images",
              "availableLanguages": ["en-GB"],
              "unavailableLanguages": [],
              "translationState": "none",
              "id": "51639de0-a1e4-4352-b166-17f86e3558bf",
              "dataFormat": "entry",
              "language": "en-GB",
              "contentTypeId": "icon"
            },
            "entryTitle": "ICON: Caching images",
            "entryDescription": null
          },
          "text": "This is my icon text"
        }
}

Decorators

Decorators are applied to _fragments.

Supported decorators

  • Strong
  • Emphasis
  • Underline
  • Strikethrough
  • Subscript
  • Superscript
  • Mark
  • Code
  • Keyboard
  • Variable
  • Code
  • Insert
  • Delete
  • _links
  • _comment

Do we want to support inline images to a paragraph? e.g. small icons / emoji

Links

Example: This is a [link](https://www.bbc.co.uk) to the BBC.

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This is a "
    },
    {
      "type": "_fragment",
      "properties": {
      "decorators": ["link"],
      "link": {
          "url": "https://www.bbc.co.uk",
          "target": "blank"
        }
      },
      "value": "link"
    },
    {
      "type": "_fragment",
      "value": " to the BBC."
    }
  ]
}

Inline entry link

The ability to link to CMS content using a search to insert links to CMS content, without the need for carrying out extra lookups on the front end for the most important information.

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This is a "
    },
    {
      "type": "_fragment",
      "properties": {
      "decorators": ["inlineEntry"],
      "inlineEntry": {
          "entryId": "0e552c3f-5f1d-4eb4-b9dc-c5d0f5ed5c9d",
          "entryUrl": "http://www.contensis.com/help-and-docs/account-settings/change-password",
          "nodeId": "fa606bf2-c9fa-42f3-94cf-4d0a879c70b3",
          "contentTypeId": "article"
        }
      },
      "value": "link"
    },
    {
      "type": "_fragment",
      "value": " to the BBC."
    }
  ]
}

Comment

Example: This text has a @@comment@@

{
  "type": "_paragraph",
  "value": [
    {
      "type": "_fragment",
      "value": "This text has a "
    },
    {
      "type": "_fragment",
      "properties": {
        "decorators": ["comment"],
        "comment": {
          "commentId": "0000-1234-5678-9101-1121",
          "authorID": "BC12-1234-5678-9101-1121"
        }
      },
      "value": "comment"
    }
  ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment