Plugin Documentation Editor 3.0

Introduction

Topol Plugin is a drag-and-drop email editor you can embed to your site. Email templates are managed on your end (self-hosted). Once the editor is saved we give you HTML and JSON of the email template. The editor can only load templates in JSON created specifically in our Editor. Please note it's not possible to load HTML to the Editor.

The integration will take you only a few minutes.

  1. Integrate Topol Plugin in 2 easy steps
  2. Save and load your templates
  3. Advanced Plugin configuration
  4. Custom storage - AWS or GCS
  5. Custom file manager
  6. Custom API endpoints

Plugin integration

Step 1. Create API token

In your Topol Account go to Settings -> Plugin and create an API token.

API key

As token name, you can set any name (usually the name of your project). As a token domain, you have set the domain where you want to run our plugin, examples:

topol.io
www.topol.io
*.topol.io
localhost:563295

The API key is always connected with given domains and will not work on a different domain.

Step 2. Add necessary code

Once you get your API key, first, insert this HTML within your application:

<div id="app" style="position: absolute; width: 100%; height: 100%;"></div>

This is where the plugin will be displayed. (It's important to set an explicit height and width for the element, otherwise the height and width of the div could be too small for the TOPOL.io builder.)

apiKey - how to get an API token
userId - UserId is ID of your user (you will not find it in our app, just use any ID you want). UserID is an alphanumeric string (it can contain letters, numbers, _ or -) and it identifies the unique user of Your app and allows the plugin to load resources for that user (e.g. images). It will be counted as a unique user for monthly billing purposes.

Then, insert this within your file with plugin implementation:

//Import Editor Loader
<script src="https://d5aoblv5p04cg.cloudfront.net/editor-3/loader/build.js" type="text/javascript"></script>

//Define Plugin Options
<script>
const TOPOL_OPTIONS = {
    id: "#app",
    authorize: {
        apiKey: "YOUR_API_KEY",
        userId: "UserID",
    },
    callbacks: {
        onSave: function(json, html) {
           //work with JSON and HTML
        }
    }
};

TopolPlugin.init(TOPOL_OPTIONS);

fetch('https://tlapi.github.io/topol-editor/templates/1.json')
    .then(response => response.text())
    .then(template => {
        //Load the template in Editor
        TopolPlugin.load(template)
    }
);
</script>

Upgrade Guide

Upgrade from Editor 2 to Editor 3 (est. upgrade time is 5 minutes)

Please note that there are not any major breaking changes in plugin functionality or template structures etc. If you would encounter any issues with the new version you can opt back to the Editor 2 version.

  • URL loader changed

From:

<script src="https://d5aoblv5p04cg.cloudfront.net/editor-2/loader/build.js" type="text/javascript"></script>

To:

<script src="https://d5aoblv5p04cg.cloudfront.net/editor-3/loader/build.js" type="text/javascript"></script>
  • i18n

Language codes now follow ISO 639-1 standard.

Changed Language Codes:

Old CodeNew Code
jpja
sesv
fifin
iwhe

Working with JS frameworks

When using frameworks like Vue, React or Angular, every time when hiding the editor, you should use the TopolPlugin.destroy() function to end the editor:

Don't forget to load your editor after your component is mounted.

TopolPlugin.destroy()

When opening again, inicialize the editor with TopolPlugin.init(TOPOL_OPTIONS).

Plugin Configuration

You can configure the plugin for your needs with providing specific configuration within the TOPOL_OPTIONS variable.

Here is the list of all possible options:

var TOPOL_OPTIONS = {
    id: "#app",
    authorize: {
        apiKey: "Your Api Key",
        userId: "User ID",
    },
    title: "My template builder", // Title shown on the top of the main menu
    language: "en",
    removeTopBar: false, // Hides the top bar of the email editor
    topBarOptions: [
        "undoRedo",
        "changePreview",
        "previewSize",
        "previewTestMail",
        "saveAndClose",
        "save"
    ], // Displays given elements in top bar 
    mainMenuAlign: "left", // Main menu on right or left side
    hideSettingsTab: false, // Hides settings tab
    disableAlerts: false, // Disables alerts
    light: false, // set the editor theme to be light
    customFileManager: false, // Sets the build in file manager to be disabled and change to call the callbacks provided below
    contentBlocks: {
        text: {
            disabled: true,
            disabledText: 'Text In Tooltip',
            hidden: true
        },
    },
    mergeTags: [
        {
            name: 'Merge tags', // Group name 
            items: [
                {
                    value: "*|FIRST_NAME|*", // Text to be inserted
                    text: "First name", // Shown text in the menu
                    label: "Customer's first name" // Shown description title in the menu
                },
                {
                    value: "*|LAST_NAME|*",
                    text: "Last name",
                    label: "Customer's last name"
                },
                
                //Nested Merge Tags
                {
                    name: 'Merge tags nested',
                    items: [
                        {
                            value: "*|FIRST_NAME_NESTED|*",
                            text: "First name 2",
                            label: "Customer's first name 2"
                        },
                        {
                            value: "*|LAST_NAME_NESTED|*",
                            text: "Last name 2",
                            label: "Customer's last name 2"
                        }
                    ]
                }
            ]
        }, {
            name: 'Special links', // Group name 
            items: [
                {
                    value: "<a href=\"*|UNSUBSCRIBE_LINK|*\">Unsubscribe</a>",
                    text: "Unsubscribe",
                    label: "Unsubscribe link"
                },
                {
                    value: "<a href=\"*|WEB_VERSION_LINK|*\">Web version</a>",
                    text: "Web version",
                    label: "Web version link"
                }
            ]
        }, {
            name: 'Special content', // Group name 
            items: [
                {
                    value: "For more details, please visit our <a href=\"https://www.shop.shop\">e-shop</a>!",
                    text: "Visit our site",
                    label: "Call to Action"
                }
            ]
        }
    ],
    googleFonts: [  // List of google fonts to load
        'Roboto',
        'K2D',
        'Mali'
    ],
    fonts: [ // List of all font shown in select box
        {
            'label': 'Roboto', // Label shown to user
            'style': 'Roboto, Tahoma, sans-serif' // CSS style applied with font selected
        },
        {
            'label': 'K2D',
            'style': 'K2D'
        },
        {
            'label': 'Mali',
            'style': 'Mali'
        }
    ],
    
    //Set default font sizes for global settings (values are aslo added to Text Editor)
    //If you want to change font sizes in TextEditor refer to tinyConfig 
    fontSizes: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, ...],

    tinyConfig: {
      //You can modify the custom TinyMCE config here
      //We are using TinyMCE version 5.4.1
      //For more information about the TinyMCE config please refer to official TinyMCE documentation. https://www.tiny.cloud/docs/
    },

    savedBlocks: [
        {
            id: 1,
            name: 'My saved block 001',
            img: 'src to my img', // The editor shows only name or img (img if both set)
            definition: [{"tagName":"mj-section","attributes":{"full-width":false,"padding":"9px 0px 9px 0px","background-color":"#000000"},"type":null,"children":[{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-social","attributes":{"padding":"10px 10px 10px 10px","text-mode":"false","icon-size":"35px","align":"center","containerWidth":198},"children":[{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/facebook.png","name":"facebook-noshare","alt":"facebook","href":"https:\/\/www.facebook.com\/PROFILE","background-color":"transparent"}},{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/twitter.png","name":"twitter-noshare","alt":"twitter","href":"https:\/\/www.twitter.com\/PROFILE","background-color":"transparent"}},{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/instagram.png","alt":"instagram","name":"instagram","href":"https:\/\/www.instagram.com\/PROFILE","background-color":"transparent"}}],"uid":"dAoF8AoOO","style":"simplewhite"}],"uid":"SJ3I0XVx7"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-image","attributes":{"src":"https:\/\/storage.googleapis.com\/jan50\/blackberrylogo.png","padding":"19px 10px 10px 10px","alt":"","href":"","containerWidth":200,"width":100,"widthPercent":50},"uid":"MFT0c-tu6X"}],"uid":"r1e280m4xQ"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-spacer","attributes":{"height":"50px","containerWidth":200},"uid":"0nV_PAUDZ2"}],"uid":"B1W380QVxX"}],"layout":1,"backgroundColor":"","backgroundImage":"","paddingTop":0,"paddingBottom":0,"paddingLeft":0,"paddingRight":0,"uid":"GD8ksoWQzG"}]
        }
    ],
    premadeBlocks: {
        'headers': [
            {
                'img': 'url', // Image url
                'definition': [{ "tagName": "mj-section", "attributes": { "full-width": false, "padding": "9px 0px 9px 0px", "background-color": "#000000" }, "type": null, "children": [{ "tagName": "mj-column", "attributes": { "width": "33.333333%", "vertical-align": "top" }, "children": [{ "tagName": "mj-social", "attributes": { "display": "facebook:url twitter:url google:url", "padding": "10px 10px 10px 30px", "text-mode": "false", "icon-size": "25px", "base-url": "https://s3-eu-west-1.amazonaws.com/ecomail-assets/editor/social-icos/simplewhite/", "facebook-href": "https://www.facebook.com/PROFILE", "facebook-icon-color": "none", "facebook-alt": "Sdílet", "twitter-href": "https://www.twitter.com/PROFILE", "twitter-icon-color": "none", "twitter-alt": "", "google-href": "https://plus.google.com/PROFILE", "google-icon-color": "none", "google-alt": "", "instagram-icon-color": "none", "linkedin-icon-color": "none", "align": "left", "youtube-icon-color": "none", "youtube-alt": "", "youtube-icon": "https://s3-eu-west-1.amazonaws.com/ecomail-assets/editor/social-icos/simplewhite/youtube.png", "youtube-href": "https://www.youtube.com", "containerWidth": 200 }, "uid": "H1lqIiX4lm" }], "uid": "SJ3I0XVx7" }, { "tagName": "mj-column", "attributes": { "width": "33.333333%", "vertical-align": "top" }, "children": [{ "tagName": "mj-image", "attributes": { "src": "https://storage.googleapis.com/jan50/blackberrylogo.png", "padding": "19px 10px 10px 10px", "alt": "", "href": "", "containerWidth": 200, "width": 100, "widthPercent": 50 }, "uid": "rkEyL-HeQ" }], "uid": "r1e280m4xQ" }, { "tagName": "mj-column", "attributes": { "width": "33.333333%", "vertical-align": "top" }, "children": [{ "tagName": "mj-spacer", "attributes": { "height": 15, "containerWidth": 200 }, "uid": "rJfqLiXEgm" }], "uid": "B1W380QVxX" }], "layout": 1, "backgroundColor": "", "backgroundImage": "", "paddingTop": 0, "paddingBottom": 0, "paddingLeft": 0, "paddingRight": 0, "uid": "rkqIjQNe7" } // MJML JSON
                ]
            }]
    },
    // URL or Callback when clicked on Save & close
    callbacks: {
        onSaveAndClose: function (json, html) {
            // HTML of the email
            console.log(html);
            // JSON object of the email
            console.log(json);
            // Implement your own close callback
            // Data variable contains the response data of the save request
        },
        onSave: function (json, html) {
            // HTML of the email
            console.log(html);
            // JSON object of the email
            console.log(json);
        },
        onTestSend: function (email, json, html) {
            // HTML of the email
            console.log(html);
            // JSON object of the email
            console.log(json);
            // Email of the recipient
            console.log(email);
            // Callback when send test email button is clicked
        },
        onOpenFileManager: function () {
            // Implement your own file manager open callback
        },
        onBlockSave(json) {
            var name = window.prompt('Enter block name:')
            if (name !== null) {
                console.log('saving block', json)
            }
        },
        onBlockRemove(id) {
            if (window.confirm('Are you sure?')) {
                console.log('removing block', id)
            }
        },
        onBlockEdit(id) {
            var name = window.prompt('Block name:', 'My block 001')
            if (name !== null) {
                console.log('saving edited block', id)
            }
        },
        onPreview(html) {
          //do something with the html
        },
        onInit() {
          //Called when editor is loaded
        }
    },
    apiAuthorizationHeader: 'Bearer token',
    api: {
        // Your own endpoint for uploading images
        IMAGE_UPLOAD: "/images/upload",
        // Your own endpoint for getting contents of folders
        FOLDERS: "/images/folder-contents",
        // Your own endpoint to retrieve base64 image edited by Image Editor
        IMAGE_EDITOR_UPLOAD: "/images/image-editor-upload",
        // Create Autosave
        AUTOSAVE: "/autosave",
        // Retreive all autosaves
        AUTOSAVES: "/autosaves",
        // Retreive an autosave 
        GET_AUTOSAVE: "/autosave/",
        // Retrieve feeds of products
        FEEDS: "/feeds",
        // Retrieve products from feed
        PRODUCTS: "/products
    }
};

