Passed
Pull Request — master (#77)
by Daniel
61:17
created

js/personal.js   B

Complexity

Total Complexity 48
Complexity/F 1.41

Size

Lines of Code 399
Function Count 34

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 48
eloc 175
c 0
b 0
f 0
dl 0
loc 399
rs 8.5599
mnd 14
bc 14
fnc 34
bpm 0.4117
cpm 1.4117
noi 0

How to fix   Complexity   

Complexity

Complex classes like js/personal.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/**
2
 * CMS Pico - Create websites using Pico CMS for Nextcloud.
3
 *
4
 * @copyright Copyright (c) 2017, Maxence Lange (<[email protected]>)
5
 * @copyright Copyright (c) 2019, Daniel Rudolf (<[email protected]>)
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
/** global: OC */
24
/** global: OCA */
25
/** global: jQuery */
26
27
(function (document, $, OC, OCA) {
28
	'use strict';
29
30
	/** @constant {number} */
31
	var WEBSITE_TYPE_PUBLIC = 1;
32
33
	/** @constant {number} */
34
	var WEBSITE_TYPE_PRIVATE = 2;
35
36
	/**
37
	 * @class
38
	 * @extends OCA.CMSPico.List
39
	 *
40
	 * @param {jQuery}        $element
41
	 * @param {Object}        [options]
42
	 * @param {string}        [options.route]
43
	 * @param {jQuery|string} [options.template]
44
	 * @param {jQuery|string} [options.itemTemplate]
45
	 * @param {jQuery|string} [options.loadingTemplate]
46
	 * @param {jQuery|string} [options.errorTemplate]
47
	 * @param {string}        [options.websiteBaseUrl]
48
	 */
49
	OCA.CMSPico.WebsiteList = function ($element, options) {
50
		this.initialize($element, options);
51
	};
52
53
	/**
54
	 * @lends OCA.CMSPico.WebsiteList.prototype
55
	 */
56
	OCA.CMSPico.WebsiteList.prototype = $.extend({}, OCA.CMSPico.List.prototype, {
57
		/** @member {Object[]} */
58
		websites: [],
59
60
		/** @member {jQuery} */
61
		$itemTemplate: $(),
62
63
		/** @member {string} */
64
		websiteBaseUrl: '',
65
66
		/**
67
		 * @constructs
68
		 *
69
		 * @param {jQuery}        $element
70
		 * @param {Object}        [options]
71
		 * @param {string}        [options.route]
72
		 * @param {jQuery|string} [options.template]
73
		 * @param {jQuery|string} [options.itemTemplate]
74
		 * @param {jQuery|string} [options.loadingTemplate]
75
		 * @param {jQuery|string} [options.errorTemplate]
76
		 * @param {string}        [options.websiteBaseUrl]
77
		 */
78
		initialize: function ($element, options) {
79
			OCA.CMSPico.List.prototype.initialize.apply(this, arguments);
80
81
			options = $.extend({
82
				itemTemplate: $element.data('itemTemplate'),
83
				websiteBaseUrl: $element.data('websiteBaseUrl')
84
			}, options);
85
86
			this.$itemTemplate = $(options.itemTemplate);
87
			this.websiteBaseUrl = options.websiteBaseUrl + ((options.websiteBaseUrl.substr(-1) !== '/') ? '/' : '');
88
89
			var signature = 'OCA.CMSPico.WebsiteList.initialize()';
90
			if (!this.$itemTemplate.length) throw signature + ': No valid item template given';
91
			if (this.websiteBaseUrl === '/') throw signature + ': No valid website base URL given';
92
93
			this._init();
94
		},
95
96
		/**
97
		 * @private
98
		 */
99
		_init: function () {
100
			var that = this;
101
			$(document).on('click.CMSPicoWebsiteList', function (event) {
102
				var $target = $(event.target),
103
					$menu;
104
105
				if ($target.is('.icon-more')) {
106
					$menu = $target.nextAll('.popovermenu');
107
					if ($menu.length) {
108
						$menu.toggleClass('open');
109
					}
110
				} else {
111
					// if clicked inside the menu, don't close it
112
					$menu = $target.closest('.popovermenu');
113
				}
114
115
				that.$element.find('.popovermenu.open').not($menu).removeClass('open');
116
			});
117
		},
118
119
		/**
120
		 * @public
121
		 *
122
		 * @param {Object}   data
123
		 * @param {Object[]} data.websites
124
		 * @param {int}      data.websites[].id
125
		 * @param {string}   data.websites[].user_id
126
		 * @param {string}   data.websites[].name
127
		 * @param {string}   data.websites[].site
128
		 * @param {string}   data.websites[].theme
129
		 * @param {int}      data.websites[].type
130
		 * @param {Object}   data.websites[].options
131
		 * @param {string}   data.websites[].path
132
		 * @param {int}      data.websites[].creation
133
		 */
134
		update: function (data) {
135
			this.websites = data.websites;
136
137
			this._content(this.$template);
138
139
			for (var i = 0, $website; i < data.websites.length; i++) {
140
				$website = this._content(this.$itemTemplate, data.websites[i]);
141
				this._setupItem($website, data.websites[i]);
142
			}
143
144
			this._setup();
145
		},
146
147
		/**
148
		 * @protected
149
		 */
150
		_setup: function () {
151
			this.$element.find('.live-relative-timestamp').each(function() {
152
				var $this = $(this),
153
					time = parseInt($this.data('timestamp'), 10) * 1000;
154
155
				$this
156
					.attr('data-timestamp', time)
157
					.text(OC.Util.relativeModifiedDate(time))
158
					.addClass('has-tooltip')
159
					.prop('title', OC.Util.formatDate(time))
160
					.tooltip();
161
			});
162
		},
163
164
		/**
165
		 * @protected
166
		 *
167
		 * @param {jQuery} $website
168
		 * @param {Object} websiteData
169
		 * @param {int}    websiteData.id
170
		 * @param {string} websiteData.user_id
171
		 * @param {string} websiteData.name
172
		 * @param {string} websiteData.site
173
		 * @param {string} websiteData.theme
174
		 * @param {int}    websiteData.type
175
		 * @param {Object} websiteData.options
176
		 * @param {string} websiteData.path
177
		 * @param {int}    websiteData.creation
178
		 */
179
		_setupItem: function ($website, websiteData) {
180
			var that = this;
181
182
			// go to website
183
			var websiteUrl = this.websiteBaseUrl + websiteData.site;
184
			this._clickRedirect($website.find('.action-open'), websiteUrl);
185
186
			// go to website directory
187
			var filesUrl = OC.generateUrl('/apps/files/') + '?dir=' + OC.encodePath(websiteData.path);
188
			this._clickRedirect($website.find('.action-files'), filesUrl);
189
190
			// toggle private website
191
			$website.find('.action-private').each(function () {
192
				var $this = $(this),
193
					$icon = $this.find('[class^="icon-"], [class*=" icon-"]'),
194
					value = (websiteData.type === WEBSITE_TYPE_PRIVATE);
195
196
				$icon
197
					.addClass(value ? 'icon-lock' : 'icon-lock-open')
198
					.removeClass(value ? 'icon-lock-open' : 'icon-lock');
199
200
				$this.data('value', value);
201
202
				$this.on('click.CMSPicoWebsiteList', function (event) {
203
					event.preventDefault();
204
205
					var websiteType = $(this).data('value') ? WEBSITE_TYPE_PUBLIC : WEBSITE_TYPE_PRIVATE;
206
					that._updateItem(websiteData.id, { type: websiteType });
207
				});
208
			});
209
210
			// change website theme
211
			$website.find('.action-theme').each(function () {
212
				var $this = $(this);
213
214
				$this.val(websiteData.theme);
215
216
				$this.on('change.CMSPicoWebsiteList', function (event) {
217
					event.preventDefault();
218
					that._updateItem(websiteData.id, { theme: $(this).val() });
219
				});
220
			});
221
222
			// delete website
223
			$website.find('.action-delete').on('click.CMSPicoWebsiteList', function (event) {
224
				event.preventDefault();
225
226
				var dialogTitle = t('cms_pico', 'Confirm website deletion'),
227
					dialogText = t('cms_pico', 'This operation will delete the website "{name}". However, all of ' +
228
							'its contents will still be available in your Nextcloud.', { name: websiteData.name });
229
230
				OC.dialogs.confirm(dialogText, dialogTitle, function (result) {
231
					if (result) {
232
						that._api('DELETE', '' + websiteData.id);
233
					}
234
				});
235
			});
236
		},
237
238
		/**
239
		 * @private
240
		 *
241
		 * @param {jQuery} $elements
242
		 * @param {string} url
243
		 */
244
		_clickRedirect: function ($elements, url) {
245
			$elements.each(function () {
246
				var $element = $(this);
247
248
				if ($element.is('a')) {
249
					$element.attr('href', url);
250
				} else {
251
					$element.on('click.CMSPicoWebsiteList', function (event) {
252
						event.preventDefault();
253
						OC.redirect(url);
254
					});
255
				}
256
			});
257
		},
258
259
		/**
260
		 * @private
261
		 *
262
		 * @param {number} item
263
		 * @param {Object} data
264
		 */
265
		_updateItem: function (item, data) {
266
			this._api('POST', '' + item, { data: data });
267
		}
268
	});
269
270
	$('.picocms-website-list').each(function () {
271
		var $this = $(this),
272
			websiteList = new OCA.CMSPico.WebsiteList($this);
273
274
		$this.data('CMSPicoWebsiteList', websiteList);
275
		websiteList.reload();
276
	});
277
278
	/**
279
	 * @class
280
	 * @extends OCA.CMSPico.Form
281
	 *
282
	 * @param {jQuery} $element
283
	 * @param {Object} [options]
284
	 * @param {string} [options.route]
285
	 */
286
	OCA.CMSPico.WebsiteForm = function ($element, options) {
287
		this.initialize($element, options);
288
	};
289
290
	/**
291
	 * @lends OCA.CMSPico.WebsiteForm.prototype
292
	 */
293
	OCA.CMSPico.WebsiteForm.prototype = $.extend({}, OCA.CMSPico.Form.prototype, {
294
		/**
295
		 * @public
296
		 */
297
		prepare: function () {
298
			var that = this,
299
				$form = this.$element.find('form'),
300
				$site = $form.find('.input-site'),
301
				$path = $form.find('.input-path');
302
303
			this._inputSite($site);
304
305
			$site.on('input.CMSPicoWebsiteForm', function (event) {
306
				that._inputSite($(this));
307
			});
308
309
			$path.on('click.CMSPicoWebsiteForm', function (event) {
310
				event.preventDefault();
311
312
				OC.dialogs.filepicker(
313
					t('cms_pico', 'Choose website directory'),
314
					function (path, type) {
315
						$path.val(path + '/' + $site.val());
316
					},
317
					false,
318
					'httpd/unix-directory',
319
					true
320
				);
321
			});
322
323
			$form.on('submit.CMSPicoWebsiteForm', function (event) {
324
				event.preventDefault();
325
				that.submit();
326
			});
327
		},
328
329
		/**
330
		 * @public
331
		 */
332
		submit: function () {
333
			var $form = this.$element.find('form'),
334
				$submitButton = this.$element.find('.form-submit'),
335
				$loadingButton = this.$element.find('.form-submit-loading'),
336
				that = this,
337
				data = {};
338
339
			$form.find(':input').each(function () {
340
				var name = this.name,
341
					value = $(this).val();
342
343
				if (name) {
344
					data[name] = value;
345
				}
346
			});
347
348
			$form.find('fieldset.form-error')
349
				.removeClass('form-error');
350
351
			$submitButton.hide();
352
			$loadingButton.show();
353
354
			$.ajax({
355
				method: 'POST',
356
				url: OC.generateUrl(this.route),
357
				data: { data: data }
358
			}).done(function (data, textStatus, jqXHR) {
359
				that._success(data);
360
			}).fail(function (jqXHR, textStatus, errorThrown) {
361
				that._formError((jqXHR.responseJSON || {}).error);
362
363
				$submitButton.show();
364
				$loadingButton.hide();
365
			});
366
		},
367
368
		/**
369
		 * @private
370
		 *
371
		 * @param {jQuery} $site
372
		 */
373
		_inputSite: function ($site) {
374
			var $form = this.$element.find('form'),
375
				$address = $form.find('.input-address'),
376
				$path = $form.find('.input-path'),
377
				value = this._val($site).replace(/[\/\\]/g, '');
378
379
			this._val($site, value);
380
			this._val($address, OC.dirname(this._val($address)) + '/' + value);
381
			this._val($path, OC.dirname(this._val($path)) + '/' + value);
382
		},
383
384
		/**
385
		 * @private
386
		 *
387
		 * @param {Object} data
388
		 */
389
		_success: function (data) {
390
			OC.reload();
391
		},
392
393
		/**
394
		 * @private
395
		 *
396
		 * @param {Object} errorData
397
		 */
398
		_formError: function (errorData) {
399
			if (errorData) {
400
				var $errorInput = this.$element.find('.input-' + errorData.field + '-error');
401
402
				if ($errorInput.length) {
403
					$errorInput.closest('fieldset').addClass('form-error');
404
					$errorInput.text(errorData.message);
405
					return;
406
				}
407
			}
408
409
			var $formError = this.$element.find('.input-unknown-error'),
410
				message = t('cms_pico', 'A unexpected error occured while performing this action. ' +
411
						'Please check Nextcloud\'s logs.');
412
413
			$formError.closest('fieldset').addClass('form-error');
414
			$formError.text(message);
415
		}
416
	});
417
418
	$('.picocms-website-form').each(function () {
419
		var $this = $(this),
420
			websiteForm = new OCA.CMSPico.WebsiteForm($this);
421
422
		$this.data('CMSPicoWebsiteForm', websiteForm);
423
		websiteForm.prepare();
424
	});
425
})(document, jQuery, OC, OCA);
426