Test Setup Failed
Pull Request — master (#99)
by Daniel
50:13 queued 21:02
created

js/personal.js   C

Complexity

Total Complexity 56
Complexity/F 1.47

Size

Lines of Code 483
Function Count 38

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 56
eloc 212
c 0
b 0
f 0
dl 0
loc 483
rs 5.5199
mnd 18
bc 18
fnc 38
bpm 0.4736
cpm 1.4736
noi 1

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;
0 ignored issues
show
Unused Code introduced by
The variable WEBSITE_TYPE_PUBLIC seems to be never used. Consider removing it.
Loading history...
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.privateSettingsTemplate]
46
	 * @param {jQuery|string} [options.loadingTemplate]
47
	 * @param {jQuery|string} [options.errorTemplate]
48
	 * @param {string}        [options.websiteBaseUrl]
49
	 */
50
	OCA.CMSPico.WebsiteList = function ($element, options) {
51
		this.initialize($element, options);
52
	};
53
54
	/**
55
	 * @lends OCA.CMSPico.WebsiteList.prototype
56
	 */
57
	OCA.CMSPico.WebsiteList.prototype = $.extend({}, OCA.CMSPico.List.prototype, {
58
		/** @member {Object[]} */
59
		websites: [],
60
61
		/** @member {jQuery} */
62
		$itemTemplate: $(),
63
64
		/** @member {jQuery} */
65
		$privateSettingsTemplate: $(),
66
67
		/** @member {string} */
68
		websiteBaseUrl: '',
69
70
		/**
71
		 * @constructs
72
		 *
73
		 * @param {jQuery}        $element
74
		 * @param {Object}        [options]
75
		 * @param {string}        [options.route]
76
		 * @param {jQuery|string} [options.template]
77
		 * @param {jQuery|string} [options.itemTemplate]
78
		 * @param {jQuery|string} [options.privateSettingsTemplate]
79
		 * @param {jQuery|string} [options.loadingTemplate]
80
		 * @param {jQuery|string} [options.errorTemplate]
81
		 * @param {string}        [options.websiteBaseUrl]
82
		 */
83
		initialize: function ($element, options) {
84
			OCA.CMSPico.List.prototype.initialize.apply(this, arguments);
85
86
			options = $.extend({
87
				itemTemplate: $element.data('itemTemplate'),
88
				privateSettingsTemplate: $element.data('privateSettingsTemplate'),
89
				websiteBaseUrl: $element.data('websiteBaseUrl')
90
			}, options);
91
92
			this.$itemTemplate = $(options.itemTemplate);
93
			this.$privateSettingsTemplate = $(options.privateSettingsTemplate);
94
			this.websiteBaseUrl = options.websiteBaseUrl + ((options.websiteBaseUrl.substr(-1) !== '/') ? '/' : '');
95
96
			var signature = 'OCA.CMSPico.WebsiteList.initialize()';
97
			if (!this.$itemTemplate.length) {
98
				throw signature + ': No valid item template given';
99
			}
100
			if (!this.$privateSettingsTemplate.length) {
101
				throw signature + ': No valid private settings template given';
102
			}
103
			if (this.websiteBaseUrl === '/') {
104
				throw signature + ': No valid website base URL given';
105
			}
106
107
			this._init();
108
		},
109
110
		/**
111
		 * @private
112
		 */
113
		_init: function () {
114
			var that = this;
115
			$(document).on('click.CMSPicoWebsiteList', function (event) {
116
				var $target = $(event.target),
117
					$menu;
118
119
				if ($target.is('.icon-more')) {
120
					$menu = $target.nextAll('.popovermenu');
121
					if ($menu.length) {
122
						$menu.toggleClass('open');
123
					}
124
				} else {
125
					// if clicked inside the menu, don't close it
126
					$menu = $target.closest('.popovermenu');
127
				}
128
129
				that.$element.find('.popovermenu.open').not($menu).removeClass('open');
130
			});
131
		},
132
133
		/**
134
		 * @public
135
		 *
136
		 * @param {Object}   data
137
		 * @param {Object[]} data.websites
138
		 * @param {int}      data.websites[].id
139
		 * @param {string}   data.websites[].user_id
140
		 * @param {string}   data.websites[].name
141
		 * @param {string}   data.websites[].site
142
		 * @param {string}   data.websites[].theme
143
		 * @param {int}      data.websites[].type
144
		 * @param {Object}   data.websites[].options
145
		 * @param {string}   data.websites[].path
146
		 * @param {int}      data.websites[].creation
147
		 */
148
		update: function (data) {
149
			this.websites = data.websites;
150
151
			this._content(this.$template);
152
153
			for (var i = 0, $website; i < data.websites.length; i++) {
154
				$website = this._content(this.$itemTemplate, data.websites[i]);
155
				this._setupItem($website, data.websites[i]);
156
			}
157
158
			this._setup();
159
		},
160
161
		/**
162
		 * @protected
163
		 */
164
		_setup: function () {
165
			this.$element.find('.live-relative-timestamp').each(function() {
166
				var $this = $(this),
167
					time = parseInt($this.data('timestamp'), 10) * 1000;
168
169
				$this
170
					.attr('data-timestamp', time)
171
					.text(OC.Util.relativeModifiedDate(time))
172
					.addClass('has-tooltip')
173
					.attr('title', OC.Util.formatDate(time))
174
					.tooltip();
175
			});
176
		},
177
178
		/**
179
		 * @protected
180
		 *
181
		 * @param {jQuery}   $website
182
		 * @param {Object}   websiteData
183
		 * @param {int}      websiteData.id
184
		 * @param {string}   websiteData.user_id
185
		 * @param {string}   websiteData.name
186
		 * @param {string}   websiteData.site
187
		 * @param {string}   websiteData.theme
188
		 * @param {int}      websiteData.type
189
		 * @param {Object}   websiteData.options
190
		 * @param {string[]} [websiteData.options.group_access]
191
		 * @param {string}   websiteData.path
192
		 * @param {int}      websiteData.creation
193
		 */
194
		_setupItem: function ($website, websiteData) {
195
			var that = this;
196
197
			// go to website
198
			var websiteUrl = this.websiteBaseUrl + websiteData.site;
199
			this._clickRedirect($website.find('.action-open'), websiteUrl);
200
201
			// go to website directory
202
			var filesUrl = OC.generateUrl('/apps/files/') + '?dir=' + OC.encodePath(websiteData.path);
203
			this._clickRedirect($website.find('.action-files'), filesUrl);
204
205
			// edit private websites settings
206
			var websitePrivate = (websiteData.type === WEBSITE_TYPE_PRIVATE),
207
				websiteGroupAccess = (websiteData.options || {}).group_access || [];
208
			$website.find('.action-private').each(function () {
209
				var $this = $(this),
210
					$icon = $this.find('[class^="icon-"], [class*=" icon-"]');
211
212
				$icon
213
					.addClass(websitePrivate ? 'icon-lock' : 'icon-lock-open')
214
					.removeClass(websitePrivate ? 'icon-lock-open' : 'icon-lock');
215
216
				var dialog = new OCA.CMSPico.Dialog(that.$privateSettingsTemplate, {
217
					title: $this.data('originalTitle') || $this.prop('title') || $this.text(),
218
					buttons: [
219
						{ type: OCA.CMSPico.Dialog.BUTTON_ABORT },
220
						{ type: OCA.CMSPico.Dialog.BUTTON_SUBMIT }
221
					]
222
				});
223
224
				dialog.on('open.CMSPicoWebsiteList', function () {
225
					var $inputType = this.$element.find('.input-private-' + (!websitePrivate ? 'public' : 'private')),
226
						$inputGroups = this.$element.find('.input-private-groups');
227
228
					$inputType.prop('checked', true);
229
					$inputGroups.val(websiteGroupAccess.join('|'));
230
					OC.Settings.setupGroupsSelect($inputGroups);
231
				});
232
233
				dialog.on('submit.CMSPicoWebsiteList', function () {
234
					var data = OCA.CMSPico.Util.serialize(this.$element);
235
					that._updateItem(websiteData.id, data);
236
				});
237
238
				$this.on('click.CMSPicoWebsiteList', function (event) {
239
					event.preventDefault();
240
					dialog.open();
241
				});
242
			});
243
244
			// change website name
245
			var nameEditable = new OCA.CMSPico.Editable(
246
				$website.find('.name > p'),
247
				$website.find('.name-edit > input')
248
			);
249
250
			nameEditable.on('submit.CMSPicoWebsiteList', function (value, defaultValue) {
251
				if (value !== defaultValue) {
252
					that._updateItem(websiteData.id, { name: value });
253
				}
254
			});
255
256
			$website.find('.action-rename').on('click.CMSPicoWebsiteList', function (event) {
257
				event.preventDefault();
258
				nameEditable.toggle();
259
			});
260
261
			// change website theme
262
			$website.find('.action-theme').each(function () {
263
				var $this = $(this);
264
265
				$this.val(websiteData.theme);
266
267
				$this.on('change.CMSPicoWebsiteList', function (event) {
268
					that._updateItem(websiteData.id, { theme: $(this).val() });
269
				});
270
			});
271
272
			// delete website
273
			$website.find('.action-delete').on('click.CMSPicoWebsiteList', function (event) {
274
				event.preventDefault();
275
276
				var dialogTitle = t('cms_pico', 'Confirm website deletion'),
277
					dialogText = t('cms_pico', 'This operation will delete the website "{name}". However, all of ' +
278
							'its contents will still be available in your Nextcloud.', { name: websiteData.name });
279
280
				OC.dialogs.confirm(dialogText, dialogTitle, function (result) {
281
					if (result) {
282
						that._api('DELETE', '' + websiteData.id);
283
					}
284
				});
285
			});
286
		},
287
288
		/**
289
		 * @private
290
		 *
291
		 * @param {jQuery} $elements
292
		 * @param {string} url
293
		 */
294
		_clickRedirect: function ($elements, url) {
295
			$elements.each(function () {
296
				var $element = $(this);
297
298
				if ($element.is('a')) {
299
					$element.attr('href', url);
300
				} else {
301
					$element.on('click.CMSPicoWebsiteList', function (event) {
302
						event.preventDefault();
303
						OC.redirect(url);
304
					});
305
				}
306
			});
307
		},
308
309
		/**
310
		 * @private
311
		 *
312
		 * @param {number} item
313
		 * @param {Object} data
314
		 */
315
		_updateItem: function (item, data) {
316
			this._api('POST', '' + item, { data: data });
317
		}
318
	});