Here is the list of all plugin functions:

TopolPlugin.init(TOPOL_OPTIONS);

// To load JSON format use this load function with the JSON template. STRING FORMAT
TopolPlugin.load('json-template'); 

// To force the save -> the onSave callback will be called with the JSON and HTML of the template
TopolPlugin.save();

// Toggles the mode of Preview
TopolPlugin.togglePreview();

// When the onOpenFileManager is called, it is awaiting to call this function with the url of the chosen file.
TopolPlugin.chooseFile('http://url.to/picture.png'); 

/* Sets the saved blocks - this should be called with updated
list of saved blocks after all actions: onBlockSave, onBlockRemove,
onBlockEdit to update the editor with the updated information.*/
TopolPlugin.setSavedBlocks([
  {
    'id': 11,
    'name': 'My saved block - by setSavedBlocks',
    'definition': [{"tagName":"mj-section","attributes":{"full-width":false,"padding":"9px 0px 9px 0px","background-color":"#000000"},"type":null,"children":[{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-social","attributes":{"padding":"10px 10px 10px 10px","text-mode":"false","icon-size":"35px","align":"center","containerWidth":198},"children":[{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/facebook.png","name":"facebook-noshare","alt":"facebook","href":"https:\/\/www.facebook.com\/PROFILE","background-color":"transparent"}},{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/twitter.png","name":"twitter-noshare","alt":"twitter","href":"https:\/\/www.twitter.com\/PROFILE","background-color":"transparent"}},{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/instagram.png","alt":"instagram","name":"instagram","href":"https:\/\/www.instagram.com\/PROFILE","background-color":"transparent"}}],"uid":"dAoF8AoOO","style":"simplewhite"}],"uid":"SJ3I0XVx7"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-image","attributes":{"src":"https:\/\/storage.googleapis.com\/jan50\/blackberrylogo.png","padding":"19px 10px 10px 10px","alt":"","href":"","containerWidth":200,"width":100,"widthPercent":50},"uid":"MFT0c-tu6X"}],"uid":"r1e280m4xQ"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-spacer","attributes":{"height":"50px","containerWidth":200},"uid":"0nV_PAUDZ2"}],"uid":"B1W380QVxX"}],"layout":1,"backgroundColor":"","backgroundImage":"","paddingTop":0,"paddingBottom":0,"paddingLeft":0,"paddingRight":0,"uid":"GD8ksoWQzG"}]
  },
  {
    'id': 12,
    'img': 'https://d5aoblv5p04cg.cloudfront.net/editor/blocks/menu1.jpg',
    'definition': [{"tagName":"mj-section","attributes":{"full-width":false,"padding":"9px 0px 9px 0px","background-color":"#000000"},"type":null,"children":[{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-social","attributes":{"padding":"10px 10px 10px 10px","text-mode":"false","icon-size":"35px","align":"center","containerWidth":198},"children":[{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/facebook.png","name":"facebook-noshare","alt":"facebook","href":"https:\/\/www.facebook.com\/PROFILE","background-color":"transparent"}},{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/twitter.png","name":"twitter-noshare","alt":"twitter","href":"https:\/\/www.twitter.com\/PROFILE","background-color":"transparent"}},{"tagName":"mj-social-element","attributes":{"src":"https:\/\/s3-eu-west-1.amazonaws.com\/ecomail-assets\/editor\/social-icos\/simplewhite\/instagram.png","alt":"instagram","name":"instagram","href":"https:\/\/www.instagram.com\/PROFILE","background-color":"transparent"}}],"uid":"dAoF8AoOO","style":"simplewhite"}],"uid":"SJ3I0XVx7"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-image","attributes":{"src":"https:\/\/storage.googleapis.com\/jan50\/blackberrylogo.png","padding":"19px 10px 10px 10px","alt":"","href":"","containerWidth":200,"width":100,"widthPercent":50},"uid":"MFT0c-tu6X"}],"uid":"r1e280m4xQ"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-spacer","attributes":{"height":"50px","containerWidth":200},"uid":"0nV_PAUDZ2"}],"uid":"B1W380QVxX"}],"layout":1,"backgroundColor":"","backgroundImage":"","paddingTop":0,"paddingBottom":0,"paddingLeft":0,"paddingRight":0,"uid":"GD8ksoWQzG"}]
      }
])

