postmortem 0.1.2 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e05359d9e039f6e44c0e89decaac3c5c72fe3447f7a96fdb8ac1667fe93d537
4
- data.tar.gz: 651d2b986f6b9b07e8fe311cd163865490005da3376c95c4ce05a4110a313e62
3
+ metadata.gz: ba1b63c74d80acb294e4bf377084882db2dd7f3c5d6422079923830b43405de2
4
+ data.tar.gz: 7b359315a60e5c44d1ea0efbf1f049c04d7cf3258b7db5bcb53636ceacbfbe13
5
5
  SHA512:
6
- metadata.gz: 66a929b9cfa7043ebce54eda09c4168e4adfd485d24e32720dc68238c6ea578030c89ad90181d770b22b33d358c2945bae12cc5df9a8f3d3019989b6a72cc8ca
7
- data.tar.gz: 82478c5ef6a04531776023947b39b3c1c04a003185a602199a4460e2ba4b6759a87493475666f4056d0bcc759b7de3a5765db8b3356d28e863e71ed445ca7e2a
6
+ metadata.gz: 57b6d1d1c02fe3f4445dfc038ef3d0762d700fca755d63a20827000d5805484a2f293f4b164bac0e22d341f24648129aa55e80f318db56243cdd32dd79dc2286
7
+ data.tar.gz: '02768d56a8cf860d2328af1922354edaf7a7caff7d481a080b27cfd3102f215084692d376f260c725a15cbdbbe0e5ec4bce2c20c0517091ad5c59495645bab75'
data/.gitignore CHANGED
@@ -10,4 +10,6 @@
10
10
  .rspec_status
11
11
  *.gem
12
12
 
13
+ preview/
14
+
13
15
  Gemfile.lock
data/.rubocop.yml CHANGED
@@ -3,49 +3,7 @@ Metrics/BlockLength:
3
3
  - 'spec/**/*_spec.rb'
4
4
  - 'postmortem.gemspec'
5
5
 
6
- Layout/EmptyLinesAroundAttributeAccessor:
7
- Enabled: true
8
- Layout/SpaceAroundMethodCallOperator:
9
- Enabled: true
10
- Lint/DeprecatedOpenSSLConstant:
11
- Enabled: true
12
- Lint/DuplicateElsifCondition:
13
- Enabled: true
14
- Lint/MixedRegexpCaptureTypes:
15
- Enabled: true
16
- Lint/RaiseException:
17
- Enabled: true
18
- Lint/StructNewOverride:
19
- Enabled: true
20
- Style/AccessorGrouping:
21
- Enabled: true
22
- Style/ArrayCoercion:
23
- Enabled: true
24
- Style/BisectedAttrAccessor:
25
- Enabled: true
26
- Style/CaseLikeIf:
27
- Enabled: true
28
- Style/ExponentialNotation:
29
- Enabled: true
30
- Style/HashAsLastArrayItem:
31
- Enabled: true
32
- Style/HashEachMethods:
33
- Enabled: true
34
- Style/HashLikeCase:
35
- Enabled: true
36
- Style/HashTransformKeys:
37
- Enabled: true
38
- Style/HashTransformValues:
39
- Enabled: true
40
- Style/RedundantAssignment:
41
- Enabled: true
42
- Style/RedundantFetchBlock:
43
- Enabled: true
44
- Style/RedundantFileExtensionInRequire:
45
- Enabled: true
46
- Style/RedundantRegexpCharacterClass:
47
- Enabled: true
48
- Style/RedundantRegexpEscape:
49
- Enabled: true
50
- Style/SlicingWithRange:
51
- Enabled: true
6
+ AllCops:
7
+ NewCops: enable
8
+ Exclude:
9
+ - 'preview/**/*'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.8
data/Gemfile CHANGED
@@ -3,5 +3,3 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
-
7
- gem 'devpack', '~> 0.2.0'
data/README.md CHANGED
@@ -1,21 +1,24 @@
1
- # Postmortem
1
+ # PostMortem
2
2
 
3
- _Postmortem_ provides a simple and clean preview of all outgoing mails sent by your _Ruby_ application to make email development a little less painful.
3
+ _PostMortem_ provides a simple and clean preview of all outgoing mails sent by your _Ruby_ application to make email development a little less painful.
4
4
 
5
5
  Every time your application sends an email a clearly-visible log entry will be written which provides a path to a temporary file containing your preview.
6
6
 
