mail_grabber 1.0.0.rc1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/lib/mail_grabber.rb +8 -0
- data/lib/mail_grabber/database_helper.rb +250 -0
- data/lib/mail_grabber/database_queries.rb +102 -0
- data/lib/mail_grabber/delivery_method.rb +28 -0
- data/lib/mail_grabber/error.rb +11 -0
- data/lib/mail_grabber/railtie.rb +14 -0
- data/lib/mail_grabber/version.rb +5 -0
- data/lib/mail_grabber/web.rb +36 -0
- data/lib/mail_grabber/web/application.rb +135 -0
- data/lib/mail_grabber/web/application_helper.rb +14 -0
- data/lib/mail_grabber/web/application_router.rb +79 -0
- data/lib/mail_grabber/web/assets/images/mail_grabber515x500.png +0 -0
- data/lib/mail_grabber/web/assets/javascripts/application.js +539 -0
- data/lib/mail_grabber/web/assets/stylesheets/application.css +159 -0
- data/lib/mail_grabber/web/views/_message_attachment_template.html.erb +7 -0
- data/lib/mail_grabber/web/views/_message_content_template.html.erb +53 -0
- data/lib/mail_grabber/web/views/_message_list_template.html.erb +14 -0
- data/lib/mail_grabber/web/views/index.html.erb +46 -0
- metadata +114 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MailGrabber
|
4
|
+
class DeliveryMethod
|
5
|
+
include DatabaseHelper
|
6
|
+
|
7
|
+
# Initialize MailGrabber delivery method (Rails needs it).
|
8
|
+
def initialize(options = {}); end
|
9
|
+
|
10
|
+
# Catch and save messages into the database that we can check those messages
|
11
|
+
# in MailGrabber web application.
|
12
|
+
#
|
13
|
+
# @param [Mail::Message] message what we would like to send
|
14
|
+
def deliver!(message)
|
15
|
+
unless message.is_a?(Mail::Message)
|
16
|
+
raise Error::WrongParameter,
|
17
|
+
'The given parameter is not a Mail::Message'
|
18
|
+
end
|
19
|
+
|
20
|
+
store_mail(message)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Delivery method settings (needed when run mail.deliver! method)
|
24
|
+
def settings
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MailGrabber
|
4
|
+
class Error < StandardError
|
5
|
+
# Specific error class for errors if database error happen
|
6
|
+
class DatabaseHelperError < Error; end
|
7
|
+
|
8
|
+
# Specific error class for errors if parameter is not given
|
9
|
+
class WrongParameter < Error; end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MailGrabber
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'mail_grabber.add_delivery_method' do
|
6
|
+
ActiveSupport.on_load :action_mailer do
|
7
|
+
ActionMailer::Base.add_delivery_method(
|
8
|
+
:mail_grabber,
|
9
|
+
MailGrabber::DeliveryMethod
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'rack'
|
5
|
+
|
6
|
+
require 'mail_grabber/error'
|
7
|
+
require 'mail_grabber/database_helper'
|
8
|
+
|
9
|
+
require 'mail_grabber/web/application_helper'
|
10
|
+
require 'mail_grabber/web/application_router'
|
11
|
+
require 'mail_grabber/web/application'
|
12
|
+
|
13
|
+
module MailGrabber
|
14
|
+
module Web
|
15
|
+
module_function
|
16
|
+
|
17
|
+
# Method which build a Rack application and run the Web::Application
|
18
|
+
def app
|
19
|
+
@app ||= Rack::Builder.new do
|
20
|
+
use Rack::Static,
|
21
|
+
urls: ['/images', '/javascripts', '/stylesheets'],
|
22
|
+
root: File.expand_path('web/assets', __dir__)
|
23
|
+
|
24
|
+
run Web::Application
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Method to call MailGrabber::Web. This method will call the app method
|
29
|
+
# above.
|
30
|
+
#
|
31
|
+
# @param [Hash] env the environment variables
|
32
|
+
def call(env)
|
33
|
+
app.call(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module MailGrabber
|
6
|
+
module Web
|
7
|
+
class Application
|
8
|
+
extend ApplicationRouter
|
9
|
+
|
10
|
+
include ApplicationHelper
|
11
|
+
include DatabaseHelper
|
12
|
+
|
13
|
+
attr_reader :request, :response
|
14
|
+
|
15
|
+
# Method to call MailGrabber::Web::Application. This method will call the
|
16
|
+
# initialize method and returns back with response of the application.
|
17
|
+
#
|
18
|
+
# @param [Hash] env the environment variables
|
19
|
+
#
|
20
|
+
# @return [Array] the response of the web application
|
21
|
+
# e.g. [200, {}, ['Hello World']]
|
22
|
+
def self.call(env)
|
23
|
+
new(env).response.finish
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initialize web application request and response then process the given
|
27
|
+
# request.
|
28
|
+
#
|
29
|
+
# @param [Hash] env the environment variables
|
30
|
+
def initialize(env)
|
31
|
+
@request = Rack::Request.new(env)
|
32
|
+
@response = Rack::Response.new
|
33
|
+
|
34
|
+
process_request
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extract env['PATH_INFO'] value. If the path info is empty then it will
|
38
|
+
# return with root path.
|
39
|
+
#
|
40
|
+
# @return [String] path the requested path or the root path if this value
|
41
|
+
# is empty
|
42
|
+
def path
|
43
|
+
@path ||= request.path_info.empty? ? '/' : request.path_info
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method returns back with extracted request parameters.
|
47
|
+
#
|
48
|
+
# @return [Hash] params
|
49
|
+
def params
|
50
|
+
request.params
|
51
|
+
end
|
52
|
+
|
53
|
+
# Extract env['REQUEST_METHOD'] value.
|
54
|
+
#
|
55
|
+
# @return [String] request_method with e.g. GET, POST or DELETE etc.
|
56
|
+
def request_method
|
57
|
+
request.request_method
|
58
|
+
end
|
59
|
+
|
60
|
+
# Extract env['SCRIPT_NAME'] value.
|
61
|
+
#
|
62
|
+
# @return [String] script_name the initial portion of the request 'path'
|
63
|
+
def script_name
|
64
|
+
request.script_name
|
65
|
+
end
|
66
|
+
|
67
|
+
# Parse the routes of the ApplicationRouter and tries to find matching
|
68
|
+
# route for the request method, which was defined in the
|
69
|
+
# get, post, put, patch or delete blocks. If the 'extracted_params' is nil
|
70
|
+
# then it could not found any defined routes. If it can find a defined
|
71
|
+
# route then it saves the params and call the given block. If it cannot
|
72
|
+
# find anything then it will set response with 404 Not Found.
|
73
|
+
def process_request
|
74
|
+
self.class.routes[request_method].each do |route|
|
75
|
+
extracted_params = route.extract_params(path)
|
76
|
+
|
77
|
+
next unless extracted_params
|
78
|
+
|
79
|
+
request.update_param('request_params', extracted_params)
|
80
|
+
|
81
|
+
return instance_exec(&route.block)
|
82
|
+
end
|
83
|
+
|
84
|
+
response.status = 404
|
85
|
+
response.write('Not Found')
|
86
|
+
end
|
87
|
+
|
88
|
+
get '/' do
|
89
|
+
response.write(render('index.html.erb'))
|
90
|
+
end
|
91
|
+
|
92
|
+
get '/messages.json' do
|
93
|
+
result =
|
94
|
+
if params['page'].nil? || params['per_page'].nil?
|
95
|
+
select_all_messages
|
96
|
+
else
|
97
|
+
select_messages_by(params['page'], params['per_page'])
|
98
|
+
end
|
99
|
+
|
100
|
+
response.write(result.to_json)
|
101
|
+
end
|
102
|
+
|
103
|
+
get '/message/:id.json' do
|
104
|
+
message = select_message_by(params['request_params']['id'])
|
105
|
+
message_parts = select_message_parts_by(params['request_params']['id'])
|
106
|
+
|
107
|
+
response.write(
|
108
|
+
{ message: message, message_parts: message_parts }.to_json
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
delete '/messages.json' do
|
113
|
+
delete_all_messages
|
114
|
+
|
115
|
+
response.write({ info: 'All messages have been deleted' }.to_json)
|
116
|
+
end
|
117
|
+
|
118
|
+
delete '/message/:id.json' do
|
119
|
+
delete_message_by(params['request_params']['id'])
|
120
|
+
|
121
|
+
response.write({ info: 'Message has been deleted' }.to_json)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Render erb template from the views folder.
|
125
|
+
#
|
126
|
+
# @param [String] template
|
127
|
+
#
|
128
|
+
# @return [String] template with the result
|
129
|
+
def render(template)
|
130
|
+
path = File.expand_path("views/#{template}", __dir__)
|
131
|
+
ERB.new(File.read(path)).result(binding)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MailGrabber
|
4
|
+
module Web
|
5
|
+
# Helper module for views
|
6
|
+
module ApplicationHelper
|
7
|
+
# This method helps us that e.g. we can load style or javascript files
|
8
|
+
# when we are running this application standalone or in Ruby on Rails.
|
9
|
+
def root_path
|
10
|
+
script_name.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MailGrabber
|
4
|
+
module Web
|
5
|
+
module ApplicationRouter
|
6
|
+
NAMED_SEGMENTS_PATTERN = %r{/([^/]*):([^.:$/]+)}.freeze
|
7
|
+
|
8
|
+
attr_reader :routes
|
9
|
+
|
10
|
+
Route =
|
11
|
+
Struct.new(:pattern, :block) do
|
12
|
+
# Extract parameters from the given path. All routes has a
|
13
|
+
# path pattern which helps to the router to find which block it should
|
14
|
+
# execute. If path contains request parameters like '/test/1' then
|
15
|
+
# it will match with the '/test/:id' pattern. In this case it will
|
16
|
+
# return with '{"id" => "1"}' hash. If it is just a simple path like
|
17
|
+
# '/' and it has a pattern to match then it will return with '{}'.
|
18
|
+
# In the other case it will return with nil.
|
19
|
+
#
|
20
|
+
# @param [String] path
|
21
|
+
#
|
22
|
+
# @return [Hash/NilClass] with the extracted parameters, empty Hash
|
23
|
+
# or nil
|
24
|
+
def extract_params(path)
|
25
|
+
if pattern.match?(NAMED_SEGMENTS_PATTERN)
|
26
|
+
named_pattern =
|
27
|
+
pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
|
28
|
+
|
29
|
+
path.match(Regexp.new("\\A#{named_pattern}\\Z"))&.named_captures
|
30
|
+
elsif path == pattern
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define route method that we can define routing blocks.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
#
|
40
|
+
# get '/' do
|
41
|
+
# response.write 'Hello MailGrabber'
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
#
|
46
|
+
# method pattern block
|
47
|
+
# | | |
|
48
|
+
# get '/test' do ... end
|
49
|
+
#
|
50
|
+
%w[GET POST PUT PATCH DELETE].each do |method|
|
51
|
+
define_method(method.downcase) do |pattern, &block|
|
52
|
+
route(method, pattern, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Store routes with the request method, the provided pattern and the given
|
57
|
+
# block.
|
58
|
+
#
|
59
|
+
# @param [String] method e.g. GET, POST etc.
|
60
|
+
# @param [String] pattern the path what we are looking for
|
61
|
+
# @param [Proc] block what we will run
|
62
|
+
def route(method, pattern, &block)
|
63
|
+
@routes ||= {}
|
64
|
+
|
65
|
+
set_route('HEAD', pattern, &block) if method == 'GET'
|
66
|
+
set_route(method, pattern, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set routes Hash with the Route object.
|
70
|
+
#
|
71
|
+
# @param [String] method e.g. GET, POST etc.
|
72
|
+
# @param [String] pattern the path what we are looking for
|
73
|
+
# @param [Proc] block what we will run
|
74
|
+
def set_route(method, pattern, &block)
|
75
|
+
(@routes[method] ||= []) << Route.new(pattern, block)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
Binary file
|
@@ -0,0 +1,539 @@
|
|
1
|
+
(function() {
|
2
|
+
var MailGrabber = {
|
3
|
+
/**
|
4
|
+
* Change which content (message part) should show. When the page is loading
|
5
|
+
* the HTML content will be active and the others will be hidden. When we
|
6
|
+
* click on tab e.g. Plain Text then this content will active and other
|
7
|
+
* hidden.
|
8
|
+
*
|
9
|
+
* @param {Object} event - which part we clicked
|
10
|
+
*/
|
11
|
+
changeMessageContent: function(event) {
|
12
|
+
var messageTabs =
|
13
|
+
document
|
14
|
+
.querySelectorAll('[data-message-tab]');
|
15
|
+
|
16
|
+
var messageContents =
|
17
|
+
document
|
18
|
+
.querySelectorAll('[data-message-content]');
|
19
|
+
|
20
|
+
var clickedTabType = event.target.dataset.messageTab;
|
21
|
+
|
22
|
+
messageTabs.forEach(function(messageTab, index) {
|
23
|
+
messageTab.classList.remove('active');
|
24
|
+
messageContents[index].classList.remove('hide');
|
25
|
+
|
26
|
+
if(messageTab.dataset.messageTab === clickedTabType) {
|
27
|
+
messageTab.classList.add('active');
|
28
|
+
}
|
29
|
+
|
30
|
+
if(messageContents[index].dataset.messageContent !== clickedTabType) {
|
31
|
+
messageContents[index].classList.add('hide');
|
32
|
+
}
|
33
|
+
});
|
34
|
+
},
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Show default backgroud image instead of any content.
|
38
|
+
*/
|
39
|
+
defaultBackground: function() {
|
40
|
+
var messageContent =
|
41
|
+
document
|
42
|
+
.querySelector('div[data-content-type=message-content]');
|
43
|
+
|
44
|
+
messageContent.innerHTML = '';
|
45
|
+
|
46
|
+
messageContent.style.background =
|
47
|
+
"url('" + messageContent.dataset.backgroundImage +
|
48
|
+
"') center center no-repeat";
|
49
|
+
messageContent.style.backgroundSize = '50%';
|
50
|
+
messageContent.style.opacity = '10%';
|
51
|
+
},
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Delete all content (message list and message content as well),
|
55
|
+
* set default backgroud and the infinite scroll params when we click on
|
56
|
+
* the Reload or Delete tabs.
|
57
|
+
*/
|
58
|
+
deleteContent: function() {
|
59
|
+
MailGrabber.lastMessageId = -1;
|
60
|
+
MailGrabber.page = 1;
|
61
|
+
MailGrabber.defaultBackground();
|
62
|
+
|
63
|
+
document
|
64
|
+
.querySelector('ul[data-content-type=message-list]')
|
65
|
+
.innerHTML = '';
|
66
|
+
},
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Delete a message when we click on the Delete tab. It will remove all
|
70
|
+
* content and reload the message list.
|
71
|
+
*
|
72
|
+
* @param {Number} messageId - which message we would like to delete
|
73
|
+
*/
|
74
|
+
deleteMessage: function(messageId) {
|
75
|
+
MailGrabber.request('DELETE', '/message/' + messageId + '.json',
|
76
|
+
function() {
|
77
|
+
MailGrabber.deleteContent();
|
78
|
+
MailGrabber.getMessageList();
|
79
|
+
}
|
80
|
+
);
|
81
|
+
},
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Delete message list when we click on the Clear tab. It will remove
|
85
|
+
* everything.
|
86
|
+
*/
|
87
|
+
deleteMessageList: function() {
|
88
|
+
MailGrabber.request('DELETE', '/messages.json', function() {
|
89
|
+
MailGrabber.deleteContent();
|
90
|
+
});
|
91
|
+
},
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Get a message and render the message content, when we click on an item
|
95
|
+
* from the message list.
|
96
|
+
*
|
97
|
+
* @param {Number} messageId - which message we would like to see
|
98
|
+
*/
|
99
|
+
getMessage: function(messageId) {
|
100
|
+
MailGrabber.request('GET', '/message/' + messageId + '.json',
|
101
|
+
function(response) {
|
102
|
+
MailGrabber.renderMessageContent(JSON.parse(response));
|
103
|
+
}
|
104
|
+
);
|
105
|
+
},
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Get a list of the messages. Also change the params of the infinite scroll
|
109
|
+
* that we can know which page we are on. It checks the message ids to not
|
110
|
+
* load those messages which are already on the list.
|
111
|
+
*/
|
112
|
+
getMessageList: function() {
|
113
|
+
var messageIds;
|
114
|
+
|
115
|
+
MailGrabber.request('GET', '/messages.json?page=' + MailGrabber.page +
|
116
|
+
'&per_page=' + MailGrabber.perPage,
|
117
|
+
function(response) {
|
118
|
+
response = JSON.parse(response);
|
119
|
+
messageIds = response.map(function(hash) { return hash['id'] });
|
120
|
+
|
121
|
+
if(response.length > 0) {
|
122
|
+
if(messageIds.indexOf(MailGrabber.lastMessageId) === -1) {
|
123
|
+
MailGrabber.lastMessageId = messageIds.pop();
|
124
|
+
MailGrabber.page++;
|
125
|
+
MailGrabber.renderMessageList(response);
|
126
|
+
} else {
|
127
|
+
MailGrabber.reloadMessageList();
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
);
|
132
|
+
},
|
133
|
+
|
134
|
+
/**
|
135
|
+
* Scrolling infinitely. Count the height of the list and if we reached the
|
136
|
+
* bottom of the list then tries to load more message.
|
137
|
+
*
|
138
|
+
* @param {Object} messageList - the message list container
|
139
|
+
*/
|
140
|
+
infiniteScroll: function(messageList) {
|
141
|
+
var scrollHeight = messageList.scrollHeight;
|
142
|
+
var scrollTop = messageList.scrollTop;
|
143
|
+
var clientHeight = messageList.clientHeight;
|
144
|
+
|
145
|
+
if(scrollHeight - scrollTop === clientHeight) {
|
146
|
+
MailGrabber.getMessageList();
|
147
|
+
}
|
148
|
+
},
|
149
|
+
|
150
|
+
/**
|
151
|
+
* Initialize MailGrabber. Add some event listeners to the Reload and the
|
152
|
+
* Clear tabs then load messages and the default background.
|
153
|
+
*/
|
154
|
+
init: function() {
|
155
|
+
document
|
156
|
+
.querySelector('li[data-content-type=message-reload-tab]')
|
157
|
+
.addEventListener('click', MailGrabber.reloadMessageList);
|
158
|
+
|
159
|
+
document
|
160
|
+
.querySelector('li[data-content-type=message-clear-tab]')
|
161
|
+
.addEventListener('click', MailGrabber.deleteMessageList);
|
162
|
+
|
163
|
+
MailGrabber.loadMessageList();
|
164
|
+
MailGrabber.defaultBackground();
|
165
|
+
},
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Format the given date or time.
|
169
|
+
*
|
170
|
+
* @param {DateTime} dateTime - the message created at attribute
|
171
|
+
* @param {String} ouputType - what type we would like to see
|
172
|
+
*
|
173
|
+
* @return {String} the new format of the date or time
|
174
|
+
*/
|
175
|
+
formatDateTime: function(dateTime, outputType) {
|
176
|
+
dateTime = new Date(dateTime);
|
177
|
+
var dateTimeNow = new Date();
|
178
|
+
// Sun Feb 21 2021 21:00:00 GMT+0100 (Central European Standard Time)
|
179
|
+
// 0 1 2 3 4 5 6 7 8 9
|
180
|
+
var dateTimeComponents = dateTime.toString().split(' ');
|
181
|
+
var output;
|
182
|
+
|
183
|
+
switch(outputType) {
|
184
|
+
case 'messageListDateOrTime':
|
185
|
+
if(dateTime.getDate() === dateTimeNow.getDate()) {
|
186
|
+
output = MailGrabber.formatTime(dateTimeComponents[4]);
|
187
|
+
} else if(dateTime.getFullYear() === dateTimeNow.getFullYear()) {
|
188
|
+
output = dateTimeComponents[1] + ' ' + dateTimeComponents[2];
|
189
|
+
} else {
|
190
|
+
output = dateTimeComponents[3] + ' ' + dateTimeComponents[1] + ' ' +
|
191
|
+
dateTimeComponents[2];
|
192
|
+
}
|
193
|
+
|
194
|
+
break;
|
195
|
+
default:
|
196
|
+
output = dateTimeComponents[3] + ' ' + dateTimeComponents[1] + ' ' +
|
197
|
+
dateTimeComponents[2] + ' - ' +
|
198
|
+
MailGrabber.formatTime(dateTimeComponents[4]);
|
199
|
+
}
|
200
|
+
|
201
|
+
return output;
|
202
|
+
},
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Format the given number (attachment size).
|
206
|
+
*
|
207
|
+
* @param {Number} size - the size of the attachment in bytes
|
208
|
+
*
|
209
|
+
* @return {String} the formatted number with unit
|
210
|
+
*/
|
211
|
+
formatSize: function(size) {
|
212
|
+
var exponent = (Math.log(size) / Math.log(1024)) | 0;
|
213
|
+
var number = +(size / Math.pow(1024, exponent)).toFixed(1);
|
214
|
+
|
215
|
+
return number + ' ' + ('KMGTPEZY'[exponent - 1] || '') + 'B';
|
216
|
+
},
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Format the given time.
|
220
|
+
*
|
221
|
+
* @param {String} time
|
222
|
+
*
|
223
|
+
* @return {String} the new format of the time
|
224
|
+
*/
|
225
|
+
formatTime: function(time) {
|
226
|
+
var timeComponents = time.split(':');
|
227
|
+
|
228
|
+
return timeComponents[0] + ':' + timeComponents[1];
|
229
|
+
},
|
230
|
+
|
231
|
+
/**
|
232
|
+
* The last message id what loaded. If it is -1 then we have no any
|
233
|
+
* messages.
|
234
|
+
*/
|
235
|
+
lastMessageId: -1,
|
236
|
+
|
237
|
+
/**
|
238
|
+
* Load the message list. Also add event listener for infinite scroll.
|
239
|
+
*/
|
240
|
+
loadMessageList: function() {
|
241
|
+
var messageList =
|
242
|
+
document
|
243
|
+
.querySelector('ul[data-content-type=message-list]');
|
244
|
+
|
245
|
+
messageList.addEventListener('scroll', function() {
|
246
|
+
MailGrabber.infiniteScroll(messageList);
|
247
|
+
});
|
248
|
+
|
249
|
+
MailGrabber.getMessageList();
|
250
|
+
},
|
251
|
+
|
252
|
+
/**
|
253
|
+
* Params that we can follow how many messages we have on the list. We are
|
254
|
+
* loading 15 messages in every requests.
|
255
|
+
*/
|
256
|
+
page: 1,
|
257
|
+
perPage: 15,
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Reload the message list. When we have new messages in the database, but
|
261
|
+
* we scrolled down or clicked on the Reload tab.
|
262
|
+
*/
|
263
|
+
reloadMessageList: function() {
|
264
|
+
MailGrabber.deleteContent();
|
265
|
+
MailGrabber.getMessageList();
|
266
|
+
},
|
267
|
+
|
268
|
+
/**
|
269
|
+
* Render message attachment. Show the list of attachments at the top of the
|
270
|
+
* message content.
|
271
|
+
*
|
272
|
+
* @param {Object} messageAttachments - the message attachments container
|
273
|
+
* @param {Object} messagePart - a message part which we loaded
|
274
|
+
*/
|
275
|
+
renderMessageAttachment: function(messageAttachments, messagePart) {
|
276
|
+
var messageAttachment = document.createElement('a');
|
277
|
+
var fileSize = document.createElement('span');
|
278
|
+
var messageAttachmentTemplate =
|
279
|
+
document
|
280
|
+
.querySelector(
|
281
|
+
'template[data-content-type=message-attachment-template]'
|
282
|
+
)
|
283
|
+
.content
|
284
|
+
.cloneNode(true);
|
285
|
+
|
286
|
+
messageAttachment.href =
|
287
|
+
'data:' + messagePart.mime_type + ';base64,' + messagePart.body;
|
288
|
+
messageAttachment.download = messagePart.filename;
|
289
|
+
messageAttachment.innerHTML = messagePart.filename;
|
290
|
+
messageAttachment.classList.add('color-black', 'no-text-decoration');
|
291
|
+
|
292
|
+
fileSize.innerHTML = MailGrabber.formatSize(messagePart.size);
|
293
|
+
fileSize.classList.add('color-gray', 'font-size-0_9', 'padding-left-10');
|
294
|
+
|
295
|
+
[messageAttachment, fileSize].forEach(function(node) {
|
296
|
+
messageAttachmentTemplate
|
297
|
+
.querySelector('li[data-content-type=message-attachment]')
|
298
|
+
.appendChild(node);
|
299
|
+
});
|
300
|
+
|
301
|
+
messageAttachments.classList.remove('hide');
|
302
|
+
messageAttachments.appendChild(messageAttachmentTemplate);
|
303
|
+
},
|
304
|
+
|
305
|
+
/**
|
306
|
+
* Render the HTML part of the message. If the message has inline images
|
307
|
+
* then it will render those images as well. An iframe will contain this
|
308
|
+
* content.
|
309
|
+
*
|
310
|
+
* @param {Object} iFrame - the iframe HTML tag
|
311
|
+
* @param {Object} messageParts - the parts of the message
|
312
|
+
* @param {Object} messageHtmlPart - the HTML part of the message
|
313
|
+
*/
|
314
|
+
renderMessageHtmlPart: function(iFrame, messageParts, messageHtmlPart) {
|
315
|
+
var messageInlineAttachmentRegExp = new RegExp("cid:");
|
316
|
+
|
317
|
+
if(messageInlineAttachmentRegExp.test(messageHtmlPart)) {
|
318
|
+
messageHtmlPart =
|
319
|
+
MailGrabber.renderMessageInlineAttachments(
|
320
|
+
messageParts, messageHtmlPart
|
321
|
+
);
|
322
|
+
}
|
323
|
+
|
324
|
+
iFrame.srcdoc = messageHtmlPart;
|
325
|
+
iFrame.onload = function() {
|
326
|
+
iFrame.height = iFrame.contentDocument.body.scrollHeight + 65;
|
327
|
+
}
|
328
|
+
},
|
329
|
+
|
330
|
+
/**
|
331
|
+
* Render inline images of the message. Change the cid:something12345 with
|
332
|
+
* the encoded image content.
|
333
|
+
*
|
334
|
+
* @param {Object} messageParts - the parts of the message
|
335
|
+
* @param {Object} messageHtmlPart - the HTML part of the message
|
336
|
+
*
|
337
|
+
* @return {Object} messageHtmlPart - the modified HTML part of the message
|
338
|
+
*/
|
339
|
+
renderMessageInlineAttachments: function(messageParts, messageHtmlPart) {
|
340
|
+
messageParts.forEach(function(messagePart) {
|
341
|
+
if(messagePart.is_attachment === 1 && messagePart.is_inline === 1) {
|
342
|
+
messageHtmlPart = messageHtmlPart.replace('cid:' + messagePart.cid,
|
343
|
+
'data:' + messagePart.mime_type + ';base64,' + messagePart.body);
|
344
|
+
}
|
345
|
+
});
|
346
|
+
|
347
|
+
return messageHtmlPart;
|
348
|
+
},
|
349
|
+
|
350
|
+
/**
|
351
|
+
* Render the content of the message (all parts, inline attachments and
|
352
|
+
* attachments). Also it sets up event listeners of the HTML, PlainText,
|
353
|
+
* Raw, Delete and Close tabs.
|
354
|
+
*
|
355
|
+
* @param {Object} response - the response the get message request
|
356
|
+
*/
|
357
|
+
renderMessageContent: function(response) {
|
358
|
+
var message = response.message;
|
359
|
+
var messageParts = response.message_parts;
|
360
|
+
var messageContentTemplate =
|
361
|
+
document
|
362
|
+
.querySelector('template[data-content-type=message-content-template]')
|
363
|
+
.content
|
364
|
+
.cloneNode(true);
|
365
|
+
|
366
|
+
var messageContent =
|
367
|
+
document
|
368
|
+
.querySelector('div[data-content-type=message-content]');
|
369
|
+
|
370
|
+
messageContent.removeAttribute('style');
|
371
|
+
messageContent.innerHTML = '';
|
372
|
+
|
373
|
+
messageContentTemplate
|
374
|
+
.querySelector('div[data-content-type=message-subject]')
|
375
|
+
.innerHTML = message.subject;
|
376
|
+
|
377
|
+
messageContentTemplate
|
378
|
+
.querySelector('dl[data-content-type=metadata]')
|
379
|
+
.innerHTML = MailGrabber.renderMetadata(message);
|
380
|
+
|
381
|
+
messageContentTemplate
|
382
|
+
.querySelector('time[data-content-type=message-sent-at]')
|
383
|
+
.innerHTML = MailGrabber.formatDateTime(message.created_at);
|
384
|
+
|
385
|
+
messageContentTemplate
|
386
|
+
.querySelectorAll('[data-message-tab]')
|
387
|
+
.forEach(function(tab) {
|
388
|
+
tab.addEventListener('click', MailGrabber.changeMessageContent);
|
389
|
+
});
|
390
|
+
|
391
|
+
messageContentTemplate
|
392
|
+
.querySelector('li[data-content-type=message-delete-tab]')
|
393
|
+
.addEventListener('click', function() {
|
394
|
+
MailGrabber.deleteMessage(message.id);
|
395
|
+
});
|
396
|
+
|
397
|
+
messageContentTemplate
|
398
|
+
.querySelector('li[data-content-type=message-close-tab]')
|
399
|
+
.addEventListener('click', MailGrabber.defaultBackground);
|
400
|
+
|
401
|
+
messageParts.forEach(function(messagePart) {
|
402
|
+
if(messagePart.is_attachment === 0 && messagePart.is_inline === 0) {
|
403
|
+
switch(messagePart.mime_type) {
|
404
|
+
case 'text/html':
|
405
|
+
MailGrabber.renderMessageHtmlPart(
|
406
|
+
messageContentTemplate
|
407
|
+
.querySelector('iframe[data-content-type=message-html-body]'),
|
408
|
+
messageParts,
|
409
|
+
messagePart.body
|
410
|
+
);
|
411
|
+
|
412
|
+
break;
|
413
|
+
case 'text/plain':
|
414
|
+
messageContentTemplate
|
415
|
+
.querySelector('pre[data-content-type=message-text-body]')
|
416
|
+
.innerText = messagePart.body;
|
417
|
+
|
418
|
+
break;
|
419
|
+
}
|
420
|
+
} else if(messagePart.is_attachment === 1 &&
|
421
|
+
messagePart.is_inline === 0) {
|
422
|
+
MailGrabber.renderMessageAttachment(
|
423
|
+
messageContentTemplate
|
424
|
+
.querySelector('ul[data-content-type=message-attachments]'),
|
425
|
+
messagePart
|
426
|
+
);
|
427
|
+
}
|
428
|
+
});
|
429
|
+
|
430
|
+
messageContentTemplate
|
431
|
+
.querySelector('pre[data-content-type=message-raw-body]')
|
432
|
+
.innerText = message.raw;
|
433
|
+
|
434
|
+
messageContent.appendChild(messageContentTemplate);
|
435
|
+
},
|
436
|
+
|
437
|
+
/**
|
438
|
+
* Render the list of the messages. Also add event listener when click on a
|
439
|
+
* message then it will load that conent.
|
440
|
+
*
|
441
|
+
* @param {Object} messages - the list of the given message.
|
442
|
+
*/
|
443
|
+
renderMessageList: function(messages) {
|
444
|
+
var messageListTemplate;
|
445
|
+
|
446
|
+
var messageList =
|
447
|
+
document
|
448
|
+
.querySelector('ul[data-content-type=message-list]');
|
449
|
+
|
450
|
+
messages.forEach(function(message) {
|
451
|
+
messageListTemplate =
|
452
|
+
document
|
453
|
+
.querySelector('template[data-content-type=message-list-template]')
|
454
|
+
.content
|
455
|
+
.cloneNode(true);
|
456
|
+
|
457
|
+
messageListTemplate
|
458
|
+
.querySelector('li')
|
459
|
+
.addEventListener('click', function() {
|
460
|
+
MailGrabber.getMessage(message.id);
|
461
|
+
});
|
462
|
+
|
463
|
+
messageListTemplate
|
464
|
+
.querySelector('div[data-content-type=message-senders]')
|
465
|
+
.innerHTML = message.senders;
|
466
|
+
|
467
|
+
messageListTemplate
|
468
|
+
.querySelector('time[data-content-type=message-sent-at]')
|
469
|
+
.innerHTML =
|
470
|
+
MailGrabber
|
471
|
+
.formatDateTime(message.created_at, 'messageListDateOrTime');
|
472
|
+
|
473
|
+
messageListTemplate
|
474
|
+
.querySelector('div[data-content-type=message-subject]')
|
475
|
+
.innerHTML = message.subject;
|
476
|
+
|
477
|
+
messageList.appendChild(messageListTemplate);
|
478
|
+
});
|
479
|
+
},
|
480
|
+
|
481
|
+
/**
|
482
|
+
* Render senders and recipients data of the message.
|
483
|
+
*
|
484
|
+
* @param {Object} message - the requested message
|
485
|
+
*
|
486
|
+
* @return {Object} the rendered metadata
|
487
|
+
*/
|
488
|
+
renderMetadata: function(message) {
|
489
|
+
var metadata =
|
490
|
+
'<dt>From:</dt><dd>' + message.senders + '</dd>' +
|
491
|
+
'<dt>To:</dt><dd>' + message.recipients + '</dd>';
|
492
|
+
|
493
|
+
if(message.carbon_copy) {
|
494
|
+
metadata += '<dt>Cc:</dt><dd>' + message.carbon_copy + '</dd>';
|
495
|
+
}
|
496
|
+
|
497
|
+
if(message.blind_carbon_copy) {
|
498
|
+
metadata += '<dt>Bcc:</dt><dd>' + message.blind_carbon_copy + '</dd>';
|
499
|
+
}
|
500
|
+
|
501
|
+
return metadata;
|
502
|
+
},
|
503
|
+
|
504
|
+
/**
|
505
|
+
* Request function to get data from the server.
|
506
|
+
*
|
507
|
+
* @param {String} method - the request method e.g. GET, POST, DELETE
|
508
|
+
* @param {String} path - the path which we can get/send data
|
509
|
+
* @param {Function} fn - the function which handle the response
|
510
|
+
*/
|
511
|
+
request: function(method, path, fn) {
|
512
|
+
var xhr = new XMLHttpRequest();
|
513
|
+
|
514
|
+
xhr.onload = function() {
|
515
|
+
if(xhr.status === 200) {
|
516
|
+
fn(xhr.responseText);
|
517
|
+
} else {
|
518
|
+
console.log('MailGrabberRequestError:', xhr.status, xhr.statusText);
|
519
|
+
}
|
520
|
+
};
|
521
|
+
xhr.open(method, MailGrabber.rootPath() + path, true);
|
522
|
+
xhr.send();
|
523
|
+
},
|
524
|
+
|
525
|
+
/**
|
526
|
+
* Root path which returns back with the server's root path. It can be empty
|
527
|
+
* string or a string. It depends on how the server is running (standalone
|
528
|
+
* or in Ruby on Rails).
|
529
|
+
*/
|
530
|
+
rootPath: function() {
|
531
|
+
return document.querySelector('body').dataset.rootPath;
|
532
|
+
}
|
533
|
+
};
|
534
|
+
|
535
|
+
/**
|
536
|
+
* When DOM loaded then call MailGrabber's init function.
|
537
|
+
*/
|
538
|
+
document.addEventListener('DOMContentLoaded', MailGrabber.init);
|
539
|
+
}).call(this);
|