Passed
Pull Request — master (#99)
by Daniel
08:11
created

js/personal.js   C

Complexity

Total Complexity 56
Complexity/F 1.47

Size

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