7
- Take a look at a [live example](https://postmortem.surge.sh/) to see _Postmortem_ in action.
7
+ Take a look at a [live example](https://postmortem.surge.sh/) to see _PostMortem_ in action.
8
8
 
9
- _Postmortem_ should only be enabled in test or development environments.
9
+ _PostMortem_ should only be enabled in test or development environments.
10
10
 
11
11
  ## Features
12
12
 
13
13
  * Seamless integration with [_ActionMailer_](https://guides.rubyonrails.org/action_mailer_basics.html), [_Pony_](https://github.com/benprew/pony), [_Mail_](https://github.com/mikel/mail), etc.
14
14
  * Email deliveries are always intercepted (can be configured to pass through).
15
+ * Live inbox monitors incoming emails so you can view them as soon as they are delivered.
15
16
  * Preview email content as well as typical email headers (recipients, subject, etc.).
16
17
  * View rendered _HTML_, plaintext, or _HTML_ source with syntax highlighting (courtesy of [highlight.js](https://highlightjs.org/)).
17
- * Dual or single column view to suit your requirements.
18
18
  * Content is loaded inside an `<iframe>` to ensure document isolation and validity.
19
+ * Local images are located and embedded in HTML so you can see the full version of outgoing emails.
20
+ * Runs without a server - single page app runs on file system with no need to run a local web server to access UI.
21
+ * Any captured email can be downloaded into a standalone HTML file which can be shared with others.
19
22
 
20
23
  ## Installation
21
24
 
@@ -23,7 +26,7 @@ Add the gem to your application's Gemfile:
23
26
 
24
27
  ```ruby
25
28
  group :development, :test do
26
- gem 'postmortem', '~> 0.1.2'
29
+ gem 'postmortem', '~> 0.2.4'
27
30
  end
28
31
  ```
29
32
 
@@ -37,9 +40,7 @@ Or install it yourself as:
37
40
 
38
41
  ## Usage
39
42
 
40
- _Postmortem_ automatically integrates with _Rails ActionMailer_ and _Pony_. When an email is sent an entry will be visible in your application's log output.
41
-
42
- The path to the preview file is based on the current time and the subject of the email. If you would prefer to use the same path for each email you can disable timestamps (see [configuration](#configuration)) and simply reload your browser every time an email is sent.
43
+ _PostMortem_ automatically integrates with _Rails ActionMailer_ and _Pony_. When an email is sent an entry will be visible in your application's log output.
43
44
 
44
45
  If you are using assets (images etc.) with _ActionMailer_ make sure to configure the asset host, e.g.:
45
46
 
@@ -50,15 +51,22 @@ Rails.application.configure do
50
51
  end
51
52
  ```
52
53
 
53
- Load the provided file in your browser to preview your email.
54
+ A log entry will be generated every time an email is sent. Load the path provided in the log entry in your browser to launch _PostMortem_:
54
55
 
55
56
  ![Screenshot](doc/screenshot.png)
56
57
 
58
+ ### Clearing the inbox
59
+
60
+ The inbox can be cleared at any time (e.g. at the start of a test run):
61
+
62
+ ```ruby
63
+ Postmortem.clear_inbox
64
+ ```
57
65
 
58
66
  ## Configuration
59
67
  <a name="configuration"></a>
60
68
 
61
- Configure _Postmortem_ by calling `Postmortem.configure`, e.g. in a _Rails_ initializer.
69
+ Configure _PostMortem_ by calling `Postmortem.configure`, e.g. in a _Rails_ initializer.
62
70
 
63
71
  ```ruby
64
72
  # config/initializers/postmortem.rb
@@ -66,11 +74,7 @@ Postmortem.configure do |config|
66
74
  # Colorize output in logs (path to preview HTML file) to improve visibility (default: true).
67
75
  config.colorize = true
68
76
 
69
- # Prefix all preview filenames with a timestamp (default: true).
70
- # Setting to false allows refreshing the same path in your browser to view the latest version.
71
- config.timestmap = true
72
-
73
- # Path to the Postmortem log file, where preview paths are written (default: STDOUT).
77
+ # Path to the PostMortem log file, where preview paths are written (default: STDOUT).
74
78
  config.log_path = '/path/to/postmortem.log'
75
79
 
76
80
  # Path to save preview .html files (default: OS-provided temp directory).
data/doc/screenshot.png CHANGED
Binary file
@@ -1,290 +1,104 @@
1
+ <!DOCTYPE html>
1
2
  <html>
3
+ <title>PostMortem Email</title>
2
4
  <head>
3
- <link rel="stylesheet"
4
- href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
5
- integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
6
- crossorigin="anonymous" />
7
- <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
8
- rel="stylesheet" />
9
- <link rel="stylesheet"
10
- href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/styles/a11y-light.min.css"
11
- integrity="sha512-zqEpZCxg7IVhGXy6EwdTb26cHl8IZzN/29Mj2/oSzkCKiLxHTi491mcC/K1NhShMWXu+WvfU5Z261XPc60lw7g=="
12
- crossorigin="anonymous" />
13
5
  <style>
14
- .headers {
15
- height: 100%;
16
- max-width: 35rem;
17
- display: none;
18
- }
19
-
20
- .content {
21
- background-color: #efefef;
22
- padding: 1rem 2rem;
23
- height: 100%;
24
- }
25
-
26
- .headers table {
27
- background-color: #fff;
28
- }
29
-
30
- .preview {
31
- display: none;
32
- background-color: #fff;
33
- padding: 2rem;
34
- height: 100%;
35
- }
36
-
37
- .preview.source-view {
38
- padding: 1.5rem;
39
- }
40
-
41
- .preview iframe {
42
- border-style: none;
43
- width: 100%;
44
- height: 100%;
45
- }
46
-
47
- .main-row {
48
- height: 100%;
49
- }
50
-
51
- .container {
52
- height: 90vh;
53
- margin-top: 2.8rem;
54
- }
55
-
56
- .container.full-width {
57
- max-width: 100%;
58
- }
59
-
60
- .visible {
61
- display: block;
62
- }
63
-
64
- .toolbar {
65
- background-color: #fff;
66
- text-align: right;
67
- padding: 0.5rem 1rem;
68
- position: fixed;
69
- width: 100%;
70
- }
71
-
72
- .toolbar i {
73
- font-size: 1.2rem;
74
- cursor: pointer;
75
- margin: 0 0.2rem;
76
- }
77
-
78
- .toolbar i.disabled {
79
- cursor: default;
80
- color: #ddd !important;
81
- }
82
-
83
- .toolbar .separator {
84
- border-right: 1px solid #ccc;
85
- margin-right: 0.5rem;
86
- margin-left: 0.2rem;
87
- }
6
+ <%= css_dependencies %>
7
+ <%= styles %>
88
8
  </style>
9
+
10
+ <link href="data:image/x-icon;base64,<%= favicon_b64 %>" rel="icon" type="image/x-icon" />
89
11
  </head>
12
+
90
13
  <body>
91
14
  <div class="toolbar">
92
15
 
16
+ <a target="_blank" href="https://github.com/bobf/postmortem"><h1 class="text-secondary" id="title">Post<i class="fa fa-envelope" aria-hidden="true"></i>Mortem</h1></a>
17
+
18
+ <a href="javascript:void(0)" class="text-secondary" id="download-link" download="Email.html">
19
+ <i data-toggle="tooltip"
20
+ title="Download for sharing"
21
+ class="fa fa-cloud-download download-button"></i>
22
+ </a>
23
+
93
24
  <i data-toggle="tooltip"
94
- title="<%= mail.html_body.nil? ? 'View HTML Part (Unavailable)' : 'View HTML Part' %>"
95
- class="fa fa-code html-view-switch"></i>
25
+ title="View HTML Source"
26
+ class="fa fa-code source-view-switch"></i>
96
27
 
97
28
  <i data-toggle="tooltip"
98
- title="<%= mail.html_body.nil? ? 'View HTML Source (Unavailable)' : 'View HTML Source' %>"
99
- class="fa fa-file-code-o source-view-switch"></i>
29
+ title="View HTML Part"
30
+ class="fa fa-file-code-o html-view-switch"></i>
100
31
 
101
32
  <i data-toggle="tooltip"
102
- title="<%= mail.text_body.nil? ? 'View Plaintext Part (Unavailable)' : 'View Plaintext Part' %>"
33
+ title="View Plaintext Part"
103
34
  class="fa fa-file-text-o text-view-switch"></i>
104
35
 
105
36
  <span class="separator"></span>
106
37
 
107
38
  <i data-toggle="tooltip"
108
- title="Toggle Email Headers"
39
+ title="Toggle Inbox"
109
40
  class="fa fa-columns column-switch"></i>
110
41
 
42
+ <i data-toggle="tooltip"
43
+ title="Toggle Headers"
44
+ class="fa fa-envelope-open-o headers-view-switch"></i>
45
+
111
46
  </div>
47
+
112
48
  <div class="content">
113
49
  <div class="container full-width">
114
50
  <div class="row main-row">
115
- <div class="col headers visible">
116
- <table class="table table-hover table table-bordered">
117
- <tbody>
118
- <tr>
119
- <th>Subject:</th>
120
- <td><%= mail.subject %></td>
121
- </tr>
122
-
123
- <tr>
124
- <th style="width: 7rem;">From:</th>
125
- <td><%= format_email_array(mail.from) %></td>
126
- </tr>
51
+ <div id="inbox-container" class="col inbox-container">
52
+ <div class="row">
53
+ <div class="col inbox-header">
54
+ <h5><i class="fa fa-inbox"></i> Inbox <span class="text-secondary" id="inbox-info"></span></h5>
55
+ <button data-toggle="tooltip" title="Hide read messages" class="btn btn-light show-hide-read-button"><i class="fa fa-envelope-open-o"></i> <i data-state="show" class="fa fa-eye show-hide-read-icon"></i></button>
56
+ <button data-toggle="tooltip" title="Mark all as read" class="btn btn-light read-all-button"><i class="fa fa-envelope-open-o"></i> All</button>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="row">
61
+ <!--INBOX-START-->
62
+ <div id="inbox" class="col inbox">
63
+ <div class="inbox-loading">Loading &mdash; <i class="fa fa-clock-o"></i></div>
64
+ </div>
65
+ <!--INBOX-END-->
66
+ </div>
67
+ </div>
127
68
 
128
- <tr>
129
- <th>Reply-To:</th>
130
- <td><%= format_email_array(mail.reply_to) %></td>
131
- </tr>
69
+ <div class="col right-column">
70
+ <div id="headers" class="row headers visible"></div>
132
71
 
133
- <tr>
134
- <th>To:</th>
135
- <td><%= format_email_array(mail.to) %></td>
136
- </tr>
72
+ <div class="preview row html-view">
73
+ <iframe id="html-iframe"></iframe>
74
+ </div>
137
75
 
138
- <tr>
139
- <th>Cc:</th>
140
- <td><%= format_email_array(mail.cc) %></td>
141
- </tr>
76
+ <div class="preview row text-view">
77
+ <iframe id="text-iframe"></iframe>
78
+ </div>
142
79
 
143
- <tr>
144
- <th>Bcc:</th>
145
- <td><%= format_email_array(mail.bcc) %></td>
146
- </tr>
147
- </tbody>
148
- </table>
149
- </div>
150
- <div class="preview col html-view">
151
- <iframe id="html-iframe"></iframe>
152
- </div>
153
- <div class="preview col text-view">
154
- <iframe id="text-iframe"></iframe>
155
- </div>
156
- <div class="preview col source-view">
157
- <iframe id="source-iframe"></iframe>
80
+ <div class="preview row source-view">
81
+ <iframe id="source-iframe"></iframe>
82
+ </div>
158
83
  </div>
159
84
  </div>
160
85
  </div>
161
86
  </div>
162
- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
163
- integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
164
- crossorigin="anonymous"></script>
165
- <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
166
- integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
167
- crossorigin="anonymous"></script>
168
- <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
169
- integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
170
- crossorigin="anonymous"></script>
87
+ <iframe id="index-iframe" src="postmortem_index.html"></iframe>
88
+ <iframe id="identity-iframe" src="postmortem_identity.html"></iframe>
89
+ <script type="text/javascript">
90
+ <%= javascript_dependencies %>
91
+ </script>
92
+ <%= headers_template %>
93
+ <script id="initialize-script" type="text/javascript">
94
+ const POSTMORTEM = {};
95
+ POSTMORTEM.downloadedPreview = false;
96
+ POSTMORTEM.initialData = <%= mail.serializable.to_json %>;
97
+ POSTMORTEM.hasHtml = <%= (!mail.html_body.nil?).to_json %>;
98
+ POSTMORTEM.hasText = <%= (!mail.text_body.nil?).to_json %>;
99
+ </script>
171
100
  <script>
172
- (function () {
173
- var htmlIframeDocument = document.querySelector("#html-iframe").contentDocument;
174
- htmlIframeDocument.write(<%= mail.html_body.to_json %>);
175
- htmlIframeDocument.close();
176
-
177
- var textIframeDocument = document.querySelector("#text-iframe").contentDocument;
178
- textIframeDocument.write('<pre>' + <%= ERB::Util.html_escape(mail.text_body).to_json %> + '</pre>');
179
- textIframeDocument.close();
180
-
181
- var sourceHighlightBundle = [
182
- '<link rel="stylesheet"',
183
- ' href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/styles/agate.min.css"',
184
- ' integrity="sha512-mMMMPADD4HAIogAWZbv+WjZTC0itafUZNI0jQm48PMBTApXt11omF5jhS7U1kp3R2Pr6oGJ+JwQKiUkUwCQaUQ=="',
185
- ' crossorigin="anonymous" />',
186
- '<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/highlight.min.js"',
187
- ' integrity="sha512-tHQeqtcNWlZtEh8As/4MmZ5qpy0wj04svWFK7MIzLmUVIzaHXS8eod9OmHxyBL1UET5Rchvw7Ih4ZDv5JojZww=="',
188
- ' crossorigin="anonymous"></' + 'script>',
189
- '<script>hljs.initHighlightingOnLoad();</' + 'script>',
190
- ].join('\n');
191
-
192
- var sourceIframeDocument = document.querySelector("#source-iframe").contentDocument;
193
- sourceIframeDocument.write('<pre><code style="padding: 1rem;" class="language-html">' + <%= ERB::Util.html_escape(mail.html_body).to_json %> + '</code></pre>');
194
- sourceIframeDocument.write(sourceHighlightBundle);
195
- sourceIframeDocument.close();
196
-
197
- var twoColumnView;
198
- var hasHtml = <%= (!mail.html_body.nil?).to_json %>;
199
- var hasText = <%= (!mail.text_body.nil?).to_json %>;
200
- var headers = document.querySelector('.headers');
201
- var columnSwitch = document.querySelector('.column-switch');
202
-
203
- var setColumnView = function (enableTwoColumnView) {
204
- var container = document.querySelector('.container');
205
- twoColumnView = enableTwoColumnView;
206
- if (twoColumnView) {
207
- setVisible(headers, true);
208
- setOn(columnSwitch);
209
- container.classList.add('full-width');
210
- } else {
211
- setVisible(headers, false);
212
- setOff(columnSwitch);
213
- container.classList.remove('full-width');
214
- }
215
- };
216
-
217
- var contexts = ['source', 'text', 'html'];
218
-
219
- var views = {
220
- source: document.querySelector('.source-view'),
221
- html: document.querySelector('.html-view'),
222
- text: document.querySelector('.text-view'),
223
- };
224
-
225
- var toolbar = {
226
- source: document.querySelector('.source-view-switch'),
227
- html: document.querySelector('.html-view-switch'),
228
- text: document.querySelector('.text-view-switch'),
229
- };
230
-
231
- var setOn = function(element) {
232
- element.classList.add('text-primary');
233
- element.classList.remove('text-secondary');
234
- };
235
-
236
- var setOff = function(element) {
237
- element.classList.add('text-secondary');
238
- element.classList.remove('text-primary');
239
- };
240
-
241
- var setDisabled = function(element) {
242
- element.classList.add('disabled');
243
- element.classList.remove('text-secondary');
244
- element.onclick = function () {};
245
- };
246
-
247
- var setVisible = function(element, visible) {
248
- if (visible) {
249
- element.classList.add('visible');
250
- } else {
251
- element.classList.remove('visible');
252
- }
253
- };
254
-
255
- var setView = function(context) {
256
- var key;
257
- for (i = 0; i < contexts.length; i++) {
258
- key = contexts[i];
259
- if (key === context) {
260
- setOn(toolbar[key]);
261
- setVisible(views[key], true);
262
- } else {
263
- setOff(toolbar[key]);
264
- setVisible(views[key], false);
265
- }
266
- }
267
- };
268
-
269
- toolbar.html.onclick = function () { setView('html'); };
270
- toolbar.text.onclick = function () { setView('text'); };
271
- toolbar.source.onclick = function () { setView('source'); };
272
- columnSwitch.onclick = function () { setColumnView(!twoColumnView); };
273
-
274
- if (!hasText) setDisabled(toolbar.text);
275
-
276
- if (hasHtml) {
277
- setView('html');
278
- } else {
279
- setDisabled(toolbar.html);
280
- setDisabled(toolbar.source);
281
- setView('text');
282
- }
283
-
284
- setColumnView(true);
285
-
286
- $('[data-toggle="tooltip"]').tooltip();
287
- })();
101
+ <%= javascript %>
288
102
  </script>
289
103
  </body>
290
104
  </html>