319
320
	$('.picocms-website-list').each(function () {
321
		var $this = $(this),
322
			websiteList = new OCA.CMSPico.WebsiteList($this);
323
324
		$this.data('CMSPicoWebsiteList', websiteList);
325
		websiteList.reload();
326
	});
327
328
	/**
329
	 * @class
330
	 * @extends OCA.CMSPico.Form
331
	 *
332
	 * @param {jQuery}        $element
333
	 * @param {Object}        [options]
334
	 * @param {string}        [options.route]
335
	 * @param {jQuery|string} [options.errorTemplate]
336
	 */
337
	OCA.CMSPico.WebsiteForm = function ($element, options) {
338
		this.initialize($element, options);
339
	};
340
341
	/**
342
	 * @lends OCA.CMSPico.WebsiteForm.prototype
343
	 */
344
	OCA.CMSPico.WebsiteForm.prototype = $.extend({}, OCA.CMSPico.Form.prototype, {
345
		/** @member {jQuery} */
346
		$errorTemplate: $(),
347
348
		/**
349
		 * @constructs
350
		 *
351
		 * @param {jQuery}        $element
352
		 * @param {Object}        [options]
353
		 * @param {string}        [options.route]
354
		 * @param {jQuery|string} [options.errorTemplate]
355
		 */
356
		initialize: function ($element, options) {
357
			OCA.CMSPico.Form.prototype.initialize.apply(this, arguments);
358
359
			options = $.extend({ errorTemplate: $element.data('errorTemplate') }, options);
360
361
			this.$errorTemplate = $(options.errorTemplate);
362
363
			if (!this.$errorTemplate.length) {
364
				throw 'OCA.CMSPico.WebsiteForm.initialize(): No valid error template given';
365
			}
366
		},
367
368
		/**
369
		 * @public
370
		 */
371
		prepare: function () {
372
			var that = this,
373
				$form = this.$element.find('form'),
374
				$site = $form.find('.input-site'),
375
				$path = $form.find('.input-path');
376
377
			this._inputSite($site);
378
379
			$site.on('input.CMSPicoWebsiteForm', function (event) {
380
				that._inputSite($(this));
381
			});
382
383
			$path.on('click.CMSPicoWebsiteForm', function (event) {
384
				event.preventDefault();
385
386
				OC.dialogs.filepicker(
387
					t('cms_pico', 'Choose website directory'),
388
					function (path, type) {
389
						$path.val(path + '/' + $site.val());
390
					},
391
					false,
392
					'httpd/unix-directory',
393
					true
394
				);
395
			});
396
397
			$form.on('submit.CMSPicoWebsiteForm', function (event) {
398
				event.preventDefault();
399
				that.submit();
400
			});
401
		},
402
403
		/**
404
		 * @public
405
		 */
406
		submit: function () {
407
			var $form = this.$element.find('form'),
408
				$submitButton = this.$element.find('.form-submit'),
409
				$loadingButton = this.$element.find('.form-submit-loading'),
410
				data = OCA.CMSPico.Util.serialize($form),
411
				that = this;
412
413
			$form.find('fieldset.form-error')
414
				.removeClass('form-error');
415
416
			$submitButton.hide();
417
			$loadingButton.show();
418
419
			$.ajax({
420
				method: 'POST',
421
				url: OC.generateUrl(this.route),
422
				data: { data: data }
423
			}).done(function (data, textStatus, jqXHR) {
424
				that._success(data);
425
			}).fail(function (jqXHR, textStatus, errorThrown) {
426
				that._formError(jqXHR.responseJSON);
427
428
				$submitButton.show();
429
				$loadingButton.hide();
430
			});
431
		},
432
433
		/**
434
		 * @private
435
		 *
436
		 * @param {jQuery} $site
437
		 */
438
		_inputSite: function ($site) {
439
			var $form = this.$element.find('form'),
440
				$address = $form.find('.input-address'),
441
				$path = $form.find('.input-path'),
442
				value = this._val($site).replace(/[\/\\]/g, '');
443
444
			this._val($site, value);
445
			this._val($address, OC.dirname(this._val($address)) + '/' + value);
446
			this._val($path, OC.dirname(this._val($path)) + '/' + value);
447
		},
448
449
		/**
450
		 * @private
451
		 *
452
		 * @param {Object} data
453
		 */
454
		_success: function (data) {
455
			OC.reload();
456
		},
457
458
		/**
459
		 * @private
460
		 *
461
		 * @param {Object}  [responseData]
462
		 * @param {string}  [responseData.error]
463
		 * @param {string}  [responseData.errorField]
464
		 * @param {int}     [responseData.status]
465
		 * @param {string}  [responseData.exception]
466
		 * @param {?string} [responseData.exceptionMessage]
467
		 * @param {?int}    [responseData.exceptionCode]
468
		 */
469
		_formError: function (responseData) {
470
			responseData = responseData || {};
471
472
			if (responseData.error && responseData.errorField) {
473
				var $inputErrorContainer = this.$element.find('.input-' + responseData.errorField + '-error');
474
475
				if ($inputErrorContainer.length) {
476
					var $inputError = $('<p></p>').text(responseData.error);
477
478
					$inputErrorContainer.empty();
479
					$inputErrorContainer.append($inputError);
480
					$inputErrorContainer.closest('fieldset').addClass('form-error');
481
					return;
482
				}
483
			}
484
485
			var $formErrorContainer = this.$element.find('.input-unknown-error'),
486
				$formError = this.$errorTemplate.octemplate(responseData);
487
488
			if (responseData.error) {
489
				$formError.filter('.error-details').show();
490
			}
491
492
			if (responseData.exception) {
493
				$formError.filter('.exception-details').show();
494
			}
495
496
			$formErrorContainer.empty();
497
			$formErrorContainer.append($formError);
498
			$formErrorContainer.closest('fieldset').addClass('form-error');
499
		}
500
	});
501
502
	$('.picocms-website-form').each(function () {
503
		var $this = $(this),
504
			websiteForm = new OCA.CMSPico.WebsiteForm($this);
505
506
		$this.data('CMSPicoWebsiteForm', websiteForm);
507
		websiteForm.prepare();
508
	});
509
})(document, jQuery, OC, OCA);
510