//Creates notification in editor
TopolPlugin.createNotification({
  title: 'Title of the notification',
  text: 'Important message you want to display',
  type: 'info' // info | error | success
})

Internationalization

List of all currently supported languages:

Languages use ISO 639-1 standard.

CountryLanguageCode
Englishen
Frenchfr
Portuguesept
Spanishes
Japaneseja
Chinesezh
Russianru
Turkishtr
Germande
Swedishsv
Dutchnl
Italianit
Finnishfi
Romanianro
Czechcs
Polishpl
Koreanko
Vietnamesevi
Hebrewhe

It's easy to add another language. Feel free to contact us (get@topol.io).

Merge tags, special links or special content provide an easy way to insert text snippets from within the text editor.

We use following structure for Merge Tags:

*|MERGE_TAG|*

You will find it in the toolbar of the text feature:

Merge tags, special links... You have the ability to use nested merge tags.

Code example:

    mergeTags: [
        {
            name: 'Merge tags', // Group name 
            items: [
                {
                    value: "*|FIRST_NAME|*", // Text to be inserted
                    text: "First name", // Shown text in the menu
                    label: "Customer's first name" // Shown description title in the menu
                },
                {
                    value: "*|LAST_NAME|*",
                    text: "Last name",
                    label: "Customer's last name"
                },
                {
                    name: 'Merge tags nested',
                    items: [
                        {
                            value: "*|FIRST_NAME_NESTED|*", // Text to be inserted
                            text: "First name 2", // Shown text in the menu
                            label: "Customer's first name 2" // Shown description title in the menu
                        },
                        {
                            value: "*|LAST_NAME_NESTED|*",
                            text: "Last name 2",
                            label: "Customer's last name 2"
                        }
                    ]
                }
            ]
        }, {
            name: 'Special links', // Group name 
            items: [
                {
                    value: "<a href=\"*|UNSUBSCRIBE_LINK|*\">Unsubscribe</a>",
                    text: "Unsubscribe",
                    label: "Unsubscribe link"
                },
                {
                    value: "<a href=\"*|WEB_VERSION_LINK|*\">Web version</a>",
                    text: "Web version",
                    label: "Web version link"
                }
            ]
        }, {
            name: 'Special content', // Group name 
            items: [
                {
                    value: "For more details, please visit our <a href=\"https://www.shop.shop\">e-shop</a>!",
                    text: "Visit our site",
                    label: "Call to Action"
                }
            ]
        }
    ],

