Kaynağa Gözat

Merge pull request #10 from babolivier/development

Custom fields
Brendan Abolivier 7 yıl önce
ebeveyn
işleme
54f7999c1e
10 değiştirilmiş dosya ile 751 ekleme ve 441 silme
  1. 3
    0
      .gitignore
  2. 112
    35
      README.md
  3. 319
    166
      front/form.js
  4. 11
    11
      front/index.html
  5. 19
    18
      locales/en.json
  6. 19
    18
      locales/fr.json
  7. 235
    166
      server.js
  8. 17
    17
      settings.example.json
  9. 16
    0
      template.example.pug
  10. 0
    10
      template.pug

+ 3
- 0
.gitignore Dosyayı Görüntüle

@@ -39,6 +39,9 @@ jspm_packages
39 39
 # Credentials
40 40
 settings.json
41 41
 
42
+# Template
43
+template.pug
44
+
42 45
 # Test files
43 46
 test*.js
44 47
 

+ 112
- 35
README.md Dosyayı Görüntüle

@@ -39,9 +39,9 @@ First, include the script in your HTML page's header:
39 39
 
40 40
 ```html
41 41
 <head>
42
-    ...
43
-    <script src="http://www.example.tld:1970/form.js" charset="utf-8"></script>
44
-    ...
42
+	...
43
+	<script src="http://www.example.tld:1970/form.js" charset="utf-8"></script>
44
+	...
45 45
 </head>
46 46
 ```
47 47
 
@@ -49,12 +49,12 @@ Then, add an empty `<form>` tag to your page's body. It **must** have an ID. Now
49 49
 
50 50
 ```html
51 51
 <body>
52
-    ...
53
-    <form id="smam"></form>
54
-    <script type="text/javascript">
55
-        generateForm('smam');
56
-    </script>
57
-    ...
52
+	...
53
+	<form id="smam"></form>
54
+	<script type="text/javascript">
55
+		generateForm('smam');
56
+	</script>
57
+	...
58 58
 </body>
59 59
 ```
60 60
 
@@ -64,23 +64,45 @@ First, you must rename the `settings.example.conf` into `settings.conf`, and edi
64 64
 
65 65
 ```json
66 66
 {
67
-    "mailserver": {
68
-        "pool": true,
69
-        "host": "mail.example.tld",
70
-        "port": 465,
71
-        "secure": true,
72
-        "auth": {
73
-            "user": "noreply@noreply.tld",
74
-            "pass": "hackme"
75
-        }
76
-    },
77
-    "recipients": [
78
-        "you@example.tld",
79
-        "someone.else@example.com"
80
-    ],
81
-    "formOrigin": "https://example.tld",
82
-    "language": "en",
83
-    "labels": true
67
+	"mailserver": {
68
+		"pool": true,
69
+		"host": "mail.example.tld",
70
+		"port": 465,
71
+		"secure": true,
72
+		"auth": {
73
+			"user": "noreply@noreply.tld",
74
+			"pass": "hackme"
75
+		}
76
+	},
77
+	"recipients": [
78
+		"you@example.tld",
79
+		"someone.else@example.com"
80
+	],
81
+	"formOrigin": "https://example.tld",
82
+	"language": "en",
83
+	"labels": true,
84
+	"customFields": {
85
+		"deadline": {
86
+			"label": "Development deadline",
87
+			"type": "select",
88
+			"options": [
89
+				"A week",
90
+				"A month",
91
+				"More than a month"
92
+			],
93
+			"required": true
94
+		},
95
+		"domain": {
96
+			"label": "Website domain",
97
+			"type": "text",
98
+			"required": false
99
+		},
100
+		"budget_max": {
101
+			"label": "Maximum budget (€)",
102
+			"type": "number",
103
+			"required": true
104
+		}
105
+	}
84 106
 }
85 107
 ```
86 108
 
@@ -94,10 +116,46 @@ The `language` string tells SMAM in which language you want your form served. Po
94 116
 
95 117
 Finally, the `labels` setting is a boolean to set whether or not labels will be displayed in the `<form></form>` block. If set to `false`, the form will still display the front-end strings ("Your name", "Your e-mail address"...), but only as placeholders in the text fields. If set to true, the said strings will appear as text fields' placeholders but also as labels outside of the fields. If not set, defaults to `true`.
96 118
 
