postmortem 0.1.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/layout/layout.css ADDED
@@ -0,0 +1,160 @@
1
+ .headers {
2
+ width: 100%;
3
+ display: none;
4
+ }
5
+
6
+ .row {
7
+ margin: 0;
8
+ }
9
+
10
+ #title {
11
+ float: left;
12
+ padding: 0;
13
+ margin: 0;
14
+ font-size: 1.2rem;
15
+ }
16
+
17
+ #title:hover {
18
+ color: #007bff !important;
19
+ }
20
+
21
+ #inbox {
22
+ overflow-y: auto;
23
+ height: 100%;
24
+ max-height: 100%;
25
+ display: none;
26
+ width: 35rem;
27
+ max-width: 35rem;
28
+ padding-right: 0.2rem;
29
+ }
30
+
31
+ #inbox.visible {
32
+ display: block;
33
+ }
34
+
35
+ #inbox li span.timestamp {
36
+ font-size: 0.8rem;
37
+ margin-right: -0.5rem;
38
+ float: right;
39
+ color: #aaa;
40
+ position: absolute;
41
+ right: 1rem;
42
+ top: 0.8rem;
43
+ }
44
+
45
+ #inbox li a {
46
+ text-decoration: none;
47
+ background-color: transparent;
48
+ max-width: 22rem;
49
+ overflow: hidden;
50
+ display: block;
51
+ white-space: nowrap;
52
+ text-overflow: ellipsis
53
+ }
54
+
55
+ #inbox li a .unread-icon {
56
+ color: #007bff;
57
+ }
58
+
59
+ #inbox li a:visited .unread-icon {
60
+ color: #ffffff00;
61
+ }
62
+
63
+ #inbox li a:hover {
64
+ text-decoration: none;
65
+ }
66
+
67
+ #inbox li.active a {
68
+ color: #fff;
69
+ }
70
+
71
+ #inbox li.active span.timestamp {
72
+ color: #fff;
73
+ }
74
+
75
+ .content {
76
+ background-color: #efefef;
77
+ padding: 1rem 2rem;
78
+ height: 100%;
79
+ }
80
+
81
+ .headers table {
82
+ background-color: #fff;
83
+ }
84
+
85
+ .preview {
86
+ display: none;
87
+ background-color: #fff;
88
+ padding: 2rem;
89
+ height: 100%;
90
+ }
91
+
92
+ .preview.source-view {
93
+ padding: 1.5rem;
94
+ }
95
+
96
+ .preview iframe {
97
+ border-style: none;
98
+ width: 100%;
99
+ height: 100%;
100
+ }
101
+
102
+ .main-row {
103
+ height: 100%;
104
+ }
105
+
106
+ .container {
107
+ height: 90vh;
108
+ margin-top: 2.8rem;
109
+ }
110
+
111
+ .container.full-width {
112
+ max-width: 100%;
113
+ }
114
+
115
+ .visible {
116
+ display: block;
117
+ }
118
+
119
+ .toolbar {
120
+ background-color: #fff;
121
+ text-align: right;
122
+ padding: 0.5rem 1rem;
123
+ position: fixed;
124
+ width: 100%;
125
+ }
126
+
127
+ .toolbar i {
128
+ font-size: 1.2rem;
129
+ cursor: pointer;
130
+ margin: 0 0.2rem;
131
+ }
132
+
133
+ .toolbar i.disabled {
134
+ cursor: default;
135
+ color: #ddd !important;
136
+ }
137
+
138
+ .toolbar .separator {
139
+ border-right: 1px solid #ccc;
140
+ margin-right: 0.5rem;
141
+ margin-left: 0.2rem;
142
+ }
143
+
144
+ .right-column {
145
+ display: flex;
146
+ flex-flow: column;
147
+ height: 100%;
148
+ }
149
+
150
+ #index-iframe, #identity-iframe {
151
+ display: none;
152
+ }
153
+
154
+ .hidden {
155
+ display: none;
156
+ }
157
+
158
+ #download-link:hover {
159
+ color: #007bff !important;
160
+ }
data/layout/layout.js ADDED
@@ -0,0 +1,268 @@
1
+ (function () {
2
+ let previousInbox;
3
+ let currentView;
4
+ let reloadIdentityIframeTimeout;
5
+ let identityUuid;
6
+ let indexUuid;
7
+ let inboxInitialized = false;
8
+
9
+ var htmlIframeDocument = document.querySelector("#html-iframe").contentDocument;
10
+ var indexIframe = document.querySelector("#index-iframe");
11
+ var identityIframe = document.querySelector("#identity-iframe");
12
+ var textIframeDocument = document.querySelector("#text-iframe").contentDocument;
13
+ var sourceIframeDocument = document.querySelector("#source-iframe").contentDocument;
14
+ var sourceHighlightBundle = [
15
+ '<link rel="stylesheet"',
16
+ ' href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/styles/agate.min.css"',
17
+ ' integrity="sha512-mMMMPADD4HAIogAWZbv+WjZTC0itafUZNI0jQm48PMBTApXt11omF5jhS7U1kp3R2Pr6oGJ+JwQKiUkUwCQaUQ=="',
18
+ ' crossorigin="anonymous" />',
19
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/highlight.min.js"',
20
+ ' integrity="sha512-tHQeqtcNWlZtEh8As/4MmZ5qpy0wj04svWFK7MIzLmUVIzaHXS8eod9OmHxyBL1UET5Rchvw7Ih4ZDv5JojZww=="',
21
+ ' crossorigin="anonymous"></' + 'script>',
22
+ '<script>hljs.initHighlightingOnLoad();</' + 'script>',
23
+ ].join('\n');
24
+
25
+ const initialize = () => {
26
+ loadMail(initialData);
27
+
28
+ reloadIdentityIframeTimeout = setTimeout(() => identityIframe.src += '', 3000);
29
+
30
+ window.addEventListener('message', function (ev) {
31
+ switch (ev.data.type) {
32
+ case 'index':
33
+ renderInbox(ev.data.uuid, ev.data.mails);
34
+ break;
35
+ case 'identity':
36
+ compareIdentity(ev.data.uuid);
37
+ break;
38
+ };
39
+ });
40
+
41
+ setInterval(function () { indexIframe.contentWindow.postMessage('HELO', '*'); }, 1000);
42
+ setInterval(function () { identityIframe.contentWindow.postMessage('HELO', '*'); }, 1000);
43
+
44
+ toolbar.html.onclick = function (ev) { setView('html', ev); };
45
+ toolbar.text.onclick = function (ev) { setView('text', ev); };
46
+ toolbar.source.onclick = function (ev) { setView('source', ev); };
47
+ toolbar.headers.onclick = function () { setHeadersView(!headersView); };
48
+ columnSwitch.onclick = function () { setColumnView(!twoColumnView); };
49
+
50
+ if (hasHtml) {
51
+ setView('html');
52
+ } else {
53
+ setView('text');
54
+ }
55
+
56
+ setColumnView(false);
57
+ setHeadersView(true);
58
+
59
+ $('[data-toggle="tooltip"]').tooltip();
60
+ }
61
+
62
+
63
+ var twoColumnView;
64
+ var headersView;
65
+ var headers = document.querySelector('.headers');
66
+ var inbox = document.querySelector('#inbox');
67
+ var columnSwitch = document.querySelector('.column-switch');
68
+ var headersViewSwitch = document.querySelector('.headers-view-switch');
69
+
70
+ var setHeadersView = function (enableHeadersView) {
71
+ headersView = enableHeadersView;
72
+ if (enableHeadersView) {
73
+ setOn(headersViewSwitch);
74
+ headers.classList.add('visible');
75
+ } else {
76
+ setOff(headersViewSwitch);
77
+ headers.classList.remove('visible');
78
+ }
79
+ };
80
+
81
+ var setColumnView = function (enableTwoColumnView) {
82
+ if (!inboxInitialized) return;
83
+
84
+ var container = document.querySelector('.container');
85
+ twoColumnView = enableTwoColumnView;
86
+ if (twoColumnView) {
87
+ setVisible(inbox, true);
88
+ setOn(columnSwitch);
89
+ container.classList.add('full-width');
90
+ } else {
91
+ setVisible(inbox, false);
92
+ setOff(columnSwitch);
93
+ container.classList.remove('full-width');
94
+ }
95
+ };
96
+
97
+ var contexts = ['source', 'text', 'html'];
98
+
99
+ var views = {
100
+ source: document.querySelector('.source-view'),
101
+ html: document.querySelector('.html-view'),
102
+ text: document.querySelector('.text-view'),
103
+ };
104
+
105
+ var toolbar = {
106
+ source: document.querySelector('.source-view-switch'),
107
+ html: document.querySelector('.html-view-switch'),
108
+ text: document.querySelector('.text-view-switch'),
109
+ headers: document.querySelector('.headers-view-switch'),
110
+ };
111
+
112
+ var setOn = function(element) {
113
+ element.classList.add('text-primary');
114
+ element.classList.remove('text-secondary');
115
+ };
116
+
117
+ var setOff = function(element) {
118
+ element.classList.add('text-secondary');
119
+ element.classList.remove('text-primary');
120
+ };
121
+
122
+ var setDisabled = function(element) {
123
+ element.classList.add('disabled');
124
+ element.classList.remove('text-secondary');
125
+ };
126
+
127
+ var setEnabled = function(element) {
128
+ element.classList.remove('disabled');
129
+ element.classList.add('text-secondary');
130
+ };
131
+
132
+ var setVisible = function(element, visible) {
133
+ if (visible) {
134
+ element.classList.add('visible');
135
+ } else {
136
+ element.classList.remove('visible');
137
+ }
138
+ };
139
+
140
+ var setView = function(context, ev) {
141
+ if (ev && $(ev.target).hasClass('disabled')) return;
142
+ var key;
143
+ for (i = 0; i < contexts.length; i++) {
144
+ key = contexts[i];
145
+ if (key === context) {
146
+ setOn(toolbar[key]);
147
+ setVisible(views[key], true);
148
+ } else {
149
+ setOff(toolbar[key]);
150
+ setVisible(views[key], false);
151
+ }
152
+ }
153
+ currentView = context;
154
+ };
155
+
156
+ const arrayIdentical = (a, b) => {
157
+ if (a && !b) return false;
158
+ if (a.length !== b.length) return false;
159
+ if (!a.every((item, index) => item === b[index])) return false;
160
+
161
+ return true;
162
+ };
163
+
164
+ const htmlEscape = (html) => {
165
+ return $("<div></div>").text(html).html();
166
+ };
167
+
168
+ const $headersTemplate = $("#headers-template");
169
+
170
+ const loadHeaders = (mail) => {
171
+ $template = $headersTemplate.clone();
172
+ $('#headers').html($template.html());
173
+ ['subject', 'from', 'replyTo', 'to', 'cc', 'bcc'].forEach(item => {
174
+ const $item = $(`#email-${item}`)
175
+ $item.text(mail[item]);
176
+ if (!mail[item]) $item.parent().addClass('hidden');
177
+ });
178
+ };
179
+
180
+ const loadToolbar = (mail) => {
181
+ if (!mail.textBody) {
182
+ setDisabled(toolbar.text);
183
+ setView('html');
184
+ } else {
185
+ setEnabled(toolbar.text);
186
+ }
187
+
188
+ if (!mail.htmlBody) {
189
+ setDisabled(toolbar.html);
190
+ setDisabled(toolbar.source);
191
+ setView('text');
192
+ } else {
193
+ setEnabled(toolbar.html);
194
+ setEnabled(toolbar.source);
195
+ }
196
+
197
+ setDisabled(columnSwitch);
198
+ setView(currentView);
199
+ };
200
+
201
+ const loadMail = (mail) => {
202
+ htmlIframeDocument.open();
203
+ htmlIframeDocument.write(mail.htmlBody);
204
+ htmlIframeDocument.close();
205
+
206
+ textIframeDocument.open();
207
+ textIframeDocument.write(`<pre>${htmlEscape(mail.textBody)}</pre>`);
208
+ textIframeDocument.close();
209
+
210
+ sourceIframeDocument.open();
211
+ sourceIframeDocument.write(`<pre><code style="padding: 1rem;" class="language-html">${htmlEscape(mail.htmlBody)}</code></pre>`);
212
+ sourceIframeDocument.write(sourceHighlightBundle);
213
+ sourceIframeDocument.close();
214
+
215
+ loadHeaders(mail);
216
+ loadToolbar(mail);
217
+ loadDownloadLink();
218
+ };
219
+
220
+ const loadDownloadLink = () => {
221
+ const blob = new Blob([document.documentElement.innerHTML], { type: 'application/octet-stream' });
222
+ const uri = window.URL.createObjectURL(blob);
223
+ $("#download-link").attr('href', uri);
224
+ };
225
+
226
+ const compareIdentity = (uuid) => {
227
+ clearTimeout(reloadIdentityIframeTimeout);
228
+ reloadIdentityIframeTimeout = setTimeout(() => identityIframe.src += '', 3000);
229
+ if (identityUuid !== uuid) indexIframe.src += '';
230
+ identityUuid = uuid;
231
+ };
232
+
233
+ const renderInbox = (uuid, mails) => {
234
+ if (uuid === indexUuid) {
235
+ return;
236
+ }
237
+ const mailsById = {};
238
+ const html = mails.map((mail, index) => {
239
+ const parsedTimestamp = new Date(mail.timestamp);
240
+ const timestampSpan = `<span class="timestamp">${parsedTimestamp.toLocaleString()}</span>`;
241
+ const classes = ['list-group-item', 'inbox-item'];
242
+ if (window.location.hash === '#' + mail.id) classes.push('active');
243
+ mailsById[mail.id] = mail;
244
+ return `<li data-email-id="${mail.id}" class="${classes.join(' ')}"><a title="${mail.subject}" href="javascript:void(0)">${mail.subject}</a>${timestampSpan}</li>`
245
+ });
246
+ if (arrayIdentical(html, previousInbox)) return;
247
+ previousInbox = html;
248
+ $('#inbox').html('<ul class="list-group">' + html.join('\n') + '</ul>');
249
+ $('.inbox-item').click((ev) => {
250
+ const $target = $(ev.currentTarget);
251
+ const id = $target.data('email-id');
252
+ $('.inbox-item').removeClass('active');
253
+ $target.addClass('active');
254
+ window.location.hash = id;
255
+ setTimeout(() => loadMail(mailsById[id].content), 0);
256
+ });
257
+
258
+ if (!inboxInitialized) {
259
+ setEnabled(columnSwitch);
260
+ setColumnView(true);
261
+ setVisible(inbox, true);
262
+ }
263
+ inboxInitialized = true;
264
+ indexUuid = uuid;
265
+ };
266
+
267
+ initialize();
268
+ })();
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+ <div data-uuid="<%= uuid %>" id="identity">
5
+ </div>
6
+ <script>
7
+ window.addEventListener('message', function (ev) {
8
+ const identity = document.querySelector('#identity');
9
+ const uuid = identity.dataset.uuid;
10
+ const type = 'identity';
11
+ ev.source.postMessage({ uuid, type }, '*');
12
+ });
13
+
14
+ setInterval(function () {
15
+ window.location.reload();
16
+ }, 3000);
17
+
18
+ </script>
19
+ </body>
20
+ </html>