Skip to content

Commit

Permalink
Introduce @TrackLink shorthand for generating tracking links.
Browse files Browse the repository at this point in the history
The default `{{ TrackLink "https://listmonk.app" }}` template function
is clumsy to write and does breaks WYSIWYG editors and HTML syntax
highlighting because of the quotes. The new syntax doesn't break HTML
and is easier to write.

Eg: `<a href="https://listmonk.app@TrackLink">Link</a>`

- Introduce @Tracklink shorthand.
- Add first-class support for tracking links in the WYSIWYG (TinyMCE)
  editor by introducing an on/off checkbox on the link dialog.
- Improve default dummy campaign content to highlight this.
  • Loading branch information
knadh committed Sep 26, 2021
1 parent d3f543c commit d86438b
Show file tree
Hide file tree
Showing 18 changed files with 139 additions and 10 deletions.
8 changes: 7 additions & 1 deletion cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,13 @@ func install(lastVer string, db *sqlx.DB, fs stuffbin.FileSystem, prompt, idempo
"Welcome to listmonk",
"No Reply <[email protected]>",
`<h3>Hi {{ .Subscriber.FirstName }}!</h3>
This is a test e-mail campaign. Your second name is {{ .Subscriber.LastName }} and you are from {{ .Subscriber.Attribs.city }}.`,
<p>This is a test e-mail campaign. Your second name is {{ .Subscriber.LastName }} and you are from {{ .Subscriber.Attribs.city }}.</p>
<p>Here is a <a href="https://listmonk.app@TrackLink">tracked link</a>.</p>
<p>Use the link icon in the editor toolbar or when writing raw HTML or Markdown,
simply suffix @TrackLink to the end of a URL to turn it into a tracking link. Example:</p>
<pre>&lt;a href=&quot;https:/&zwnj;/listmonk.app&#064;TrackLink&quot;&gt;&lt;/a&gt;</pre>
<p>For help, refer to the <a href="https://listmonk.app/docs">documentation</a>.</p>
`,
nil,
"richtext",
nil,
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/assets/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ body.is-noscroll {
border: 0;
}
}

.tox-track-link {
display: block !important;
cursor: pointer !important;

margin: 5px 0 10px 0 !important;
input {
margin-right: 5px !important;
}
}

.plain-editor textarea {
height: 65vh;
}
Expand Down
85 changes: 81 additions & 4 deletions frontend/src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export default {
isReady: false,
isRichtextReady: false,
richtextConf: {},
isTrackLink: false,
form: {
body: '',
format: this.contentType,
Expand All @@ -174,7 +175,18 @@ export default {
const { lang } = this.serverConfig;
this.richtextConf = {
init_instance_callback: () => { this.isReady = true; },
urlconverter_callback: this.onEditorURLConvert,
setup: (editor) => {
editor.on('init', () => {
this.onEditorDialogOpen(editor);
});
},
min_height: 500,
entity_encoding: 'raw',
convert_urls: true,
plugins: [
'autoresize', 'autolink', 'charmap', 'code', 'emoticons', 'fullscreen', 'help',
'hr', 'image', 'imagetools', 'link', 'lists', 'paste', 'searchreplace',
Expand All @@ -194,15 +206,14 @@ export default {
table, td { border-color: #ccc;}
`,
language: LANGS[lang] || null,
language_url: LANGS[lang] ? `${uris.static}/tinymce/lang/${LANGS[lang]}.js` : null,
file_picker_types: 'image',
file_picker_callback: (callback) => {
this.isMediaVisible = true;
this.runTinyMceImageCallback = callback;
},
init_instance_callback: () => { this.isReady = true; },
language: LANGS[lang] || null,
language_url: LANGS[lang] ? `${uris.static}/tinymce/lang/${LANGS[lang]}.js` : null,
};
this.isRichtextReady = true;
Expand Down Expand Up @@ -258,6 +269,72 @@ export default {
);
},
onEditorURLConvert(url) {
let u = url;
if (this.isTrackLink) {
u = `${u}@TrackLink`;
}
this.isTrackLink = false;
return u;
},
onEditorDialogOpen(editor) {
const ed = editor;
const oldEd = ed.windowManager.open;
const self = this;
ed.windowManager.open = (t, r) => {
const isOK = t.initialData && 'url' in t.initialData && 'anchor' in t.initialData;
// Not the link modal.
if (!isOK) {
return oldEd.apply(this, [t, r]);
}
// If an existing link is being edited, check for the tracking flag `@TrackLink` at the end
// of the url. Remove that from the URL and instead check the checkbox.
let checked = false;
if (!t.initialData.link !== '') {
const t2 = t;
const url = t2.initialData.url.value.replace(/@TrackLink$/, '');
if (t2.initialData.url.value !== url) {
t2.initialData.url.value = url;
checked = true;
}
}
// Execute the modal.
const modal = oldEd.apply(this, [t, r]);
// Is it the link dialog?
if (isOK) {
// Insert tracking checkbox.
const c = document.createElement('input');
c.setAttribute('type', 'checkbox');
if (checked) {
c.setAttribute('checked', checked);
}
// Store the checkbox's state in the Vue instance to pick up from
// the TinyMCE link conversion callback.
c.onchange = (e) => {
self.isTrackLink = e.target.checked;
};
const l = document.createElement('label');
l.appendChild(c);
l.appendChild(document.createTextNode('Track link?'));
l.classList.add('tox-label', 'tox-track-link');
document.querySelector('.tox-form__controls-h-stack .tox-control-wrap').appendChild(l);
}
return modal;
};
},
onEditorChange() {
if (!this.isReady) {
return;
Expand Down
1 change: 1 addition & 0 deletions i18n/cs-cz.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-maily",
"campaigns.testSent": "Testovací zpráva odeslána",
"campaigns.timestamps": "Časová razítka",
"campaigns.trackLink": "Track link",
"campaigns.views": "Pohledy",
"dashboard.campaignViews": "Pohledy na kampaň",
"dashboard.linkClicks": "Klepnutí na odkaz",
Expand Down
1 change: 1 addition & 0 deletions i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-Mails",
"campaigns.testSent": "Testnachricht gesendet",
"campaigns.timestamps": "Zeitstempel",
"campaigns.trackLink": "Track link",
"campaigns.views": "Ansichten",
"dashboard.campaignViews": "Kampagnenansichten",
"dashboard.linkClicks": "Linkklicks",
Expand Down
1 change: 1 addition & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-mails",
"campaigns.testSent": "Test message sent",
"campaigns.timestamps": "Timestamps",
"campaigns.trackLink": "Track link",
"campaigns.views": "Views",
"dashboard.campaignViews": "Campaign views",
"dashboard.linkClicks": "Link clicks",
Expand Down
1 change: 1 addition & 0 deletions i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "Correos electrónicos",
"campaigns.testSent": "Mensaje de prueba enviado",
"campaigns.timestamps": "Marca de timepo",
"campaigns.trackLink": "Track link",
"campaigns.views": "Vistas",
"dashboard.campaignViews": "Vista de campañas",
"dashboard.linkClicks": "Vinculos cliqueados",
Expand Down
1 change: 1 addition & 0 deletions i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "Emails de test",
"campaigns.testSent": "Message de test envoyé",
"campaigns.timestamps": "Horodatages",
"campaigns.trackLink": "Track link",
"campaigns.views": "Vues",
"dashboard.campaignViews": "vues de campagne",
"dashboard.linkClicks": "clics sur liens",
Expand Down
1 change: 1 addition & 0 deletions i18n/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "Emails di prova",
"campaigns.testSent": "Messaggio di prova inviato",
"campaigns.timestamps": "Marcatura temporale ",
"campaigns.trackLink": "Track link",
"campaigns.views": "Visualizzazioni",
"dashboard.campaignViews": "Visualizzazioni della campagna",
"dashboard.linkClicks": "Clic sui link",
Expand Down
1 change: 1 addition & 0 deletions i18n/ml.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "ഈ-മെയിലുകൾ",
"campaigns.testSent": "ടെസ്റ്റ് സന്ദേശം അയച്ചു",
"campaigns.timestamps": "സമയം",
"campaigns.trackLink": "Track link",
"campaigns.views": "കാഴ്ചകൾ",
"dashboard.campaignViews": "ക്യാമ്പേയ്ൻ കാഴ്ചകൾ",
"dashboard.linkClicks": "കണ്ണിയിലെ ക്ലിക്കുകൾ",
Expand Down
1 change: 1 addition & 0 deletions i18n/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-maile",
"campaigns.testSent": "Wiadomość testowa wysłana",
"campaigns.timestamps": "Sygnatury czasowe",
"campaigns.trackLink": "Track link",
"campaigns.views": "Wyświetlenia",
"dashboard.campaignViews": "Wyświetlenia kampanii",
"dashboard.linkClicks": "Kliknięcia linków",
Expand Down
1 change: 1 addition & 0 deletions i18n/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-mails",
"campaigns.testSent": "Mensagem de teste enviada",
"campaigns.timestamps": "Data e hora",
"campaigns.trackLink": "Track link",
"campaigns.views": "Visualizações",
"dashboard.campaignViews": "Visualizações da campanha",
"dashboard.linkClicks": "Links clicados",
Expand Down
1 change: 1 addition & 0 deletions i18n/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-mails",
"campaigns.testSent": "Mensagem de teste enviada",
"campaigns.timestamps": "Carimbo de hora",
"campaigns.trackLink": "Track link",
"campaigns.views": "Visualizações",
"dashboard.campaignViews": "Vista de campanhas",
"dashboard.linkClicks": "Cliques nos links",
Expand Down
1 change: 1 addition & 0 deletions i18n/ro.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "Emailuri",
"campaigns.testSent": "Mesaju de test a fost trimis",
"campaigns.timestamps": "Marcaje de timp",
"campaigns.trackLink": "Track link",
"campaigns.views": "Vizualizări",
"dashboard.campaignViews": "Vizualizări ale campaniei",
"dashboard.linkClicks": "Clickuri pe link",
Expand Down
1 change: 1 addition & 0 deletions i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-mails",
"campaigns.testSent": "Тестовое сообщение отправлено",
"campaigns.timestamps": "Метки времени",
"campaigns.trackLink": "Track link",
"campaigns.views": "Просмотры",
"dashboard.campaignViews": "Просмотров компании",
"dashboard.linkClicks": "Кликов по ссылкам",
Expand Down
1 change: 1 addition & 0 deletions i18n/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"campaigns.testEmails": "E-postalar",
"campaigns.testSent": "Test mesajı gönderildi",
"campaigns.timestamps": "Zaman etiketi",
"campaigns.trackLink": "Track link",
"campaigns.views": "Görüntülenme",
"dashboard.campaignViews": "Kampanya görüntülenme Sayısı",
"dashboard.linkClicks": "Linklerin tıklanması",
Expand Down
18 changes: 13 additions & 5 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,23 @@ type regTplFunc struct {
replace string
}

// Regular expression for matching {{ Track "http://link.com" }} in the template
// and substituting it with {{ Track "http://link.com" .Campaign.UUID .Subscriber.UUID }}
// before compilation. This string gimmick is to make linking easier for users.
var regTplFuncs = []regTplFunc{
regTplFunc{
// Convert the shorthand https://google.com@TrackLink to {{ TrackLink ... }}.
// This is for WYSIWYG editors that encode and break quotes {{ "" }} when inserted
// inside <a href="{{ TrackLink "https://these-quotes-break" }}>.
{
regExp: regexp.MustCompile(`(https?://.+?)@TrackLink`),
replace: `{{ TrackLink "$1" . }}`,
},

// Regular expression for matching {{ TrackLink "http://link.com" }} in the template
// and substituting it with {{ Track "http://link.com" . }} (the dot context)
// before compilation. This is to make linking easier for users.
{
regExp: regexp.MustCompile("{{(\\s+)?TrackLink\\s+?(\"|`)(.+?)(\"|`)(\\s+)?}}"),
replace: `{{ TrackLink "$3" . }}`,
},
regTplFunc{
{
regExp: regexp.MustCompile(`{{(\s+)?(TrackView|UnsubscribeURL|OptinURL|MessageURL)(\s+)?}}`),
replace: `{{ $2 . }}`,
},
Expand Down
14 changes: 14 additions & 0 deletions static/email-templates/default.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@
color: #444;
}
pre {
background: #f4f4f4f4;
padding: 2px;
}
table {
width: 100%;
border: 1px solid #ddd;
}
table td {
border-color: #ddd;
padding: 5px;
}
.wrap {
background-color: #fff;
padding: 30px;
Expand Down

0 comments on commit d86438b

Please sign in to comment.