119
+The `customFields` section is optional and describes custom form fields, which are described below.
120
+
121
+## Custom fields
122
+
123
+SMAM allows you to add custom fields to your form (in addition to the default ones, which are the sender's name, the sender's e-mail address, the message's subject and the message's content). These fields will be added in your form below the content's field, in a tag defined by the settings file (one of &lt;input&gt;, &lt;select&gt; and &lt;textarea&gt;). We'll see below how to set the field's type.
124
+
125
+A custom field is defined in the `customFields` section of your settings file, as described below:
126
+
127
+```json
128
+"field_name": {
129
+	"label": "My field",
130
+	"type": "select",
131
+	"required": true,
132
+	"options": [
133
+		"Option 1",
134
+		"Option 2",
135
+		"Option 3"
136
+	]
137
+}
138
+```
139
+
140
+* **field_name** (required) is an identifier for your field, it will only be used internally by the software.
141
+* **label** (required) is both the label/placeholder displayed with your field in the form and the label displayed next to the user-input value in the final e-mail.
142
+* **type** (required) is the type of your field. If you set it to "select", the form field will use a &lt;select&gt; tag (this will require the `options` parameters being set). "textarea" will set the field to use the &lt;textarea&gt; tag. If any other value is set here, it will be placed in the `type` attribute of an &lt;input&gt; tag. Head over [here](https://developer.mozilla.org/fr/docs/Web/HTML/Element/Input#attr-type) for a full list of accepted input types. Please not that the `checkbox` and `radio` types aren't currently supported (but will be in the future). There's no support planned for the `submit` and `reset` types.
143
+* **required** (optional) is a boolean. Set it to true and the field will be set as required in your form. A modern browser should prevent an user to send the form if a required field isn't filled. On top of that, SMAM's server will check the field marked as required to make sure they've been filled.
144
+* **options** (optional) is an array of possible values. This is currently useful only if your field is of the "select" type.
145
+
97 146
 ## Templating
98 147
 
99 148
 Each e-mail sent by the form follows a template described in `template.pug` (it's [Pug](pugjs.org/)). If you want to change the way the e-mails you receive are displayed in your mailbox, just edit it! You don't even need to restart the server aftewards :smile:
100 149
 
150
+The template also features custom fields, iterating over the `custom` object, containing the field's label and user-input value:
151
+
152
+```json
153
+"field_name": {
154
+	"label": "My field",
155
+	"value": "Hello"
156
+}
157
+```
158
+
101 159
 ## Personnalising
102 160
 
103 161
 As you might have already seen, the contact form is generated without any form of style except your browser's default one. But that doesn't meen that you have to add an ugly form to your site to receive contact e-mails, as every element has a specific id (beginning with the `form_` prefix), allowing you to use your own style on your contact form.
@@ -107,26 +165,45 @@ The generated form will look like this:
107 165
 ```html
108 166
 <p id="form_status"></p>
109 167
 <div id="form_name">
110
-    <label for="form_name_input">Your name</label>
111
-    <input required="required" placeholder="Your name" id="form_name_input" type="text">
168
+	<label for="form_name_input">Your name</label>
169
+	<input required="required" placeholder="Your name" id="form_name_input" type="text">
112 170
 </div>
113 171
 <div id="form_addr">
114
-    <label for="form_addr_input">Your e-mail address</label>
115
-    <input required="required" placeholder="Your e-mail address" id="form_addr_input" type="email">
172
+	<label for="form_addr_input">Your e-mail address</label>
173
+	<input required="required" placeholder="Your e-mail address" id="form_addr_input" type="email">
116 174
 </div>
117 175
 <div id="form_subj">
118
-    <label for="form_subj_input">Your message's subject</label>
119
-    <input required="required" placeholder="Your message's subject" id="form_subj_input" type="text">
176
+	<label for="form_subj_input">Your message's subject</label>
177
+	<input required="required" placeholder="Your message's subject" id="form_subj_input" type="text">
120 178
 </div>
121 179
 <div id="form_text">
122
-    <label for="form_text_textarea">Your message</label>
123
-    <textarea required="required" placeholder="Your message" id="form_text_textarea"></textarea>
180
+	<label for="form_text_textarea">Your message</label>
181
+	<textarea required="required" placeholder="Your message" id="form_text_textarea"></textarea>
124 182
 </div>
125 183
 <div id="form_subm">
126
-    <button type="submit" id="form_subm_btn">Send the mail</button>
184
+	<button type="submit" id="form_subm_btn">Send the mail</button>
127 185
 </div>
128 186
 ```
129 187
 
188
+Custom fields will be formatted the same way (and with identifiers following the same guidelines) as default fields. For example, a custom field described as
189
+
190
+```json
191
+"budget": {
192
+	"label": "Maximum budget allowed",
193
+	"type": "number",
194
+	"required": true
195
+}
196
+```
197
+in the settings file will result in
198
+```html
199
+<div id="form_budget">
200
+	<label for="form_budget_input">Maximum budget allowed</label>
201
+	<input required="required" placeholder="Maximum budget allowed" id="form_budget_input" type="number">
202
+</div>
203
+```
204
+
205
+Please note that the field's identifier ends with the field's tag name and not its type. For example, our `budget` field above will see its identifier become `form_budget_select` if it has the "select" type, `form_budget_textarea` if it has the "textarea" type, or `form_budget_input` for any other "type" value.
206
+
130 207
 Now it's all yours to make good use of all these identifiers and have a magnificient contact form :wink:
131 208
 
132 209
 I think that the code in itself is clear enough, if not please tell me so I can detail everything here!

+ 319
- 166
front/form.js Dosyayı Görüntüle

@@ -1,69 +1,94 @@
1
+/***************************************************************
2
+*                                                              *
3
+*                     SMAM: Send Me A Mail                     *
4
+*                                                              *
5
+* Made with ♥ by Brendan Abolivier <foss@brendanabolivier.com> *
6
+* Source code available under GPLv3 license here:              *
7
+*                       https://github.com/babolivier/smam/    *
8
+*                                                              *
9
+***************************************************************/
10
+
11
+var prefix = 'form'
12
+
1 13
 var items = {
2
-    name: 'form_name',
3
-    addr: 'form_addr',
4
-    subj: 'form_subj',
5
-    text: 'form_text',
14
+	name: 'name',
15
+	addr: 'addr',
16
+	subj: 'subj',
17
+	text: 'text',
6 18
 };
7 19
 
8
-var server  = getServer();
9
-var token   = "";
10
-var labels  = true;
11
-var lang    = {};
20
+var DOMFields = {};
21
+
22
+var server  		= getServer();
23
+var token   		= "";
24
+var labels  		= true;
25
+var lang			= [];
26
+var customFields 	= {};
12 27
 
13 28
 var xhr = {
14
-    lang: new XMLHttpRequest(),
15
-    token: new XMLHttpRequest(),
16
-    send: new XMLHttpRequest()
29
+	customFields: new XMLHttpRequest(),
30
+	lang: new XMLHttpRequest(),
31
+	token: new XMLHttpRequest(),
32
+	send: new XMLHttpRequest()
17 33
 }
18 34
 
19 35
 // XHR callbacks
20 36
 
37
+xhr.customFields.onreadystatechange = function() {
38
+	if(xhr.customFields.readyState == XMLHttpRequest.DONE) {
39
+		customFields = JSON.parse(xhr.customFields.responseText);
40
+		for(let field in customFields) {
41
+			customFields[field].name = field;
42
+		}
43
+	}
44
+};
45
+
21 46
 xhr.token.onreadystatechange = function() {
22
-    if(xhr.token.readyState == XMLHttpRequest.DONE) {
23
-        token = xhr.token.responseText;
24
-    }
47
+	if(xhr.token.readyState == XMLHttpRequest.DONE) {
48
+		token = xhr.token.responseText;
49
+	}
25 50
 };
26 51
 
27 52
 xhr.lang.onreadystatechange = function() {
28
-    if(xhr.lang.readyState == XMLHttpRequest.DONE) {
29
-        let response = JSON.parse(xhr.lang.responseText);
30
-        lang = response.translations;
31
-        labels = response.labels;
32
-    }
53
+	if(xhr.lang.readyState == XMLHttpRequest.DONE) {
54
+		let response = JSON.parse(xhr.lang.responseText);
55
+		lang = response.translations;
56
+		labels = response.labels;
57
+	}
33 58
 };
34 59
 
35 60
 xhr.send.onreadystatechange = function() {
36
-    if(xhr.send.readyState == XMLHttpRequest.DONE) {
37
-        let status = document.getElementById('form_status');
38
-        status.setAttribute('class', '');
39
-        if(xhr.send.status === 200) {
40
-            cleanForm();
41
-            status.setAttribute('class', 'success');
42
-            status.innerHTML = lang.send_status_success;
43
-        } else {
44
-            status.setAttribute('class', 'failure');
45
-            status.innerHTML = lang.send_status_failure;
46
-        }
47
-    }
61
+	if(xhr.send.readyState == XMLHttpRequest.DONE) {
62
+		let status = document.getElementById('form_status');
63
+		status.setAttribute('class', '');
64
+		if(xhr.send.status === 200) {
65
+			cleanForm();
66
+			status.setAttribute('class', 'success');
67
+			status.innerHTML = lang.send_status_success;
68
+		} else {
69
+			status.setAttribute('class', 'failure');
70
+			status.innerHTML = lang.send_status_failure;
71
+		}
72
+	}
48 73
 };
49 74
 
50 75
 
51 76
 // Returns the server's base URI based on the user's script tag
52 77
 // return: the SMAM server's base URI
53 78
 function getServer() {
54
-    var scripts = document.getElementsByTagName('script');
55
-    // Parsing all the <script> tags to find the URL to our file
56
-    for(var i = 0; i < scripts.length; i++) {
57
-        let script = scripts[i];
58
-        if(script.src) {
59
-            let url = script.src;
60
-            // This should be our script
61
-            if(url.match(/form\.js$/)) {
62
-                // Port has been found
63
-                return url.match(/^(https?:\/\/[^\/]+)/)[1];
64
-            }
65
-        }
66
-    }    
79
+	var scripts = document.getElementsByTagName('script');
80
+	// Parsing all the <script> tags to find the URL to our file
81
+	for(var i = 0; i < scripts.length; i++) {
82
+		let script = scripts[i];
83
+		if(script.src) {
84
+			let url = script.src;
85
+			// This should be our script
86
+			if(url.match(/form\.js$/)) {
87
+				// Port has been found
88
+				return url.match(/^(https?:\/\/[^\/]+)/)[1];
89
+			}
90
+		}
91
+	}
67 92
 }
68 93
 
69 94
 
@@ -71,100 +96,191 @@ function getServer() {
71 96
 // id: HTML identifier of the document's block to create the form into
72 97
 // return: nothing
73 98
 function generateForm(id) {
74
-    // Get translated strings
75
-    getLangSync();
76
-    
77
-    var el = document.getElementById(id);
78
-    
79
-    // Set the form's behaviour
80
-    el.setAttribute('onsubmit', 'sendForm(); return false;');
81
-    
82
-    // Add an empty paragraph for status
83
-    var status = document.createElement('p');
84
-    status.setAttribute('id', 'form_status');
85
-    el.appendChild(status);
86
-    
87
-    var input = {
88
-        name: getField(items.name, lang.form_name_label, false, 'input'),
89
-        addr: getField(items.addr, lang.form_addr_label, true, 'input'),
90
-        subj: getField(items.subj, lang.form_subj_label, false, 'input'),
91
-        text: getField(items.text, lang.form_mesg_label, false, 'textarea')
92
-    };
93
-    
94
-    // Adding nodes to document
95
-    
96
-    el.appendChild(input.name);
97
-    el.appendChild(input.addr);
98
-    el.appendChild(input.subj);
99
-    el.appendChild(input.text);
100
-    
101
-    // Adding submit button
102
-    
103
-    el.appendChild(getSubmitButton('form_subm', lang.form_subm_label));
104
-    
105
-    // Retrieve the token from the server
106
-    
107
-    getToken();
99
+	// Get translated strings
100
+	getLangSync();
101
+	// Get custom fields if defined in the configuration
102
+	getCustomFieldsSync();
103
+
104
+	var el = document.getElementById(id);
105
+
106
+	// Set the form's behaviour
107
+	el.setAttribute('onsubmit', 'sendForm(); return false;');
108
+
109
+	// Add an empty paragraph for status
110
+	var status = document.createElement('p');
111
+	status.setAttribute('id', 'form_status');
112
+	el.appendChild(status);
113
+
114
+	// Default fields
115
+	DOMFields = {
116
+		name: getField({
117
+			name: items.name,
118
+			label: lang.form_name_label,
119
+			type: 'text',
120
+			required: true
121
+		}),
122
+		addr: getField({
123
+			name: items.addr,
124
+			label: lang.form_addr_label,
125
+			type: 'email',
126
+			required: true
127
+		}),
128
+		subj: getField({
129
+			name: items.subj,
130
+			label: lang.form_subj_label,
131
+			type: 'text',
132
+			required: true
133
+		}),
134
+		text: getField({
135
+			name: items.text,
136
+			label: lang.form_mesg_label,
137
+			type: 'textarea',
138
+			required: true
139
+		})
140
+	};
141
+
142
+	// Adding custom fields
143
+	for(let fieldName in customFields) {
144
+		let field = customFields[fieldName];
145
+		DOMFields[fieldName] = getField(field);
146
+	}
147
+
148
+	// Adding all nodes to document
149
+	for(let field in DOMFields) {
150
+		el.appendChild(DOMFields[field]);
151
+	}
152
+
153
+	// Adding submit button
154
+	el.appendChild(getSubmitButton('form_subm', lang.form_subm_label));
155
+
156
+	// Retrieve the token from the server
157
+	getToken();
108 158
 }
109 159
 
110 160
 
111
-// Returns a form field
112
-// id: field HTML identifier
113
-// placeholder: placeholder text
114
-// email: boolean: is it an email field?
115
-// type: 'input' or 'textarea'
116
-// return: a div node containing a label and an input text field
117
-function getField(id, placeholder, email, type) {
118
-    var field = document.createElement('div');
119
-    
120
-    field.setAttribute('id', id); // TODO: configurable prefix
121
-    if(labels) {
122
-        field.appendChild(getLabel(id, placeholder, type));
123
-    }
124
-    field.appendChild(getInputField(id, placeholder, email, type));
125
-    
126
-    return field;
161
+// Get the HTML element for a given field
162
+// fieldInfos: object describing the field
163
+// required: boolean on whether the field is required or optional
164
+// return: a block containing the field and a label describing it (if enabled)
165
+function getField(fieldInfos) {
166
+	var block = document.createElement('div');
167
+	block.setAttribute('id', fieldInfos.name);
168
+
169
+	// Declare the variable first
170
+	let field = {};
171
+
172
+	// Easily add new supported input types
173
+	switch(fieldInfos.type) {
174
+		case 'textarea':	field = getTextarea(fieldInfos);
175
+							break;
176
+		case 'select':		field = getSelectField(fieldInfos);
177
+							break;
178
+		default:			field = getInputField(fieldInfos);
179
+							break;
180
+	}
181
+
182
+	// We need the input field's ID to bind it to the label, so we generate the
183
+	// field first
184
+	if(labels) {
185
+		block.appendChild(getLabel(fieldInfos.label, field.id));
186
+	}
187
+
188
+	// Assemble the block and return it
189
+	block.appendChild(field);
190
+	return block;
127 191
 }
128 192
 
129 193
 
130 194
 // Returns a label
131
-// id: field HTML identifier
132 195
 // content: label's inner content
133
-// type: 'input' or 'textarea'
196
+// id: field HTML identifier
134 197
 // return: a label node the field's description
135
-function getLabel(id, content, type) {
136
-    var label = document.createElement('label');
137
-    
138
-    label.setAttribute('for', id + '_' + type);
139
-    label.innerHTML = content;
140
-    
141
-    return label;
198
+function getLabel(content, id) {
199
+	var label = document.createElement('label');
200
+
201
+	label.setAttribute('for', id);
202
+	label.innerHTML = content;
203
+
204
+	return label;
142 205
 }
143 206
 
144 207
 
145
-// Returns an input text field
146
-// id: field HTML identifier
147
-// placeholder: placeholder text, field description
148
-// email: boolean: is it an email field?
149
-// type: 'input' or 'textarea'
150
-// return: an input text or email field (depending on "email"'s value') with an
151
-//         HTML id and a placeholder text
152
-function getInputField(id, placeholder, email, type) {
153
-    var input = document.createElement(type);
154
-    
155
-    if(!type.localeCompare('input')) { // Set input type if input
156
-        if(email) {
157
-            input.setAttribute('type', 'email');
158
-        } else {
159
-            input.setAttribute('type', 'text');
160
-        }
161
-    }
162
-    
163
-    input.setAttribute('required', 'required');
164
-    input.setAttribute('placeholder', placeholder);
165
-    input.setAttribute('id', id + '_' + type);
166
-    
167
-    return input;
208
+// Returns a <select> HTML element
209
+// fieldInfos: object describing the field
210
+// required: boolean on whether the field is required or optional
211
+// return: a <select> element corresponding to the info passed as input
212
+function getSelectField(fieldInfos) {
213
+	let field = document.createElement('select');
214
+
215
+	// Set attributes when necessary
216
+	if(fieldInfos.required) {
217
+		field.setAttribute('required', 'required');
218
+	}
219
+	field.setAttribute('id', prefix + '_' + fieldInfos.name + '_select');
220
+
221
+	let index = 0;
222
+
223
+	// Add header option, useful if the field is required
224
+	let header = document.createElement('option');
225
+	// The value must be an empty string so the browser can block the submit
226
+	// event if the field is required
227
+	header.setAttribute('value', '');
228
+	header.innerHTML = lang.form_select_header_option;
229
+	field.appendChild(header);
230
+
231
+	// Add all options to select
232
+	for(let choice of fieldInfos.options) {
233
+		let option = document.createElement('option');
234
+		// Options' values are incremental numeric indexes
235
+		option.setAttribute('value', index);
236
+		// Set the value defined by the user
237
+		option.innerHTML = choice;
238
+		field.appendChild(option);
239
+		// Increment the index
240
+		index++;
241
+	}
242
+
243
+	return field
244
+}
245
+
246
+
247
+// Returns a <input> HTML element with desired type
248
+// fieldInfos: object describing the field
249
+// required: boolean on whether the field is required or optional
250
+// type: type of the input field (text, email, date...)
251
+// return: a <input> HTML element corresponding to the info passed as input
252
+function getInputField(fieldInfos, required) {
253
+	let field = getBaseField(fieldInfos, required, 'input')
254
+	field.setAttribute('type', fieldInfos.type);
255
+	return field;
256
+}
257
+
258
+
259
+// Returns a <textarea> HTML element
260
+// fieldInfos: object describing the field
261
+// required: boolean on whether the field is required or optional
262
+// return: a <textarea> element corresponding to the info passed as input
263
+function getTextarea(fieldInfos, required) {
264
+	return getBaseField(fieldInfos, required, 'textarea');
265
+}
266
+
267
+
268
+// Returns a base HTML element with generic info to be processed by functions at 
269
+// higher level
270
+// fieldInfos: object describing the field
271
+// required: boolean on whether the field is required or optional
272
+// tag: the HTML tag the field element must have
273
+// return: a HTML element of the given tag with basic info given as input
274
+function getBaseField(fieldInfos, required, tag) {
275
+	let field = document.createElement(tag);
276
+
277
+	if(fieldInfos.required) {
278
+		field.setAttribute('required', 'required');
279
+	}
280
+	field.setAttribute('placeholder', fieldInfos.label);
281
+	field.setAttribute('id', prefix + '_' + fieldInfos.name + '_' + tag);
282
+
283
+	return field;
168 284
 }
169 285
 
170 286
 
@@ -173,74 +289,111 @@ function getInputField(id, placeholder, email, type) {
173 289
 // text: button text
174 290
 // return: a div node containing the button
175 291
 function getSubmitButton(id, text) {
176
-    var submit = document.createElement('div');
177
-    
178
-    submit.setAttribute('id', id);
179
-    
180
-    var button = document.createElement('button');
181
-    
182
-    button.setAttribute('type', 'submit');
183
-    button.setAttribute('id', id + '_btn');
184
-    
185
-    button.innerHTML = text;
186
-    
187
-    submit.appendChild(button);
188
-    
189
-    return submit;
292
+	var submit = document.createElement('div');
293
+
294
+	submit.setAttribute('id', id);
295
+
296
+	var button = document.createElement('button');
297
+
298
+	button.setAttribute('type', 'submit');
299
+	button.setAttribute('id', id + '_btn');
300
+
301
+	button.innerHTML = text;
302
+
303
+	submit.appendChild(button);
304
+
305
+	return submit;
190 306
 }
191 307
 
192 308
 
193 309
 // Send form data through the XHR object
194 310
 // return: nothing
195 311
 function sendForm() {
196
-    // Clear status
197
-    let status = document.getElementById('form_status');
198
-    status.setAttribute('class', 'sending');
199
-    status.innerHTML = lang.send_status_progress;
200
-    
201
-    xhr.send.open('POST', server + '/send');
202
-    xhr.send.setRequestHeader('Content-Type', 'application/json');
203
-    xhr.send.send(JSON.stringify(getFormData()));
204
-    
205
-    // Get a new token
206
-    getToken();
312
+	// Clear status
313
+	let status = document.getElementById('form_status');
314
+	status.setAttribute('class', 'sending');
315
+	status.innerHTML = lang.send_status_progress;
316
+
317
+	xhr.send.open('POST', server + '/send');
318
+	xhr.send.setRequestHeader('Content-Type', 'application/json');
319
+	xhr.send.send(JSON.stringify(getFormData()));
320
+
321
+	// Get a new token
322
+	getToken();
207 323
 }
208 324
 
209 325
 
210 326
 // Fetch form inputs from HTML elements
211 327
 // return: an object containing all the user's input
212 328
 function getFormData() {
213
-    return {
214
-        name: document.getElementById(items.name + '_input').value,
215
-        addr: document.getElementById(items.addr + '_input').value,
216
-        subj: document.getElementById(items.subj + '_input').value,
217
-        text: document.getElementById(items.text + '_textarea').value,
218
-        token: token
219
-    }
329
+	let data = {};
330
+	data.token = token;
331
+	data.custom = {};
332
+
333
+	// Select the field
334
+	let index = 0;
335
+	if(labels) {
336
+		index = 1;
337
+	}
338
+
339
+	// Iterate over all the fields
340
+	for(let field in DOMFields) {
341
+		let el = DOMFields[field].children[index];
342
+		// Do we need to push this field into default or custom fields?
343
+		if(field in customFields) {
344
+			data.custom[field] = el.value;
345
+		} else {
346
+			data[field] = el.value;
347
+		}
348
+	}
349
+
350
+	return data;
220 351
 }
221 352
 
222 353
 
223 354
 // Empties the form fields
224 355
 // return: nothing
225 356
 function cleanForm() {
226
-    document.getElementById(items.name + '_input').value = '';
227
-    document.getElementById(items.addr + '_input').value = '';
228
-    document.getElementById(items.subj + '_input').value = '';
229
-    document.getElementById(items.text + '_textarea').value = '';
357
+	// Select the field
358
+	let index = 0;
359
+	if(labels) {
360
+		index = 1;
361
+	}
362
+
363
+	// Iterate over all the fields
364
+	for(let field in DOMFields) {
365
+		let el = DOMFields[field].children[index];
366
+		// If it's a <select> element, select the first element so it looks
367
+		// like a reset
368
+		if(!el.tagName.toLowerCase().localeCompare('select')) {
369
+			el.children[0].selected = true;
370
+		} else {
371
+			el.value = '';
372
+		}
373
+	}
230 374
 }
231 375
 
232 376
 
233 377
 // Asks the server for a token
234 378
 // return: nothing
235 379
 function getToken() {
236
-    xhr.token.open('GET', server + '/register');
237
-    xhr.token.send();
380
+	xhr.token.open('GET', server + '/register');
381
+	xhr.token.send();
238 382
 }
239 383
 
240 384
 
241 385
 // Asks the server for translated strings to display
242 386
 // return: notghing
243 387
 function getLangSync() {
244
-    xhr.lang.open('GET', server + '/lang', false);
245
-    xhr.lang.send();
388
+	xhr.lang.open('GET', server + '/lang', false);
389
+	xhr.lang.send();
390
+}
391
+
392
+
393
+// Asks the server for the custom fields if there's one or more set in the
394
+// configuration file
395
+// return: nothing
396
+function getCustomFieldsSync() {
397
+	xhr.customFields.open('GET', server + '/fields', false);
398
+	xhr.customFields.send();
246 399
 }

+ 11
- 11
front/index.html Dosyayı Görüntüle

@@ -1,14 +1,14 @@
1 1
 <!DOCTYPE html>
2 2
 <html>
3
-    <head>
4
-        <meta charset="utf-8">
5
-        <title>Send Me A Mail</title>
6
-        <script src="http://localhost:1970/form.js" charset="utf-8"></script>
7
-    </head>
8
-    <body>
9
-        <form id="smam"></form>
10
-        <script type="text/javascript">
11
-            generateForm('smam');
12
-        </script>
13
-    </body>
3
+	<head>
4
+		<meta charset="utf-8">
5
+		<title>Send Me A Mail</title>
6
+		<script src="http://localhost:1970/form.js" charset="utf-8"></script>
7
+	</head>
8
+	<body>
9
+		<form id="smam"></form>
10
+		<script type="text/javascript">
11
+			generateForm('smam');
12
+		</script>
13
+	</body>
14 14
 </html>

+ 19
- 18
locales/en.json Dosyayı Görüntüle

@@ -1,20 +1,21 @@
1 1
 {
2
-    "client": {
3
-        "form_name_label": "Your name",
4
-        "form_addr_label": "Your e-mail address",
5
-        "form_subj_label": "Your message's subject",
6
-        "form_mesg_label": "Your message",
7
-        "form_subm_label": "Send the mail",
8
-        "send_status_success": "Your message has been sent.",
9
-        "send_status_failure": "An error happened while sending your message, please retry later.",
10
-        "send_status_progress": "Sending the e-mail"
11
-    },
12
-    "server": {
13
-        "log_server_start": "Server started on",
14
-        "log_sending": "Sending message from",
15
-        "log_send_success": "Message sent to",
16
-        "log_send_failure": "Message failed to send to",
17
-        "log_invalid_token": "just tried to send a message with an invalid token",
18
-        "log_cleared_token": "Cleared expired tokens"
19
-    }
2
+	"client": {
3
+		"form_name_label": "Your name",
4
+		"form_addr_label": "Your e-mail address",
5
+		"form_subj_label": "Your message's subject",
6
+		"form_mesg_label": "Your message",
7
+		"form_subm_label": "Send the mail",
8
+		"form_select_header_option": "Select an answer",
9
+		"send_status_success": "Your message has been sent.",
10
+		"send_status_failure": "An error happened while sending your message, please retry later.",
11
+		"send_status_progress": "Sending the e-mail"
12
+	},
13
+	"server": {
14
+		"log_server_start": "Server started on",
15
+		"log_sending": "Sending message from",
16
+		"log_send_success": "Message sent to",
17
+		"log_send_failure": "Message failed to send to",
18
+		"log_invalid_token": "just tried to send a message with an invalid token",
19
+		"log_cleared_token": "Cleared expired tokens"
20
+	}
20 21
 }

+ 19
- 18
locales/fr.json Dosyayı Görüntüle

@@ -1,20 +1,21 @@
1 1
 {
2
-    "client": {
3
-        "form_name_label": "Votre nom",
4
-        "form_addr_label": "Votre adresse e-mail",
5
-        "form_subj_label": "Sujet de votre message",
6
-        "form_mesg_label": "Votre message",
7
-        "form_subm_label": "Envoyer",
8
-        "send_status_success": "Votre message a été envoyé.",
9
-        "send_status_failure": "Une erreur est survenue lors de l'envoi du message, merci de réessayer plus tard.",
10
-        "send_status_progress": "Envoi du message en cours"
11
-    },
12
-    "server": {
13
-        "log_server_start": "Serveur démarré pour",
14
-        "log_sending": "Envoi d'un message de",
15
-        "log_send_success": "Message envoyé à",
16
-        "log_send_failure": "Erreur lors de l'envoi du message à",
17
-        "log_invalid_token": "vient d'essayer d'envoyer un message avec un jeton invalide",
18
-        "log_cleared_token": "Les jetons expirés ont été effacés"
19
-    }
2
+	"client": {
3
+		"form_name_label": "Votre nom",
4
+		"form_addr_label": "Votre adresse e-mail",
5
+		"form_subj_label": "Sujet de votre message",
6
+		"form_mesg_label": "Votre message",
7
+		"form_subm_label": "Envoyer",
8
+		"form_select_header_option": "Sélectionnez une réponse",
9
+		"send_status_success": "Votre message a été envoyé.",
10
+		"send_status_failure": "Une erreur est survenue lors de l'envoi du message, merci de réessayer plus tard.",
11
+		"send_status_progress": "Envoi du message en cours"
12
+	},
13
+	"server": {
14
+		"log_server_start": "Serveur démarré pour",
15
+		"log_sending": "Envoi d'un message de",
16
+		"log_send_success": "Message envoyé à",
17
+		"log_send_failure": "Erreur lors de l'envoi du message à",
18
+		"log_invalid_token": "vient d'essayer d'envoyer un message avec un jeton invalide",
19
+		"log_cleared_token": "Les jetons expirés ont été effacés"
20
+	}
20 21
 }

+ 235
- 166
server.js Dosyayı Görüntüle

@@ -1,23 +1,23 @@
1
-var pug         = require('pug');
1
+var pug			= require('pug');
2 2
 var nodemailer  = require('nodemailer');
3
-var crypto      = require('crypto');
4
-var settings    = require('./settings');
3
+var crypto		= require('crypto');
4
+var settings	= require('./settings');
5 5
 
6 6
 // Translation
7
-var locale  = require('./locales/' + settings.language);
8
-var lang    = locale.server;
7
+var locale		= require('./locales/' + settings.language);
8
+var lang		= locale.server;
9 9
 
10 10
 // Web server
11
-var bodyParser  = require('body-parser');
12
-var cors        = require('cors');
13
-var express     = require('express');
14
-var app = express();
11
+var bodyParser	= require('body-parser');
12
+var cors		= require('cors');
13
+var express		= require('express');
14
+var app			= express();
15 15
 
16 16
 // Logging
17 17
 var printit = require('printit');
18 18
 var log = printit({
19
-    prefix: 'SMAM',
20
-    date: true
19
+	prefix: 'SMAM',
20
+	date: true
21 21
 });
22 22
 
23 23
 
@@ -47,76 +47,80 @@ app.options('*', cors(corsOptions));
47 47
 // A request on /register generates a token and store it, along the user's
48 48
 // address, on the tokens object
49 49
 app.get('/register', function(req, res, next) {
50
-    // Get IP from express
51
-    let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
52
-    if(tokens[ip] === undefined) {
53
-        tokens[ip] = [];
54
-    }
55
-    // Generate token
56
-    crypto.randomBytes(10, (err, buf) => { 
57
-        let token = buf.toString('hex');
58
-        // Store and send the token
59
-        tokens[ip].push({
60
-            token: token,
61
-            // A token expires after 12h
62
-            expire: new Date().getTime() + 12 * 3600 * 1000
63
-        });
64
-        res.status(200).send(token);
65
-    });
50
+	// Get IP from express
51
+	let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
52
+	if(tokens[ip] === undefined) {
53
+		tokens[ip] = [];
54
+	}
55
+	// Generate token
56
+	crypto.randomBytes(10, (err, buf) => { 
57
+		let token = buf.toString('hex');
58
+		// Store and send the token
59
+		tokens[ip].push({
60
+			token: token,
61
+			// A token expires after 12h
62
+			expire: new Date().getTime() + 12 * 3600 * 1000
63
+		});
64
+		res.status(200).send(token);
65
+	});
66 66
 });
67 67
 
68 68
 
69 69
 // A request on /send with user input = mail to be sent
70 70
 app.post('/send', function(req, res, next) {
71
-    // Response will be JSON
72
-    res.header('Access-Control-Allow-Headers', 'Content-Type');
73
-    
74
-    if(!checkBody(req.body)) {
75
-        return res.status(400).send();
76
-    }
77
-    
78
-    let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
79
-    
80
-    if(!checkToken(ip, req.body.token)) {
81
-        return res.status(403).send();
82
-    }
83
-    
84
-    // Count the failures
85
-    let status = {
86
-        failed: 0,
87
-        total: settings.recipients.length
88
-    };
89
-    
90
-    // params will be used as:
91
-    // - values for html generation from the pug template
92
-    // - parameters for sending the mail(s)
93
-    let params = {
94
-        subject: req.body.subj,
95
-        from: req.body.name + '<' + settings.mailserver.auth.user + '>',
96
-        replyTo: req.body.name + ' <' + req.body.addr + '>',
97
-        html: req.body.text
98
-    };
99
-    
100
-    // Replacing the mail's content with HTML from the pug template
101
-    // Commenting the line below will bypass the generation and only user the
102
-    // text entered by the user
103
-    params.html = pug.renderFile('template.pug', params);
104
-    
105
-    log.info(lang.log_sending, params.replyTo);
106
-    
107
-    // Send the email to all users
108
-    sendMails(params, function(err, infos) {
109
-        if(err) {
110
-            log.error(err);
111
-        }
112
-        logStatus(infos);
113
-    }, function() {
114
-        if(status.failed === status.total) {
115
-            res.status(500).send();
116
-        } else {
117
-            res.status(200).send();
118
-        }
119
-    })
71
+	// Response will be JSON
72
+	res.header('Access-Control-Allow-Headers', 'Content-Type');
73
+
74
+	if(!checkBody(req.body)) {
75
+		return res.status(400).send();
76
+	}
77
+
78
+	let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
79
+
80
+	// Token verification
81
+	if(!checkToken(ip, req.body.token)) {
82
+		return res.status(403).send();
83
+	}
84
+
85
+	// Count the failures
86
+	let status = {
87
+		failed: 0,
88
+		total: settings.recipients.length
89
+	};
90
+
91
+	// params will be used as:
92
+	// - values for html generation from the pug template
93
+	// - parameters for sending the mail(s)
94
+	let params = {
95
+		subject: req.body.subj,
96
+		from: req.body.name + '<' + settings.mailserver.auth.user + '>',
97
+		replyTo: req.body.name + ' <' + req.body.addr + '>',
98
+		html: req.body.text
99
+	};
100
+
101
+	// Process custom fields to get data we can use in the HTML generation
102
+	params.custom = processCustom(req.body.custom);
103
+
104
+	// Replacing the mail's content with HTML from the pug template
105
+	// Commenting the line below will bypass the generation and only user the
106
+	// text entered by the user
107
+	params.html = pug.renderFile('template.pug', params);
108
+
109
+	log.info(lang.log_sending, params.replyTo);
110
+
111
+	// Send the email to all users
112
+	sendMails(params, function(err, infos) {
113
+		if(err) {
114
+			log.error(err);
115
+		}
116
+		logStatus(infos);
117
+	}, function() {
118
+		if(status.failed === status.total) {
119
+			res.status(500).send();
120
+		} else {
121
+			res.status(200).send();
122
+		}
123
+	})
120 124
 });
121 125
 
122 126
 
@@ -124,20 +128,33 @@ app.post('/send', function(req, res, next) {
124 128
 // the app settings), alongside the boolean for the display of labels in the
125 129
 // form block.
126 130
 app.get('/lang', function(req, res, next) {
127
-    // Response will be JSON
128
-    res.header('Access-Control-Allow-Headers', 'Content-Type');
129
-
130
-    // Preventing un-updated settings files
131
-    let labels = true;
132
-    if(settings.labels !== undefined) {
133
-        labels = settings.labels;
134
-    }
135
-    
136
-    // Send the infos
137
-    res.status(200).send({
138
-        'labels': labels,
139
-        'translations': locale.client
140
-    });
131
+	// Response will be JSON
132
+	res.header('Access-Control-Allow-Headers', 'Content-Type');
133
+
134
+	// Preventing un-updated settings files
135
+	let labels = true;
136
+	if(settings.labels !== undefined) {
137
+		labels = settings.labels;
138
+	}
139
+
140
+	// Send the infos
141
+	res.status(200).send({
142
+		'labels': labels,
143
+		'translations': locale.client
144
+	});
145
+});
146
+
147
+
148
+// A request on /fields sends data on custom fields.
149
+app.get('/fields', function(req, res, next) {
150
+	// Response will be JSON
151
+	res.header('Access-Control-Allow-Headers', 'Content-Type');
152
+
153
+	// Send an object anyway, its size will determine if we need to display any
154
+	let customFields = settings.customFields || {};
155
+
156
+	// Send custom fields data
157
+	res.status(200).send(customFields);
141 158
 });
142 159
 
143 160
 
@@ -147,7 +164,7 @@ var port = process.env.PORT || 1970;
147 164
 var host = process.env.HOST || '0.0.0.0';
148 165
 // Start the server
149 166
 app.listen(port, host, function() {
150
-    log.info(lang.log_server_start, host + ':' + port);
167
+	log.info(lang.log_server_start, host + ':' + port);
151 168
 });
152 169
 
153 170
 
@@ -158,31 +175,31 @@ var tokensChecks = setTimeout(cleanTokens, 3600 * 1000);
158 175
 // Send mails to the recipients specified in the JSON settings file
159 176
 // content: object containing mail params
160 177
 //  {
161
-//      subject: String
162
-//      from: String (following RFC 1036 (https://tools.ietf.org/html/rfc1036#section-2.1.1))
163
-//      html: String
178
+//	  subject: String
179
+//	  from: String (following RFC 1036 (https://tools.ietf.org/html/rfc1036#section-2.1.1))
180
+//	  html: String
164 181
 //  }
165 182
 // update(next, infos): Called each time a mail is sent with the infos provided
166
-//                      by nodemailer
183
+//					  by nodemailer
167 184
 // done(): Called once each mail has been sent
168 185
 function sendMails(params, update, done) {
169
-    let mails = settings.recipients.map((recipient) => {
170
-        // Promise for each recipient to send each mail asynchronously
171
-        return new Promise((sent) => {
172
-            params.to = recipient;
173
-            // Send the email
174
-            transporter.sendMail(params, (err, infos) => {
175
-                sent();
176
-                if(err) {
177
-                    return update(err, recipient);
178
-                }
179
-                update(null, infos);
180
-                // Promise callback
181
-            });
182
-        });
183
-    });
184
-    // Run all the promises (= send all the mails)
185
-    Promise.all(mails).then(done);
186
+	let mails = settings.recipients.map((recipient) => {
187
+		// Promise for each recipient to send each mail asynchronously
188
+		return new Promise((sent) => {
189
+			params.to = recipient;
190
+			// Send the email
191
+			transporter.sendMail(params, (err, infos) => {
192
+				sent();
193
+				if(err) {
194
+					return update(err, recipient);
195
+				}
196
+				update(null, infos);
197
+				// Promise callback
198
+			});
199
+		});
200
+	});
201
+	// Run all the promises (= send all the mails)
202
+	Promise.all(mails).then(done);
186 203
 }
187 204
 
188 205
 
@@ -190,13 +207,13 @@ function sendMails(params, update, done) {
190 207
 // infos: infos provided by nodemailer
191 208
 // return: nothing
192 209
 function logStatus(infos) {
193
-    if(infos.accepted.length !== 0) {
194
-        log.info(lang.log_send_success, infos.accepted[0]);
195
-    }
196
-    if(infos.rejected.length !== 0) {
197
-        status.failed++;
198
-        log.info(lang.log_send_failure, infos.rejected[0]);
199
-    }
210
+	if(infos.accepted.length !== 0) {
211
+		log.info(lang.log_send_success, infos.accepted[0]);
212
+	}
213
+	if(infos.rejected.length !== 0) {
214
+		status.failed++;
215
+		log.info(lang.log_send_failure, infos.rejected[0]);
216
+	}
200 217
 }
201 218
 
202 219
 
@@ -205,29 +222,29 @@ function logStatus(infos) {
205 222
 // token: token used by the sender
206 223
 // return: true if the user was registered, false else
207 224
 function checkToken(ip, token) {
208
-    let verified = false;
209
-    
210
-    // Check if there's at least one token for this IP
211
-    if(tokens[ip] !== undefined) {
212
-        if(tokens[ip].length !== 0) {
213
-            // There's at least one element for this IP, let's check the tokens
214
-            for(var i = 0; i < tokens[ip].length; i++) {
215
-                if(!tokens[ip][i].token.localeCompare(token)) {
216
-                    // We found the right token
217
-                    verified = true;
218
-                    // Removing the token
219
-                    tokens[ip].pop(tokens[ip][i]);
220
-                    break;
221
-                }
222
-            }
223
-        } 
224
-    }
225
-    
226
-    if(!verified) {
227
-        log.warn(ip, lang.log_invalid_token);
228
-    }
229
-    
230
-    return verified;
225
+	let verified = false;
226
+
227
+	// Check if there's at least one token for this IP
228
+	if(tokens[ip] !== undefined) {
229
+		if(tokens[ip].length !== 0) {
230
+			// There's at least one element for this IP, let's check the tokens
231
+			for(var i = 0; i < tokens[ip].length; i++) {
232
+				if(!tokens[ip][i].token.localeCompare(token)) {
233
+					// We found the right token
234
+					verified = true;
235
+					// Removing the token
236
+					tokens[ip].pop(tokens[ip][i]);
237
+					break;
238
+				}
239
+			}
240
+		} 
241
+	}
242
+
243
+	if(!verified) {
244
+		log.warn(ip, lang.log_invalid_token);
245
+	}
246
+
247
+	return verified;
231 248
 }
232 249
 
233 250
 
@@ -235,34 +252,86 @@ function checkToken(ip, token) {
235 252
 // body: body taken from express's request object
236 253
 // return: true if the body is valid, false else
237 254
 function checkBody(body) {
238
-    let valid = false;
239
-    
240
-    if(body.token !== undefined && body.subj !== undefined 
241
-        && body.name !== undefined && body.addr !== undefined 
242
-        && body.text !== undefined) {
243
-        valid = true;
244
-    }
245
-    
246
-    return valid;
255
+	// Check default fields
256
+	if(isInvalid(body.token) || isInvalid(body.subj) || isInvalid(body.name) 
257
+		|| isInvalid(body.addr) || isInvalid(body.text)) {
258
+		return false;
259
+	}
260
+
261
+	// Checking required custom fields
262
+	for(let field in settings.customFields) {
263
+		// No need to check the field if its not required in the settings
264
+		if(settings.customFields[field].required) {
265
+			if(isInvalid(body.custom[field])) {
266
+				return false;
267
+			}
268
+		}
269
+	}
270
+
271
+	return true;
247 272
 }
248 273
 
249 274
 
275
+// Checks if the field is invalid. A field is considered as invalid if undefined
276
+// or is an empty string
277
+// field: user-input value of the field
278
+// return: true if the field is valid, false if not
279
+function isInvalid(field) {
280
+	return (field === undefined || field.length == 0);
281
+}
282
+
250 283
 // Checks the tokens object to see if no token has expired
251 284
 // return: nothing
252 285
 function cleanTokens() {
253
-    // Get current time for comparison
254
-    let now = new Date().getTime();
255
-    
256
-    for(let ip in tokens) { // Check for each IP in the object
257
-        for(let token of tokens[ip]) { // Check for each token of an IP
258
-            if(token.expire < now) { // Token has expired
259
-                tokens[ip].pop(token);
260
-            }
261
-        }
262
-        if(tokens[ip].length === 0) { // No more element for this IP
263
-            delete tokens[ip];
264
-        }
265
-    }
266
-    
267
-    log.info(lang.log_cleared_token);
286
+	// Get current time for comparison
287
+	let now = new Date().getTime();
288
+
289
+	for(let ip in tokens) { // Check for each IP in the object
290
+		for(let token of tokens[ip]) { // Check for each token of an IP
291
+			if(token.expire < now) { // Token has expired
292
+				tokens[ip].pop(token);
293
+			}
294
+		}
295
+		if(tokens[ip].length === 0) { // No more element for this IP
296
+			delete tokens[ip];
297
+		}
298
+	}
299
+
300
+	log.info(lang.log_cleared_token);
301
+}
302
+
303
+
304
+// Process custom fields to something usable in the HTML generation
305
+// For example, this function replaces indexes with answers in select fields
306
+// custom: object describing data from custom fields
307
+// return: an object with user-input data from each field:
308
+// {
309
+//	  field name: {
310
+//		  value: String,
311
+//		  label: String
312
+//	  }
313
+// }
314
+function processCustom(custom) {
315
+	let fields = {};
316
+
317
+	// Process each field
318
+	for(let field in custom) {
319
+		let type = settings.customFields[field].type;
320
+		// Match indexes with data when needed
321
+		switch(type) {
322
+			case 'select':  custom[field] = settings.customFields[field]
323
+											.options[custom[field]];
324
+							break;
325
+		}
326
+
327
+		// Insert data into the final object if the value is set
328
+		if(!isInvalid(custom[field])) {
329
+			fields[field] = {
330
+				value: custom[field],
331
+				label: settings.customFields[field].label
332
+			}
333
+		}
334
+	}
335
+
336
+	return fields;
268 337
 }

+ 17
- 17
settings.example.json Dosyayı Görüntüle

@@ -1,19 +1,19 @@
1 1
 {
2
-    "mailserver": {
3
-        "pool": true,
4
-        "host": "mail.example.tld",
5
-        "port": 465,
6
-        "secure": true,
7
-        "auth": {
8
-            "user": "noreply@example.tld",
9
-            "pass": "hackme"
10
-        }
11
-    },
12
-    "recipients": [
13
-        "you@example.tld",
14
-        "someone.else@example.com"
15
-    ],
16
-    "formOrigin": "https://example.tld",
17
-    "language": "en",
18
-    "labels": true
2
+	"mailserver": {
3
+		"pool": true,
4
+		"host": "mail.example.tld",
5
+		"port": 465,
6
+		"secure": true,
7
+		"auth": {
8
+			"user": "noreply@example.tld",
9
+			"pass": "hackme"
10
+		}
11
+	},
12
+	"recipients": [
13
+		"you@example.tld",
14
+		"someone.else@example.com"
15
+	],
16
+	"formOrigin": "https://example.tld",
17
+	"language": "en",
18
+	"labels": true
19 19
 }

+ 16
- 0
template.example.pug Dosyayı Görüntüle

@@ -0,0 +1,16 @@
1
+html
2
+	body
3
+		p.subj
4
+			span(style="font-weight:bold") Subject:&nbsp;
5
+			span= subject
6
+		p.from
7
+			span(style="font-weight:bold") Sent from:&nbsp;
8
+			span= replyTo
9
+		each field in custom
10
+			p.custom
11
+				span(style="font-weight:bold")= field.label + ': '
12
+				span= field.value
13
+		p.message
14
+			span(style="font-weight:bold") Message:&nbsp;
15
+		
16
+		p= html

+ 0
- 10
template.pug Dosyayı Görüntüle

@@ -1,10 +0,0 @@
1
-html
2
-    body
3
-        p.subj
4
-            span(style="font-weight:bold") Subject:&nbsp;
5
-            span= subject
6
-        p.from
7
-            span(style="font-weight:bold") Sent from:&nbsp;
8
-            span= replyTo
9
-        
10
-        p= html