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.
- Integrate Topol Plugin in 2 easy steps
- Save and load your templates
- Advanced Plugin configuration
- Custom storage - AWS or GCS
- Custom file manager
- Custom API endpoints
Plugin integration
Step 1. Create API token
In your Topol Account go to Settings -> Plugin and create an API token.
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 Code | New Code |
---|---|
jp | ja |
se | sv |
fi | fin |
iw | he |
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.
Country | Language | Code |
---|---|---|
![]() ![]() | English | en |
![]() | French | fr |
![]() ![]() | Portuguese | pt |
![]() | Spanish | es |
![]() | Japanese | ja |
![]() | Chinese | zh |
![]() | Russian | ru |
![]() | Turkish | tr |
![]() | German | de |
![]() | Swedish | sv |
![]() | Dutch | nl |
![]() | Italian | it |
![]() | Finnish | fi |
![]() | Romanian | ro |
![]() | Czech | cs |
![]() | Polish | pl |
![]() | Korean | ko |
![]() | Vietnamese | vi |
![]() | Hebrew | he |
It's easy to add another language. Feel free to contact us (get@topol.io).
Merge tags Special links Special content
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:
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.
Main menu align
By setting an option mainMenuAlign: "right"
you will get the main menu on the right side of the builder.
Remove 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
},
}
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 Image 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: 'discounted price of the product',
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