Skip to content

Instantly share code, notes, and snippets.

@xagronaut
Last active August 10, 2022 09:43
Show Gist options
  • Save xagronaut/da5ff3e4242298c7594e706cad73bfe7 to your computer and use it in GitHub Desktop.
Save xagronaut/da5ff3e4242298c7594e706cad73bfe7 to your computer and use it in GitHub Desktop.
Generate email links complete with subject and body from custom templates. It uses AngularJS to complete forms, show message preview, and create a mailto link that launches the custom content in your default mail client. You can save generated links as bookmarks, embed in documents, or email them to others!
<!DOCTYPE html>
<html lang='en'>
<head>
<!-- Author: Jeffrey A. Miller, @xagronaut -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- jQuery (CDN) -->
<!-- jQuery v2.x (CDN) -->
<!-- <script type='text/javascript' src='https://code.jquery.com/jquery-2.1.4.js'></script> -->
<!-- jQuery v1.x (CDN) (for Internet Explorer compatibility) -->
<script type='text/javascript' src='https://code.jquery.com/jquery-1.11.3.js'></script>
<script type='text/javascript' src='https://code.jquery.com/ui/1.11.3/jquery-ui.js'></script>
<!-- Angular (CDN) -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>
<!-- Bootstrap (CDN) -->
<script type='text/javascript' src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js'></script>
<link type='text/css' rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css' />
<style type='text/css'>
body {
padding: 15px;
background-color: #FFFFFF;
}
label.inputLabel {
font-weight: bold;
min-width: 150px;
vertical-align: top;
word-wrap: break-word;
display: inline-block;
}
label.inputLabel span.fieldName {
font-size: x-small;
}
span.inputField {
vertical-align: top;
padding-bottom: 3px;
display: inline-block;
}
span.inputField input[type="text"], span.inputField textarea {
font-size: 10pt;
min-width: 250px;
}
span.renderedField {
/*margin-left: 150px;*/
vertical-align: top;
padding-bottom: 3px;
display: inline-block;
word-wrap: break-word;
}
textarea#mailBody {
min-width: 250px;
width: 98%;
max-width: 600px;
}
.bodyPreview {
/* no additional styles needed at this time */
}
table.renderedFieldTable th {
min-width: 150px;
}
.renderedFieldTable tr td, .renderedFieldTable tr th {
vertical-align: top !important;
padding-bottom: 1em;
}
.renderedFieldTable tr:last td, .renderedFieldTable tr:last th {
padding-bottom: initial !important;
}
h3 {
/* text-decoration: underline; */
border-bottom: 2px solid #999999;
}
div.inputForm {
/*line-height: 2.5em;*/
max-width: 650px;
/* background-color: #FFFFE0; */
background-color: #FAFAD2;
/* background-color: #EEE8AA; */
/* background-color: #D2B48C; */
/* background-color: #FFFFA5; */
border-radius: 5px;
border: 1px solid gray;
padding: 12px;
overflow-x: auto;
}
/* Help with dragging outside of the container */
.dragParent {
overflow: visible !important;
}
.fieldHandle.ui-draggable {
background-color: #FAFAD2;
/* background-color: #FFFFF0; */
border: 2px dashed darkred;
padding: 6px;
margin: 6px;
display: inline-block;
whitespace: nowrap;
}
.fieldHandle.ui-draggable-dragging {
box-shadow: 4px 4px #AAAAAA;
}
.ui-droppable-hover {
border-left: 4px solid darkred;
}
#specialFields ul {
margin-left: 0px;
padding: 5px;
background-color: #EEE;
border: 1px solid #999;
}
#specialFields ul li {
list-style: none;
margin-left: 0px;
}
span.fieldTip:after {
content: "?";
padding: 5px;
background-color: lightyellow;
border: 1px solid gray;
border-radius: 3px;
font-size: 8px;
margin: 3px;
width: 10px;
}
div#messagePreview {
/* background-color: #FAFAFA; */
background-color: #FFFFF0;
padding: 10px;
border-radius: 5px;
border: 1px solid gray;
box-shadow: 2px 2px 2px #999999;
margin-bottom: 10px;
}
div#tokenList {
background-color: #FFFFF0;
}
.description-block {
word-wrap: break-word;
}
pre.display-block {
word-wrap: break-word;
}
table.referenceTable {
border-spacing: 3px;
border-collapse: collapse;
}
table.referenceTable tr th {
line-height: 2em;
}
table.referenceTable tr th {
padding-top: 4px;
padding-right: 10px;
}
.validation-inline {
padding-left: 5px;
font-weight: bold;
font-style: italic;
vertical-align: top;
}
input.ng-valid-pattern::before {
content: "Valid";
}
input.ng-invalid {
color: red;
}
input.ng-invalid + span.validation-inline::before {
}
input.ng-invalid-required ~ span.validation-inline::before,
textarea.ng-invalid-required ~ span.validation-inline::before {
content: "* required";
}
input.ng-invalid-pattern ~ span.validation-inline::before {
content: "* invalid format";
}
</style>
<!--
# Replacement Tokens - enables reuse of logical recipient lists. If the list of "Developers" or "Testers" ever changes, you can update them in just one place.
The following script tag (with id="replacementTokens") holds tokens that will be expanded later in the templates defined.
## Format
1. Token - the leftmost text (a name, without spaces), followed by a colon (":") and a space.
2. Replacement text - the text with which to replace the token; this can contain other previously defined tokens
### Note
When using Outlook (and possibly other mail programs), make sure to separate the names of recipients with a semicolon (";").
It is also recommended to follow single recipient names with a semicolon (";").
### Note
As currently implemented, you can reference other tokens in a token definition, but they must already be defined above the reference.
### Note
You can reference these tokens in the to, cc, subject, and body fields.
-->
<script id="replacementTokens" type="text/customdata" data-note="Please keep this section flush to the margin (no leading whitespace).">
Developers: Devs@acme.com;
BusinessAnalysts: BA@Acme.com;
ProjectManager: Project, Polly;
Leads: Tech.Lead@acme.com; HelpDesk.Manager@acme.com; {{ProjectManager}}
TeamManagers: Supervisor, Cindy;
Testers: Tester1@acme.com; Tester2@acme.com;
DMLDeployers: DML.Deployer@acme.com;
CodeDeployers: Deployer, Danny;
DeploymentCC: {{DMLDeployers}} {{Developers}}
TechLead: TeamLeader, Thomas;
DBAs: DBAs@acme.com;
EmailSupport: Email.Support@acme.com;
Operators: Ops@acme.com;
OperationsManager: Ops.Lead@acme.com;
HelpDeskStaff: HelpDesk@acme.com;
HelpDeskManager: HelpDesk.Manager@acme.com;
WholeTeam: {{Developers}} {{Testers}} {{BusinessAnalysts}} {{Leads}} {{TeamManagers}} {{HelpDeskStaff}} {{HelpDeskManager}}
FeatureToggleTo: {{Developers}} {{Testers}} {{BusinessAnalysts}} {{Leads}}
FeatureToggleCC: {{TeamManagers}}
</script>
<script type='text/javascript'>
(function() {
var app = angular.module('EmailLinkBuilder', []);
String.prototype.asUriEncoded = function() {
return encodeURIComponent(this).replace(/\r?\n/g, '%0D%0A');
}
app.controller('EmailLinkBuilderCtrl', ['$scope', '$sce', EmailLinkBuilderCtrl]);
function EmailLinkBuilderCtrl($scope, $sce) {
var noneSelectedText = 'None selected';
var unselectedTemplate = {
title: noneSelectedText,
description: '',
to: [noneSelectedText],
cc: [noneSelectedText],
subject: noneSelectedText,
body: noneSelectedText,
fields: {}
};
var replacementTokens = $scope.replacementTokens = {};
$scope.templates = [
//unselectedTemplate
];
function parseReplacementTokens() {
$('script#replacementTokens').each(function(){
var tokenList = $(this).html(), tokenLines = tokenList.split('\n');
// populate token object
for(var i = 0; i < tokenLines.length; i++) {
var parsedTokenLine = (tokenLines[i] || '').match(/^\s*(\S+):\s*(.+)$/),
tokenName = (parsedTokenLine && parsedTokenLine.length >= 2 && parsedTokenLine[1]),
tokenText = (tokenName && parsedTokenLine.length >= 3 && parsedTokenLine[2]),
replacementText = tokenText || '';
// resolve token substitutions
var regexResult;
//var tokenReferenceRegex = /{{(\S+)}}/g;
//while((regexResult = tokenReferenceRegex.exec(replacementText)) != null) {
while((regexResult = /{{(\S+)}}/g.exec(replacementText)) != null) {
var tokenReference = (regexResult.length && regexResult.length >= 2 && regexResult[1]);
var registeredValue = (replacementTokens[tokenReference] || '{{Undefined replacement: ' + tokenReference + ' }}');
replacementText = replacementText.split('{{' + tokenReference +'}}').join(registeredValue);
}
(tokenName && tokenText && (replacementTokens[tokenName] = replacementText));
}
});
}
function parseTemplates() {
$('script.emailTemplate').each(function(){
var scriptContents = $(this).html(), lines = scriptContents.split('\n');
function basicKeyWordRegex(keyword) { return "^" + keyword + "(?:\\s*:\\s*)([^\\s].*)$"; }
var template = {
title : "Not set",
description: "",
to: "Not set",
cc: "Not set",
subject: "Not set",
body: "",
fields : {}
};
var basicKeywords = ["title", "description", "to", "cc", "subject"];
var keywords = {};
basicKeywords.map(function(item) { keywords[item] = basicKeyWordRegex(item); });
keywords["body"] = "^body\s*:\s*$";
keywords["fields"] = "^_fields_\s*$";
var sectionTerminator = /^-----*$/;
var lineIndex = 0, line = (lines.length == lineIndex && 'EOF') || lines[lineIndex], mode ='start';
while(line != 'EOF') {
var regex, result;
for(var key in keywords) {
regex = new RegExp(keywords[key]);
if(result = regex.exec(line)) {
mode = key;
break;
}
}
switch(mode) {
case "description":
template[mode] = $sce.trustAsHtml(result[1]);
break;
case "title":
case "subject":
template[mode] = result[1];
break;
case "to":
case "cc":
template[mode] = [result[1]];
break;
case "fields":
while((lines.length > lineIndex + 1)
&& !sectionTerminator.test(line = lines[++lineIndex])
) {
var fieldRegex = /^(\S+)\s*:\s*(\S.*)$/gi;
var fieldParts;
if(fieldParts = fieldRegex.exec(line)) {
var fieldName = (fieldParts.length > 1 && fieldParts[1]) || null;
var fieldParameters = (fieldParts.length > 2 && fieldParts[2] && fieldParts[2].split(/\s*\|\s*/)) || [];
fieldName && fieldName.length && (template.fields[fieldName] = { name : fieldName });
// parse field label and optional tip
var fieldLabelParts, fieldLabel, fieldTip;
fieldName && fieldParameters.length > 0 && (fieldLabelParts = /^(?:(\S.*?)(?:\s*--\s*(.+)?)?)\s*$/.exec(fieldParameters[0]));
fieldLabelParts && fieldLabelParts.length > 1 && (fieldLabel = fieldLabelParts[1]);
fieldLabelParts && fieldLabelParts.length > 2 && (fieldTip = fieldLabelParts[2])
// populate field label and tip properties
fieldLabel && fieldLabel.length && (template.fields[fieldName].label = fieldLabel);
fieldTip && fieldTip.length && (template.fields[fieldName].tip = fieldTip);
// value and placeholder
fieldName && fieldParameters.length > 1 && (template.fields[fieldName].value = replaceBuiltInFieldTokens(fieldParameters[1]));
fieldName && fieldParameters.length > 2 && (template.fields[fieldName].placeholder = fieldParameters[2]);
// parse named parameters
if(fieldName && fieldParameters.length > 3) {
var namedParameters = fieldParameters[3].split(/;/);
if(namedParameters && namedParameters.length) {
for(var i = 0; i < namedParameters.length; i++) {
var nameValuePair = /(?:\s*(\S+)\s*:\s*(.+)\s*)/i.exec(namedParameters[i]);
// parse out the names and values
if(nameValuePair && nameValuePair.length > 2) {
var namedParameterName = nameValuePair[1], namedParameterValue = nameValuePair[2];
switch(namedParameterValue) {
case "true": namedParameterValue = true; break;
case "false": namedParameterValue = false; break;
}
template.fields[fieldName][namedParameterName] = namedParameterValue;
}
}
}
}
}
// Note (JM, 04/19/2016): The lineIndex will increment next iteration??
}
break;
case "body":
while(lines.length > lineIndex + 1){
line = lines[++lineIndex];
template.body += line + '\n';
}
line = 'EOF';
break;
case "start":
// just keep going...
break;
}
// read next line if available
line = (++lineIndex >= lines.length && 'EOF') || lines[lineIndex];
}
template.title && template.title != "Not set" && $scope.templates.push(template);
});
}
parseReplacementTokens();
parseTemplates();
$scope.selectedTemplate = $.extend(true, {}, unselectedTemplate);
$scope.selectTemplate = function() {
$.extend($scope, $scope.selectedTemplate);
setTimeout(function() {
$('span.fieldHandle').draggable({
revert: true,
cursor: 'move',
containment: 'window',
helper: 'clone'
});
$('.receivesTokens').droppable({
accept : '.fieldHandle',
activeClass : 'ui-droppable-hover',
drop: function(event, ui) {
console.dir(ui);
debugger;
var fieldToken = ui.draggable.text();
if(['INPUT', 'TEXTAREA'].indexOf(this.tagName.toUpperCase()) >= 0) {
$(this).val($(this).val() + fieldToken);
}
$(this).change();
},
tolerance: 'pointer'
});
}, 1);
};
function replaceBuiltInFieldTokens(startingContent) {
var content = startingContent;
if(content && content.length && content.indexOf('{{Today}}') >= 0) {
content = content.replace('{{Today}}', (new Date()).toISOString().substring(0,10));
}
for(var tokenName in replacementTokens) {
var tokenValue = replacementTokens[tokenName];
if(content && typeof content !== "string") {
console.dir(content);
debugger;
}
(tokenValue && content && (content = (content.split('{{' + tokenName + '}}').join(tokenValue))));
}
return content;
}
function replaceFieldTokens(startingContent) {
var fieldDefinitions = ($scope.fields || {});
var content = replaceBuiltInFieldTokens(startingContent);
for(var field in fieldDefinitions) {
var fieldDefinition = fieldDefinitions[field];
var fieldTag = '{{'+ field + '}}';
var fieldValue = fieldDefinition.value;
if(fieldDefinition.useCheckbox && fieldDefinition.formatText) {
var formatOptions = (fieldDefinition.formatText + '//').split('/');
fieldValue = (fieldValue === true) ? formatOptions[0] : formatOptions[1]
}
var fieldHasValue = (fieldValue !== undefined && fieldValue !== null && fieldValue !== '');
(fieldHasValue && (content = content.split(fieldTag).join(fieldValue)));
}
return content;
}
$scope.getMailToLink = function() {
function joinIfArray(val, joinChar) {
return Array.isArray(val) ? val.join(joinChar) : val;
}
return 'mailto:' + replaceFieldTokens((Array.isArray($scope.to) ? $scope.to[0] : $scope.to)) +
'?'
+ (($scope.subject && ('subject=' + $scope.replaceSubjectTokens().asUriEncoded())) || '')
+ (($scope.to && ('&to=' + replaceFieldTokens(joinIfArray($scope.to, ";")).asUriEncoded())) || '')
+ (($scope.cc && ('&cc=' + replaceFieldTokens(joinIfArray($scope.cc, ";")).asUriEncoded())) || '')
+ (($scope.body && ('&body=' + $scope.replaceBodyTokens().asUriEncoded())) || '');
};
$scope.containsTokens = function(whichField) {
var target = $scope[whichField] || '';
function hasTokenReferences(val) {
return (val && !Array.isArray(val) && val.search(/{{\S+}}/) != -1);
}
if(target && Array.isArray(target)) {
var hasTokens = false;
target.forEach(function(item, index){ hasTokens = hasTokens || hasTokenReferences(item); });
return hasTokens;
}
return hasTokenReferences(target);
}
$scope.replaceRecipientTokens = function(whichField) {
var target = $scope[whichField];
if(target && Array.isArray(target)) {
return target.map(function(item, index) { return replaceFieldTokens(item); }).join(';');
}
return replaceFieldTokens(target);
};
$scope.replaceSubjectTokens = function() {
return replaceFieldTokens($scope.subject);
};
$scope.replaceBodyTokens = function() {
return replaceFieldTokens($scope.body);
};
$scope.hasFields = function() {
return ($scope.fields && Object.keys($scope.fields).length > 0);
};
$scope.to = unselectedTemplate.to;
$scope.cc = unselectedTemplate.cc;
$scope.title = unselectedTemplate.title;
$scope.subject = unselectedTemplate.subject;
$scope.body = unselectedTemplate.body;
$scope.fields = unselectedTemplate.fields;
}
}());
</script>
<!--
*******
* Begin email templates
*******
# Requirements for new templates
All email templates must meet the following requirements:
1. They must be contained *within* this HTML file; while it may be tempting to put them outside the file, this doesn't work. Most browsers won't retrieve the text if the "type" attribute is non-standard.
2. They must have the class "emailTemplate". This helps the script code identify an email template.
3. They must have the type "text/template". This causes the browser to ignore the contents since it doesn't recognize the type.
# Standard fields
* title: This is the name of the email template as it appears in lists for selection. It doesn't appear in the final message.
* to: email recipients
* cc: email cc recipients
* subject: the email's subject matter (replacement tokens can be used)
# Custom fields
After specifying the standard fields, you may optionally add custom fields for use as text replacement tokens in the final message.
## Starting the custom fields section
The custom fields section begins with "_fields_" all by itself on the line, then each custom field follows on subsequent lines.
Example:
_fields_
FirstName: First Name | | person's first name
LastName: Last Name | | person's last name
SenderName: Sender Name | | person sending the message (i.e. your name)
----
## Custom field format
Example:
MySpecialField: My Special Field -- special instructional hint text | really special default | Enter something cool | useTextArea: true
^^ field name^: ^^ field label ^^ ^^ help text ^^ ^^ default value ^^ ^^ placeholder ^^ ^^ optional parameters ^^
### Field name
The field name (without spaces) followed by a colon (":"). This is the name that will be used to replace the text when the template is converted to the final message.
### Field settings
After the field name and the colon (":"), several field settings are specified using values separated by "|" characters. The positions are as follows:
1. Field label - The label text you want to appear with the textbox on the form. Spaces are encouraged to separate words.
a. Optional help text can be specified by following the field label with " -- " and a bit of text for the user with special instructions
2. Default value (optional) - This is the value entered into the textbox by default when using the template.
3. Placeholder text (optional) - Hint text for recommended values can be displayed before any data is entered into the textbox.
4. Special parameters (optional) - Optional parameters (such as "useTextArea: true" for multi-line text entry)
Note: Optional parameters after the field label my be left blank between the pipe ("|") characters if a later positional parameter is specified.
Example: you may leave the default value parameter as a blank between two pipes "| |" if you need to specify a placeholder but don't want a default value.
## Ending the custom fields section
The custom fields section ends with "----" all by itself on the line after all custom fields have been specified.
# Message Body
The message body is the last part of the template's script tag. It begins with the text "body:" on a line by itself. All subsequent lines are part of the message.
-->
<script class='emailTemplate' type='text/template'>
title: Database request
description: Use this email template when requesting database changes that require DBA assistance.
to: {{DBAs}}
cc: {{Operators}} {{TeamManagers}} {{TechLead}}
subject: Database request
_fields_
IssueNumber: Issue Number | | Example: ISS123403
ServerName: Server Name | | Examples: OURDEVDB01, OURTESTDB03
Instructions: Instructions | | | useTextArea: true
SenderName: Sender Name
----
body:
DBAs,
Per Ticket Helper issue {{IssueNumber}}, please make the following changes to {{ServerName}}.
{{Instructions}}
Thanks,
{{SenderName}}
</script>
<script class='emailTemplate' type='text/template'>
title: Code Deployment Request
to: {{Deployment}}
cc: {{TechLead}}
subject: Please deploy {{CodeBranch}} code to {{Environment}}
_fields_
CodeBranch: Code Branch | Dev
Environment : Environment | Dev
ApplicationName : Application(s) | | (example: Core Framework)
UserStories : User Stories -- List user story numbers separated by comma, or one per line if title is included | | List user story numbers with optional titles | useTextArea : true
SenderName: Sender Name
----
body:
Please deploy the {{CodeBranch}} code for the following application(s) to the {{Environment}} environment:
Application(s): {{ApplicationName}}
User Stories:
{{UserStories}}
Thanks,
{{SenderName}}
</script>
<script class='emailTemplate' type='text/template'>
title: Access to Team mailbox
to: messaging@acme.com;
cc: {{TeamLeaders}} {{TeamManagers}} {{ProjectManager}}
subject: Please add {{AccountOwner}} to the Team mailbox
_fields_
AccountOwner: Account Owner | | Example: So AndSo
NetworkAccount: Network Account | | Example: ACME\imauser
EmailAddress: Email Address | | Example: So.AndSo@acme.com
SenderName: Sender Name
----
body:
Please add {{AccountOwner}} ({{NetworkAccount}}, {{EmailAddress}}) to the Team Email group to allow access the TEAM mailbox.
Thanks,
{{SenderName}}
</script>
<script class='emailTemplate' type='text/template'>
title: Make custom link
description: Use this template to create a custom mailto: link to embed in other applications. When you are finished filling out the fields, copy the hyperlink at the bottom and paste it into another application, such as OneNote or an email.
to: *Replace with To: list recipients*
cc: *Replace with CC: list recipients*
subject: *Replace with a subject*
_fields_
RecipientName: Recipient Name | | Example: DBAs or All
MessageBody: Message Body | | Enter your custom message | useTextArea : true
SenderName: Sender Name
----
body:
{{RecipientName}},
{{MessageBody}}
Thanks,
{{SenderName}}
</script>
</head>
<body ng-app='EmailLinkBuilder' ng-controller='EmailLinkBuilderCtrl'>
<div class="container-fluid">
<div class="row">
<h1 title="email creation utility by Jeffrey A. Miller">Email generator</h1>
<div class='inputForm dragParent col-xs-12 col-sm-12 col-md-12 col-lg-6'>
<div class='alert alert-info'>Select a template, complete the fields, then click the hyperlink at the bottom of the page to generate an email in your default mail client.</div>
<span class='inputField'><label class='inputLabel' for='templateList'>Template:</label><select id='templateList' ng-options='template.title for template in templates' ng-model='selectedTemplate' ng-change='selectTemplate()'></select></span><br />&#160;<br />
<div ng-show='description' class='description-block alert alert-info'><strong>Description: </strong><span ng-bind-html='description'></span></div>
<span class='inputField'><label class='inputLabel' for='mailTo'>To:</label><input name='mailTo' type='text' class='receivesTokens' ng-model='to' /></span><br />
<span class='inputField'><label class='inputLabel' for='mailCc'>CC:</label><input name='mailCc' type='text' class='receivesTokens' ng-model='cc' /></span><br />
<span class='inputField'><label class='inputLabel' for='mailSubject'>Subject:</label><input name='mailSubject' type='text' class='receivesTokens' ng-model='subject' /></span><br />
<div ng-show='hasFields()'>
<fieldset id='specialFields'><legend>Special fields:</legend>
<ul>
<li ng-repeat='field in fields'><span class='inputField'><label class='inputLabel' title='Token: {{field.name}}' >{{field.label}}<span ng-show='field.tip' class='fieldTip' title='{{field.tip}}'></span>:<br /><span class='fieldName fieldHandle'>{{<span>{{field.name}}</span>}}</span></label><span ng-hide='field.useCheckbox'><input type='text' ng-show='!field.useTextArea' ng-model='field.value' placeholder='{{field.placeholder}}' ng-required='field.required' /></span><span ng-hide='field.useCheckbox'><textarea ng-show='field.useTextArea' ng-model='field.value' placeholder='{{field.placeholder}}' ng-required='field.required'></textarea><span class='validation-inline'></span></span><span ng-show='field.useCheckbox'><input type='checkbox' ng-model='field.value' ng-show='field.useCheckbox' /></span></li>
</ul>
</fieldset>
</div>
<span class='inputField'><label class='inputLabel textAreaLabel' for='mailBody'>Body:</label><textarea id='mailBody' name='mailBody' class='receivesTokens' rows='6' cols='70' ng-model='body'></textarea></span><br />
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-1">&#160;</div>
<div id='messagePreview' class='inputForm col-xs-12 col-sm-12 col-md-12 col-lg-5'>
<h3>Preview: {{title}}</h3>
<table class='renderedFieldTable'>
<tbody>
<tr><th>To:</th><td class='renderedField'>{{replaceRecipientTokens("to")}}</td></tr>
<tr><th>CC:</th><td class='renderedField'>{{replaceRecipientTokens("cc")}}</td></tr>
<tr><th>Subject:</th><td class='renderedField'>{{replaceSubjectTokens()}}</td></tr>
</tbody>
</table>
<hr />
<pre><code class="bodyPreview">{{replaceBodyTokens()}}</code></pre>
</div>
</div>
<div class="row" style="margin-top: 2em;">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<span class='instructions'>Click or copy this link to create an email message ==&gt;:</span>
<span class='resultLink'><a class='btn btn-primary' id='emailLink' ng-href='{{getMailToLink()}}'>{{replaceSubjectTokens()}}</a></span><br />
<small><strong>You can also drag a link to your browser toolbar for future use!</strong></small>
</div>
</div>
<div class="row" style="margin-top: 3em;">
<div id='tokenList' class='inputForm dragParent col-xs-12 col-sm-12 col-md-12 col-lg-5'>
<h3>Available Replacement Tokens</h3>
<table class="referenceTable">
<tbody>
<tr ng-repeat='(key, value) in replacementTokens'><th><span class='fieldHandle'>{{<span>{{key}}</span>}}</span></th><td>{{value}}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment