mirror of
https://github.com/penpot/penpot.git
synced 2025-04-10 14:01:29 -05:00
🎉 Add html emails.
This commit is contained in:
parent
721879aaa8
commit
fbd6e395a4
27 changed files with 368 additions and 366 deletions
|
@ -19,8 +19,9 @@
|
|||
io.prometheus/simpleclient_hotspot {:mvn/version "0.9.0"}
|
||||
io.prometheus/simpleclient_httpserver {:mvn/version "0.9.0"}
|
||||
|
||||
selmer/selmer {:mvn/version "1.12.18"}
|
||||
|
||||
expound/expound {:mvn/version "0.8.4"}
|
||||
instaparse/instaparse {:mvn/version "1.4.10"}
|
||||
com.cognitect/transit-clj {:mvn/version "1.0.324"}
|
||||
|
||||
io.lettuce/lettuce-core {:mvn/version "5.2.2.RELEASE"}
|
||||
|
|
85
backend/resources/emails/base.html
Normal file
85
backend/resources/emails/base.html
Normal file
|
@ -0,0 +1,85 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
{% block head %}
|
||||
<title>UXBOX Email</title>
|
||||
{% endblock %}
|
||||
{% include "emails/partials/inline_style.html" %}
|
||||
</head>
|
||||
|
||||
<body bgcolor="#f6f6f6" cz-shortcut-listen="true">
|
||||
<!-- body -->
|
||||
<table class="body-wrap">
|
||||
<tbody><tr>
|
||||
<td></td>
|
||||
<td class="container" bgcolor="#FFFFFF">
|
||||
<!-- logo -->
|
||||
<div class="logo">
|
||||
<img src="{{assets-uri}}/images/email/logo.png" alt="UXBOX">
|
||||
</div>
|
||||
<!-- content -->
|
||||
<div class="content">
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<td>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<!-- /content -->
|
||||
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- /body -->
|
||||
|
||||
<!-- footer -->
|
||||
<table class="footer-wrap">
|
||||
<tbody><tr>
|
||||
<td></td>
|
||||
<td class="container">
|
||||
<!-- content -->
|
||||
<div class="content">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p>UXBOX is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="text-align: center; margin: 10px 0;">
|
||||
<a href="#" target="_blank"><img style="display: inline-block; width: 25px; margin: 0 15px;" src="{{assets-uri}}/images/email/uxbox.png" alt="UXBOX"></a>
|
||||
<a href="#" target="_blank"><img style="display: inline-block; width: 25px; margin: 0 15px;" src="{{assets-uri}}/images/email/twitter.png" alt="TWITTER"></a>
|
||||
<a href="#" target="_blank"><img style="display: inline-block; width: 25px; margin: 0 15px;" src="{{assets-uri}}/images/email/github.png" alt="GITHUB"></a>
|
||||
<a href="#" target="_blank"><img style="display: inline-block; width: 25px; margin: 0 15px;" src="{{assets-uri}}/images/email/instagram.png" alt="INSTAGRAM"></a>
|
||||
<a href="#" target="_blank"><img style="display: inline-block; width: 25px; margin: 0 15px;" src="{{assets-uri}}/images/email/taiga.png" alt="TAIGA"></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% comment %}
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p>Sent from UXBOX | <a href="#" target="_blank"><unsubscribe>Email preferences</unsubscribe></a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endcomment %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div><!-- /content -->
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- /footer -->
|
||||
</body>
|
||||
</html>
|
19
backend/resources/emails/change-email/en.html
Normal file
19
backend/resources/emails/change-email/en.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "emails/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>Hello {{name}}!</p>
|
||||
|
||||
<p>We received a request to change your current email to {{ pending-email }}.</p>
|
||||
|
||||
<p>Click to the link below to confirm the change:</p>
|
||||
|
||||
<a class="btn-primary" href="{{ public-uri }}/#/auth/verify-token?token={{token}}">Confirm email change</a>
|
||||
|
||||
<p>If you received this email by mistake, please consider changing your password
|
||||
for security reasons.</p>
|
||||
|
||||
<p>Enjoy!</p>
|
||||
|
||||
<p>The UXBOX team.</p>
|
||||
|
||||
{% endblock %}
|
1
backend/resources/emails/change-email/en.subj
Normal file
1
backend/resources/emails/change-email/en.subj
Normal file
|
@ -0,0 +1 @@
|
|||
Email change
|
|
@ -1,19 +1,13 @@
|
|||
-- begin :subject
|
||||
Email change.
|
||||
-- end
|
||||
|
||||
-- begin :body-text
|
||||
Hello {{name}}!
|
||||
|
||||
We received a request to change your current email to {{ pendingEmail }}.
|
||||
We received a request to change your current email to {{ pending-email }}.
|
||||
|
||||
Click to the link below to confirm the change:
|
||||
|
||||
{{ publicUri }}/#/auth/verify-token?token={{token}}
|
||||
{{ public-uri }}/#/auth/verify-token?token={{token}}
|
||||
|
||||
If you received this email by mistake, please consider changing your password
|
||||
for security reasons.
|
||||
|
||||
Enjoy!
|
||||
The UXBOX team.
|
||||
-- end
|
|
@ -1,14 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<section style="font-family: Monoid, monospace; font-size: 14px;">
|
||||
<h1>Available Emails:</h1>
|
||||
<ul>
|
||||
{{#emails}}
|
||||
<li>
|
||||
<a href="/debug/emails/{{ id }}">{{id}}</a>
|
||||
</li>
|
||||
{{/emails}}
|
||||
</ul>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -1,46 +0,0 @@
|
|||
<table class="footer-wrap">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="text-align: center;">
|
||||
<a href="#" target="_blank">
|
||||
<img src="{{#static}}images/email/twitter.png{{/static}}"
|
||||
style="display: inline-block; width: 25px; margin-right: 5px;" />
|
||||
</a>
|
||||
<a href="#" target="_blank">
|
||||
<img src="{{#static}}images/email/github.png{{/static}}"
|
||||
style="display: inline-block; width: 25px; margin-right: 5px;" />
|
||||
</a>
|
||||
<a href="#" target="_blank">
|
||||
<img src="{{#static}}images/email/linkedin.png{{/static}}"
|
||||
style="display: inline-block; width: 25px; margin-right: 5px;" />
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{#comment}}
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p>
|
||||
<span>Sent from UXBOX | </span>
|
||||
<a href="#" target="_blank">
|
||||
<unsubscribe>Email preferences</unsubscribe>
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
{{/comment}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,6 +0,0 @@
|
|||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
<meta content="width=device-width" name="viewport" />
|
||||
<title>title</title>
|
||||
{{> inline_style }}
|
||||
</head>
|
173
backend/resources/emails/partials/inline_style.html
Normal file
173
backend/resources/emails/partials/inline_style.html
Normal file
|
@ -0,0 +1,173 @@
|
|||
<style>
|
||||
/* GLOBAL */
|
||||
* {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing:antialiased;
|
||||
-webkit-text-size-adjust:none;
|
||||
width: 100%!important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ELEMENTS */
|
||||
a {
|
||||
color: rgb(35, 211, 161);
|
||||
text-decoration:none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
text-decoration:none;
|
||||
color: #000;
|
||||
background-color: #31EFB8;
|
||||
padding: 10px 30px;
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
text-decoration:none;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
padding: 10px 30px;
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.btn-primary:hover,
|
||||
.btn-secondary:hover {
|
||||
color: #31EFB8;
|
||||
background-color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-color: #f6f6f6;
|
||||
padding: 10px;
|
||||
}
|
||||
.logo h2 {
|
||||
color: #000;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.logo img {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
/* BODY */
|
||||
table.body-wrap {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
table.body-wrap .container{
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
/* FOOTER */
|
||||
table.footer-wrap {
|
||||
width: 100%;
|
||||
clear:both!important;
|
||||
}
|
||||
|
||||
.footer-wrap .container p {
|
||||
font-size: 12px;
|
||||
color:#666666;
|
||||
|
||||
}
|
||||
|
||||
table.footer-wrap a{
|
||||
color: #999;
|
||||
}
|
||||
|
||||
|
||||
/* TYPOGRAPHY */
|
||||
h1,h2,h3{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.1;
|
||||
margin-bottom:15px;
|
||||
color:#000;
|
||||
margin: 25px 0 15px;
|
||||
line-height: 1.2;
|
||||
font-weight:200;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #000;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
p, ul {
|
||||
margin-bottom: 20px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-left:5px;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/* RESPONSIVE */
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block !important;
|
||||
max-width: 620px !important;
|
||||
margin: 0 auto !important; /* makes it centered */
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
padding: 20px;
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Let's make sure tables in the content area are 100% wide */
|
||||
.content table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,162 +0,0 @@
|
|||
<style>
|
||||
/* GLOBAL */
|
||||
* {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.img-header {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing:antialiased;
|
||||
-webkit-text-size-adjust:none;
|
||||
width: 100%!important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ELEMENTS */
|
||||
a {
|
||||
color: #78dbbe;
|
||||
text-decoration:none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
text-decoration:none;
|
||||
color: #fff;
|
||||
background-color: #78dbbe;
|
||||
padding: 10px 30px;
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
color: #FFF;
|
||||
background-color: #8eefcf;
|
||||
}
|
||||
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-color: #f6f6f6;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
.logo h2 {
|
||||
color: #777;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
/* BODY */
|
||||
table.body-wrap {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
table.body-wrap .container{
|
||||
border-radius: 5px;
|
||||
color: #ababab;
|
||||
}
|
||||
|
||||
|
||||
/* FOOTER */
|
||||
table.footer-wrap {
|
||||
width: 100%;
|
||||
clear:both!important;
|
||||
}
|
||||
|
||||
.footer-wrap .container p {
|
||||
font-size: 12px;
|
||||
color:#666;
|
||||
|
||||
}
|
||||
|
||||
table.footer-wrap a{
|
||||
color: #999;
|
||||
}
|
||||
|
||||
|
||||
/* TYPOGRAPHY */
|
||||
h1,h2,h3{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.1;
|
||||
margin-bottom:15px;
|
||||
color:#000;
|
||||
margin: 40px 0 10px;
|
||||
line-height: 1.2;
|
||||
font-weight:200;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #777;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
p, ul {
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-left:5px;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/* RESPONSIVE */
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block !important;
|
||||
max-width: 620px !important;
|
||||
margin: 0 auto !important; /* makes it centered */
|
||||
clear: both !important;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
padding: 20px;
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Let's make sure tables in the content area are 100% wide */
|
||||
.content table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
22
backend/resources/emails/password-recovery/en.html
Normal file
22
backend/resources/emails/password-recovery/en.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends "emails/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>Hello {{name}}!</p>
|
||||
|
||||
<p>We received a request to reset your password. Click the link
|
||||
below to choose a new one:</p>
|
||||
|
||||
<a class="btn-primary" href="{{ public-uri }}/#/auth/recovery?token={{token}}">
|
||||
Reset password.
|
||||
</a>
|
||||
|
||||
<p>
|
||||
If you received this email by mistake, you can safely ignore
|
||||
it. Your password won't be changed.
|
||||
</p>
|
||||
|
||||
<p>Enjoy!</p>
|
||||
|
||||
<p>The UXBOX team.</p>
|
||||
|
||||
{% endblock %}
|
1
backend/resources/emails/password-recovery/en.subj
Normal file
1
backend/resources/emails/password-recovery/en.subj
Normal file
|
@ -0,0 +1 @@
|
|||
Password reset
|
|
@ -1,18 +1,12 @@
|
|||
-- begin :subject
|
||||
Password reset.
|
||||
-- end
|
||||
|
||||
-- begin :body-text
|
||||
Hello {{name}}!
|
||||
|
||||
We received a request to reset your password. Click the link below to choose a
|
||||
new one:
|
||||
|
||||
{{ publicUri }}/#/auth/recovery?token={{token}}
|
||||
{{ public-uri }}/#/auth/recovery?token={{token}}
|
||||
|
||||
If you received this email by mistake, you can safely ignore it. Your password
|
||||
won't be changed.
|
||||
|
||||
Enjoy!
|
||||
The UXBOX team.
|
||||
-- end
|
20
backend/resources/emails/register/en.html
Normal file
20
backend/resources/emails/register/en.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "emails/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>Hello {{name}}!</p>
|
||||
|
||||
<p>
|
||||
Thanks for signing up for your UXBOX account! Please verify your
|
||||
email using the link below adn get started building mockups and
|
||||
prototypes today!
|
||||
</p>
|
||||
|
||||
<a class="btn-primary" href="{{public-uri}}/#/auth/verify-token?token={{token}}">
|
||||
Verify token
|
||||
</a>
|
||||
|
||||
<p>Enjoy!</p>
|
||||
|
||||
<p>The UXBOX team.</p>
|
||||
|
||||
{% endblock %}
|
1
backend/resources/emails/register/en.subj
Normal file
1
backend/resources/emails/register/en.subj
Normal file
|
@ -0,0 +1 @@
|
|||
Verify email.
|
|
@ -1,15 +1,9 @@
|
|||
-- begin :subject
|
||||
Verify email.
|
||||
-- end
|
||||
|
||||
-- begin :body-text
|
||||
Hello {{name}}!
|
||||
|
||||
Thanks for signing up for your UXBOX account! Please verify your email using the
|
||||
link below adn get started building mockups and prototypes today!
|
||||
|
||||
{{ publicUri }}/#/auth/verify-token?token={{token}}
|
||||
{{ public-uri }}/#/auth/verify-token?token={{token}}
|
||||
|
||||
Enjoy!
|
||||
The UXBOX team.
|
||||
-- end
|
|
@ -1,17 +0,0 @@
|
|||
-- begin :subject
|
||||
Bienvenue sur UXBOX.
|
||||
-- end
|
||||
|
||||
-- begin :body-text
|
||||
Bonjour {{user}}!
|
||||
|
||||
Bienvenue sur UXBOX.
|
||||
|
||||
L'équipe UXBOX.
|
||||
-- end
|
||||
|
||||
-- begin :body-html
|
||||
<p>Bonjour {{user}} !</p>
|
||||
<p>Bienvenue sur UXBOX.</p>
|
||||
<p>L'équipe UXBOX.</p>
|
||||
-- end
|
BIN
backend/resources/public/static/images/email/instagram.png
Normal file
BIN
backend/resources/public/static/images/email/instagram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 901 B |
BIN
backend/resources/public/static/images/email/taiga.png
Normal file
BIN
backend/resources/public/static/images/email/taiga.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 604 B |
BIN
backend/resources/public/static/images/email/uxbox.png
Normal file
BIN
backend/resources/public/static/images/email/uxbox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 746 B |
|
@ -32,8 +32,8 @@
|
|||
:redis-uri "redis://redis/0"
|
||||
:media-directory "resources/public/media"
|
||||
:assets-directory "resources/public/static"
|
||||
:media-uri "http://localhost:6060/media/"
|
||||
:assets-uri "http://localhost:6060/static/"
|
||||
:media-uri "http://localhost:6060/media"
|
||||
:assets-uri "http://localhost:6060/static"
|
||||
|
||||
:sendmail-backend "console"
|
||||
:sendmail-reply-to "no-reply@example.com"
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
|
||||
(defn default-context
|
||||
[]
|
||||
{:static media/resolve-asset
|
||||
:comment (constantly nil)
|
||||
{:assets-uri (:assets-uri cfg/config)
|
||||
:public-uri (:public-uri cfg/config)})
|
||||
|
||||
;; --- Public API
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
[mount.core :as mount :refer [defstate]]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.util.migrations :as mg]
|
||||
[uxbox.util.template :as tmpl]))
|
||||
[uxbox.util.migrations :as mg]))
|
||||
|
||||
(def +migrations+
|
||||
{:name "uxbox-main"
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
(emails/send! conn emails/register
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:token token})
|
||||
profile)))
|
||||
|
||||
|
@ -339,7 +338,6 @@
|
|||
(emails/send! conn emails/change-email
|
||||
{:to (:email profile)
|
||||
:name (:fullname profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:pending-email email
|
||||
:token token})
|
||||
nil)))
|
||||
|
@ -430,7 +428,6 @@
|
|||
(send-email-notification [conn profile]
|
||||
(emails/send! conn emails/password-recovery
|
||||
{:to (:email profile)
|
||||
:public-url (:public-uri cfg/config)
|
||||
:token (:token profile)
|
||||
:name (:fullname profile)}))]
|
||||
|
||||
|
|
|
@ -52,15 +52,15 @@
|
|||
:cron (dt/cron "1 1 */1 * * ? *")
|
||||
:fn #'uxbox.tasks.gc/remove-media}])
|
||||
|
||||
(defstate worker
|
||||
(defstate tasks-worker
|
||||
:start (impl/start-worker! {:tasks tasks
|
||||
:xtor scheduler})
|
||||
:stop (impl/stop! worker))
|
||||
:stop (impl/stop! tasks-worker))
|
||||
|
||||
(defstate scheduler-worker
|
||||
:start (impl/start-scheduler-worker! {:schedule schedule
|
||||
:xtor scheduler})
|
||||
:stop (impl/stop! worker))
|
||||
:stop (impl/stop! scheduler-worker))
|
||||
|
||||
;; --- Public API
|
||||
|
||||
|
|
|
@ -9,48 +9,13 @@
|
|||
[clojure.java.io :as io]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[instaparse.core :as insta]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.common.exceptions :as ex]
|
||||
[uxbox.util.template :as tmpl]))
|
||||
|
||||
;; --- Impl.
|
||||
|
||||
(def ^:private grammar
|
||||
(str "message = part*"
|
||||
"part = begin header body end; "
|
||||
"header = tag* eol; "
|
||||
"tag = space keyword; "
|
||||
"body = line*; "
|
||||
"begin = #'--\\s+begin\\s+'; "
|
||||
"end = #'--\\s+end\\s*' eol*; "
|
||||
"keyword = #':[\\w\\-]+'; "
|
||||
"space = #'\\s*'; "
|
||||
"line = #'.*\\n'; "
|
||||
"eol = ('\\n' | '\\r\\n'); "))
|
||||
|
||||
(def ^:private parse-fn (insta/parser grammar))
|
||||
(def ^:private email-path "emails/%(id)s/%(lang)s.mustache")
|
||||
|
||||
(defn- parse-template
|
||||
[content]
|
||||
(loop [state {}
|
||||
parts (drop 1 (parse-fn content))]
|
||||
(if-let [[_ _ header body] (first parts)]
|
||||
(let [type (get-in header [1 2 1])
|
||||
type (keyword (str/slice type 1))
|
||||
content (apply str (map second (rest body)))]
|
||||
(recur (assoc state type (str/trim content " \n"))
|
||||
(rest parts)))
|
||||
state)))
|
||||
|
||||
(s/def ::subject string?)
|
||||
(s/def ::body-text string?)
|
||||
(s/def ::body-html string?)
|
||||
|
||||
(s/def ::parsed-email
|
||||
(s/keys :req-un [::subject ::body-text]
|
||||
:opt-un [::body-html]))
|
||||
(def ^:private email-path "emails/%(id)s/%(lang)s.%(type)s")
|
||||
|
||||
(defn- build-base-email
|
||||
[data context]
|
||||
|
@ -66,13 +31,28 @@
|
|||
(:body-html data) (conj {:type "text/html"
|
||||
:value (:body-html data)}))})
|
||||
|
||||
(defn- render-email-part
|
||||
[type id context]
|
||||
(let [lang (:lang context :en)
|
||||
path (str/format email-path {:id (name id)
|
||||
:lang (name lang)
|
||||
:type (name type)})]
|
||||
(some-> (io/resource path)
|
||||
(tmpl/render context))))
|
||||
|
||||
(defn- impl-build-email
|
||||
[id context]
|
||||
(let [lang (:lang context :en)
|
||||
path (str/format email-path {:id (name id) :lang (name lang)})]
|
||||
(-> (tmpl/render path context)
|
||||
(parse-template)
|
||||
(build-base-email context))))
|
||||
subj (render-email-part :subj id context)
|
||||
html (render-email-part :html id context)
|
||||
text (render-email-part :txt id context)]
|
||||
|
||||
{:subject subj
|
||||
:content (cond-> []
|
||||
text (conj {:type "text/plain"
|
||||
:value text})
|
||||
html (conj {:type "text/html"
|
||||
:value html}))}))
|
||||
|
||||
;; --- Public API
|
||||
|
||||
|
|
|
@ -12,57 +12,24 @@
|
|||
[clojure.walk :as walk]
|
||||
[clojure.java.io :as io]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.common.exceptions :as ex])
|
||||
(:import
|
||||
java.io.StringReader
|
||||
java.util.HashMap
|
||||
java.util.function.Function;
|
||||
com.github.mustachejava.DefaultMustacheFactory
|
||||
com.github.mustachejava.Mustache))
|
||||
|
||||
(def ^DefaultMustacheFactory +mustache-factory+ (DefaultMustacheFactory.))
|
||||
|
||||
(defn- adapt-context
|
||||
[data]
|
||||
(walk/postwalk (fn [x]
|
||||
(cond
|
||||
(instance? clojure.lang.Named x)
|
||||
(str/camel (name x))
|
||||
|
||||
(instance? clojure.lang.MapEntry x)
|
||||
x
|
||||
|
||||
(fn? x)
|
||||
(reify Function
|
||||
(apply [this content]
|
||||
(try
|
||||
(x content)
|
||||
(catch Exception e
|
||||
(log/error e "Error on executing" x)
|
||||
""))))
|
||||
|
||||
(or (vector? x) (list? x))
|
||||
(java.util.ArrayList. ^java.util.List x)
|
||||
|
||||
(map? x)
|
||||
(java.util.HashMap. ^java.util.Map x)
|
||||
|
||||
(set? x)
|
||||
(java.util.HashSet. ^java.util.Set x)
|
||||
|
||||
:else
|
||||
x))
|
||||
data))
|
||||
[selmer.parser :as sp]
|
||||
[uxbox.common.exceptions :as ex]))
|
||||
|
||||
;; (sp/cache-off!)
|
||||
|
||||
(defn render
|
||||
[path context]
|
||||
(try
|
||||
(let [context (adapt-context context)
|
||||
template (.compile +mustache-factory+ path)]
|
||||
(with-out-str
|
||||
(let [scope (HashMap. ^java.util.Map (walk/stringify-keys context))]
|
||||
(.execute ^Mustache template *out* scope))))
|
||||
(sp/render-file path context)
|
||||
(catch Exception cause
|
||||
(ex/raise :type :internal
|
||||
:code :template-render-error
|
||||
:cause cause))))
|
||||
|
||||
(defn render-string
|
||||
[content context]
|
||||
(try
|
||||
(sp/render content context)
|
||||
(catch Exception cause
|
||||
(ex/raise :type :internal
|
||||
:code :template-render-error
|
||||
|
|
Loading…
Add table
Reference in a new issue