Custom Google Fonts

You can define two options to customize font loading and selection. To load Google Fonts use the googleFonts options. To update the select box user sees, update the fonts option.

googleFonts: [ // List of google fonts to load
  'Roboto',
  'K2D',
  'Mali'
],
fonts: [ // List of all font shown in select box
  {
     'label': 'Roboto', // Label shown to user
     'style': 'Roboto, Tahoma, sans-serif' // CSS style applied when font selected
  },
  {
     'label': 'K2D',
     'style': 'K2D'
  },
  {
     'label': 'Mali',
     'style': 'Mali'
  }
],

When defining the style option of a font, please keep in mind that font names with space (two or more words), needs to be inside double brackets, for ex. '"Verdana Pro", sans-serif'.

Premade blocks

Premade blocks are used to enable users to use prepared parts of the email. For ex. headers or footers. You can define your own premade blocks with option premadeBlocks on the TOPOL_OPTIONS object. You can hide premade blocks and the premade block button using premadeBlocks: false.

premadeBlocks: {
  'headers': [
    {
      'img': 'url', // Image url, for best experience use width > 330 px
      'name': 'Premade header 1', // Or name if not image available
      // MJML JSON
      'definition': [{"tagName":"mj-section","attributes":{"full-width":false,"padding":"9px 0px 9px 0px","background-color":"#000000"},"type":null,"children":[{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-social","attributes":{"display":"facebook:url twitter:url google:url","padding":"10px 10px 10px 30px","text-mode":"false","icon-size":"25px","base-url":"https://s3-eu-west-1.amazonaws.com/ecomail-assets/editor/social-icos/simplewhite/","facebook-href":"https://www.facebook.com/PROFILE","facebook-icon-color":"none","facebook-alt":"Sdílet","twitter-href":"https://www.twitter.com/PROFILE","twitter-icon-color":"none","twitter-alt":"","google-href":"https://plus.google.com/PROFILE","google-icon-color":"none","google-alt":"","instagram-icon-color":"none","linkedin-icon-color":"none","align":"left","youtube-icon-color":"none","youtube-alt":"","youtube-icon":"https://s3-eu-west-1.amazonaws.com/ecomail-assets/editor/social-icos/simplewhite/youtube.png","youtube-href":"https://www.youtube.com","containerWidth":200},"uid":"H1lqIiX4lm"}],"uid":"SJ3I0XVx7"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-image","attributes":{"src":"https://storage.googleapis.com/jan50/blackberrylogo.png","padding":"19px 10px 10px 10px","alt":"","href":"","containerWidth":200,"width":100,"widthPercent":50},"uid":"rkEyL-HeQ"}],"uid":"r1e280m4xQ"},{"tagName":"mj-column","attributes":{"width":"33.333333%","vertical-align":"top"},"children":[{"tagName":"mj-spacer","attributes":{"height":15,"containerWidth":200},"uid":"rJfqLiXEgm"}],"uid":"B1W380QVxX"}],"layout":1,"backgroundColor":"","backgroundImage":"","paddingTop":0,"paddingBottom":0,"paddingLeft":0,"paddingRight":0,"uid":"rkqIjQNe7"}]
    }
  ]
}

List of available categories

  • headers
  • content
  • ecomm // E-commerce blocks
  • footers

Saved blocks

Users can save their sections by clicking save button while moving mouse over section. To enable this feature set savedBlocks to [], to disable it don't set the value at all or set it to null You can hide saved blocks and the premade block button using premadeBlocks: false a savedBlocks: false.

savedBlocks: []

Each block has the following structure:

{
  id: 1, // ID of the block, if not set it uses the Array Index
  name: 'My saved block 001', // You can set a name or an image
  img: 'http://...', // The editor shows only name or image, not both, if both set it will show the image
  definition: 'json',
}

There are three actions you need to implement to make saved blocks work: onBlockSave, onBlockEdit and onBlockRemove. All of them needs to be implement on callbacks object.

Example:

onBlockSave(json) {
  var name = window.prompt('Enter block name:')
  if (name !== null) {
    console.log('saving block', json)
  }
},
onBlockRemove(id) {
  if (window.confirm('Are you sure?')) {
    console.log('removing block', id)
  }
},
onBlockEdit(id) {
  var name = window.prompt('Block name:', 'My block 001')
  if (name !== null) {
    console.log('saving edited block', id)
  }
}

Since it's your responsibility to take care of those actions, you also need to update the list of saved blocks once updated. To do so you the following function:

TopolPlugin.setSavedBlocks([{
  id: 1, // ID of the block, if not set it uses the Array Index
  name: 'My saved block 001', // You can set a name or an image
  img: 'http://...', // The editor shows only name or image, not both, if both set it will show the image
  definition: 'json',
}]);

This function takes an array of all saved block you want to show within the editor.

Light theme

By setting an option light: true you will switch the editor design to light theme.

Dark theme Light theme

By setting an option mainMenuAlign: "right" you will get the main menu on the right side of the builder.

Main menu light align

Remove Top Bar

Top bar

If you decide to create your own save & close buttons, you can completely remove the top bar from the editor, so you don't have to be limited by our own interface, to do so use: removeTopBar: true.

Top Bar Options

If you want to hide only some elements in top bar you can do so by using an option topBarOptions: []. You can add these elements to display them:

  • "undoRedo" Shows undo/redo button

  • "changePreview" Shows Change preview button

  • "previewSize" Shows toggle mobile/desktop preview button

  • "previewTestMail" Shows sending testing email button

  • "save" Shows save button

  • "saveAndClose" Shows Save & Close button

Default value is [].

Empty array means all elements are visible.

On Test Send

When the user clicks on Send Test (The icon). The template is rendered and then the callback is called. The test input can be hidden by emitting previewTestMail in Top Bar Options.

onTestSend: function(email, json, html) {
    console.log(html);
    console.log(json);
    // Email of the recipient
    console.log(email);
},

Preview mode on desktop and mobile

When top bar is removed, You can access the preview mode using the function:

TopolPlugin.togglePreview(); // Toggles the mode of Preview

Show custom notification in editor

This is useful when you want to use built in notifications in the editor. You can create notification with following function:

TopolPlugin.createNotification({
  title: 'Title of the notification',
  text: 'Important message you want to display',
  type: 'info' // info | error | success
});

Default notification type is info.

Redo and Undo

You can apply redo and undo function on the TopolPlugin object.

TopolPlugin.undo();
TopolPlugin.redo();

On Init

Called when Editor is loaded. Can be useful for implementing custom loading before Editor is loaded.

onInit() {}

Content Blocks Options

This option provides a way of working with content blocks. You can disable a block, hide it completely or provide tooltip for disabled content block.

You have an option to modify following content blocks:

text, image, gif, button, divider, spacer, social, video, html, product

contentBlocks: {
    text: {
        disabled: true,
        disabledText: 'Text In Tooltip',
        hidden: true
    },
}

Image Editor

There is built-in font picker in image editor, to use its full potentional, you have to provide Google Fonts Api Token.

googleApiKey: 'YOUR_API_TOKEN'

Save and load options

Self-stored templates

You can find predefined templates below (click to show the JSON code). Use the "start from scratch" for new blank template:

templates
TopolPlugin.load("{\"tagName\":..."); // Load json template from STRING format

API load example:

fetch('https://tlapi.github.io/topol-editor/templates/1.json')
    .then(response => response.text())
    .then(template => {
        //Load the template in Editor
        TopolPlugin.load(template)
    }
);

Inline load example:

TopolPlugin.load(JSON.stringify({
    "tagName": "mj-global-style",
    "attributes": {
        "h1:color": "#000",
        "h1:font-family": "Helvetica, sans-serif",
        "h2:color": "#000",
        "h2:font-family": "Ubuntu, Helvetica, Arial, sans-serif",
        "h3:color": "#000",
        "h3:font-family": "Ubuntu, Helvetica, Arial, sans-serif",
        ":color": "#000",
        ":font-family": "Ubuntu, Helvetica, Arial, sans-serif",
        ":line-height": "1.5",
        "a:color": "#24bfbc",
        "button:background-color": "#e85034",
        "containerWidth": 600,
        "fonts": "Helvetica,sans-serif,Ubuntu,Arial",
        "mj-text": {
            "line-height": 1.5,
            "font-size": 15
        },
        "mj-button": []
    },
    "children": [
        {
            "tagName": "mj-container",
            "attributes": {
                "background-color": "#FFFFFF",
                "containerWidth": 600
            },
            "children": [
                {
                    "tagName": "mj-section",
                    "attributes": {
                        "full-width": false,
                        "padding": "9px 0px 9px 0px"
                    },
                    "children": [
                        {
                            "tagName": "mj-column",
                            "attributes": {
                                "width": "100%"
                            },
                            "children": [],
                            "uid": "HJQ8ytZzW"
                        }
                    ],
                    "layout": 1,
                    "backgroundColor": null,
                    "backgroundImage": null,
                    "paddingTop": 0,
                    "paddingBottom": 0,
                    "paddingLeft": 0,
                    "paddingRight": 0,
                    "uid": "Byggju-zb"
                }
            ]
        }
    ],
    "style": {
        "h1": {
            "font-family": "\"Cabin\", sans-serif"
        }
    },
    "fonts": [
        "\"Cabin\", sans-serif"
    ]
})); // Load json template from STRING format

onSave and onSaveAndClose

When the user clicks on Save or on Save & Close button, the SAVE API endpoint is called and then the onSave/onSaveAndClose is called.

You can insert a callback that is called right after the SAVE API endpoint is called. The callback have a data attribute that contains the data of the response.

onSave: function(json, html) {
    console.log(html);
    console.log(json);
},
onSaveAndClose: function(json, jtml) {
    console.log(html);
    console.log(json);
}

Custom File Manager

customFileManager

If you want to disable our built-in File Manager just define a customFileManager: true in the options object. Then the editor will use the below functions.

onOpenFileManager

When the user clicks on Choose a file (ex. as a property of an image). This callback is called. For ex. you can develop your own file manager dialog.

onOpenFileManager: function() {
    // Open your file manager and let user choose a file.
},

chooseFile

When a user chooses a file, you need to call chooseFile function on the TopolPlugin instance. with the url of the file.

TopolPlugin.chooseFile('http://url.to/picture.png');

Custom storage

We provide built-in solution for storage with AWS S3 and Google Cloud Storage. You can, however use your own storage as described below.

Tutorial - How to Integrate custom AWS
Tutorial - How to Integrace custom Google Cloud Storage
Self-hosted options

Custom API endpoints

Using a CUSTOM API endpoint can be useful in various scenarios.

When it is necessary to use Custom API Endpoints.

  • Custom self-hosted file storage
  • Using Auto-saves
  • Working with feeds and products

Security

We offer a possibility to add an authorization header to the custom API endpoints. You can insert a token using following option in TOPOL_OPTIONS. This authorization token will be added to all ongoing requests to your server.

  apiAuthorizationHeader: 'Bearer token',

CORS issues

When working with custom endpoints don't forget to allow our server d5aoblv5p04cg.cloudfront.net as a known origin. Otherwise you are going to run into CORS issues.

Self-hosted storage

If our integrated storage providers don't fit your needs we offer an ability to use your self-hosted storage using various API endpoints. This way we offer a possibility to use our integrated File Manager together with your self-hosted images.

FOLDERS

Retrieve Images

Method: GET

Called when File Manager opens. This call used for retrieving files and folders. with these GET parameters path, userId, uuid

Response:

[{
    "name": "filename", 
    "date": last-date-modified,
    "size": 500,
    "path": "/path/",
    "type": "file" OR "folder",
    "extension": ".jpg",
    "url": "https://url-to-image.com/image.jpeg"
}]

Add Images

Method: POST

Called when folder is added to the filemanager.

Body:

name: folder name where the images is added path: new image directory

Response:

[{
  "url": "url_of_uploaded_image"
}]

Delete Images

Method: POST

We append /delete to the api.FOLDER URL.

Body: Array of directories to be removed

Expected Response is status code 200 or 204.

IMAGE_UPLOAD

Method: POST

Called when the user wants to upload a new file with the request containing path and image data. This endpoint is used even when user drops image to the image block.

Response:

Content-Type:application/json

{
  "success": true,
  "url": "http://191n.mj.am/img/191n/1t/hs.png"
}

IMAGE_EDITOR_UPLOAD

Method: POST

You should use this endpoint when retriving edited image from integrated image editor.

Request:

Content-Type:application/json

{
  "content": "base64 enconded image",
  "filename": "Name of the file"
}

Expected Response:

{ 
  "url": "https://url-to-image.com/image.jpeg"
}

Autosaves

You can take advantage of built-in autosaves to provide better experience for your users.

First you need to enable the autosaves on options using enableAutosaves: true

AUTOSAVES

Method: GET

This endpoint is called when retrieving all autosaves.

Params:

Content-Type:application/json

{
  "key": "api key",
  "hostname": "origin"
  "templateId": "id of the template",
  "uuid": "userId"
}

Expected Response:

{
  success: true,
  data: [
    {
      uid: 'userID',
      key: 'autosaveKey', //used in GET_AUTOSAVE param when retrieving,
      time: 'ISO 8601 time'
    }
  ]
}

AUTOSAVE

Method: POST

This endpoint is called when autosave is created.

You should use this data to save JSON of the template, time when autosave was created, user id, and unique autosave identifier. This data are then used when retrieving autosave.

Request:

Content-Type:application/json

{
  "definition": "MJML in JSON",
  "key": "api key",
  "hostname": "origin"
  "templateId": "id of the template",
  "uuid": "userId"
}

Expected Response: STATUS 200

GET_AUTOSAVE

Method: GET

This endpoint is called when retrieving created autosave.

This endpoint appends '/{{ key }}'

Params:

Content-Type:application/json

{
  "key": "api key",
  "hostname": "origin"
  "templateId": "id of the template",
  "uuid": "userId"
}

Expected Response:

{
  success: true,
  data: "{"tempalte":"jsonTemplate"}"
}

Product from feeds

We provide an option to use predefined products and display them in a dropdown so your users can simply select a product and display all the important data.

PRODUCT

Method: GET

This endpoint is called when retrieving products from FEED.

Params:

Content-Type:application/json

{
  "id": "productID",
  "search": "search query string",
  "per_page": "expected products per page" //default 10
  "current_page": "current page of paginated products",
  "feed": "active FEED id"
}

Expected Response:

{
  success: true,
  data: [
    {
      id: 'product id',
      name: 'name of the product',
      description: 'description of the product',
      url: 'link to the product',
      img_url: 'link to an image of the product',
      price_with_vat: 'price of the product including VAT', //without currency
      currency: 'currency of the price',
      price_before: 'original price after product is discounted',
      product_feed_id: 'id of the feed product belongs to'
    }
  ],
  //pagination helpers
  from: "from id of the resource",
  to: "to ide of the resource",
  total_records: "total records of the resource",
  per_page: "resource per page",
  current_page: "current page of the resource",
  last_page: "last page of the resource",
}

FEEDS

Method: GET

This endpoint is called when retrieving FEEDS of products.

Params:

Content-Type:application/json

{
  "id": "feed ID",
  "search": "search query string",
  "per_page": "expected feeds per page" //default 10
  "current_page": "current page of paginated feeds",
}

Expected Response:

{
  success: true
  data: [
    {
      id: 'id of the feed',
      name: "name of the feed"
    }
  ],
  //pagination helpers
  from: "from id of the resource",
  to: "to ide of the resource",
  total_records: "total records of the resource",
  per_page: "resource per page",
  current_page: "current page of the resource",
  last_page: "last page of the resource",
}

Miscellaneous

Domains used by TOPOL.io plugin

What domains are used by the plugin? Sometimes there could be an issue caused by firewall on your clients network, so they may need to add the domains to a whitelist. TOPOL.io plugin is using following domains:

d5aoblv5p04cg.cloudfront.net
fonts.googleapis.com

Flags made by Freepik from www.flaticon.com