Passed
Push — master ( fa41e0...4bd63f )
by Daniel
07:07 queued 12s
created

js/personal.js   C

Complexity

Total Complexity 56
Complexity/F 1.47

Size

Lines of Code 478
Function Count 38

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 56
eloc 207
c 0
b 0
f 0
dl 0
loc 478
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
					dialog.open();
242
				});
243
			});
244
245
			// change website name
246
			var nameEditable = new OCA.CMSPico.Editable(
247
				$website.find('.name > p'),
248
				$website.find('.name-edit > input')
249
			);
250
251
			nameEditable.on('submit.CMSPicoWebsiteList', function (value, defaultValue) {
252
				if (value !== defaultValue) {
253
					that._updateItem(websiteData.id, { name: value });
254
				}
255
			});
256
257
			$website.find('.action-rename').on('click.CMSPicoWebsiteList', function (event) {
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
				var dialogTitle = t('cms_pico', 'Confirm website deletion'),
275
					dialogText = t('cms_pico', 'This operation will delete the website "{name}". However, all of ' +
276
							'its contents will still be available in your Nextcloud.', { name: websiteData.name });
277
278
				OC.dialogs.confirm(dialogText, dialogTitle, function (result) {
279
					if (result) {
280
						that._api('DELETE', '' + websiteData.id);
281
					}
282
				});
283
			});
284
		},
285
286
		/**
287
		 * @private
288
		 *
289
		 * @param {jQuery} $elements
290
		 * @param {string} url
291
		 */
292
		_clickRedirect: function ($elements, url) {
293
			$elements.each(function () {
294
				var $element = $(this);
295
296
				if ($element.is('a')) {
297
					$element.attr('href', url);
298
				} else {
299
					$element.on('click.CMSPicoWebsiteList', function (event) {
300
						OC.redirect(url);
301
					});
302
				}
303
			});
304
		},
305
306
		/**
307
		 * @private
308
		 *
309
		 * @param {number} item
310
		 * @param {Object} data
311
		 */
312
		_updateItem: function (item, data) {
313
			this._api('POST', '' + item, { data: data });
314
		}
315
	});
316
317
	$('.picocms-website-list').each(function () {
318
		var $this = $(this),
319
			websiteList = new OCA.CMSPico.WebsiteList($this);
320
321
		$this.data('CMSPicoWebsiteList', websiteList);
322
		websiteList.reload();
323
	});
324
325
	/**
326
	 * @class
327
	 * @extends OCA.CMSPico.Form
328
	 *
329
	 * @param {jQuery}        $element
330
	 * @param {Object}        [options]
331
	 * @param {string}        [options.route]
332
	 * @param {jQuery|string} [options.errorTemplate]
333
	 */
334
	OCA.CMSPico.WebsiteForm = function ($element, options) {
335
		this.initialize($element, options);
336
	};
337
338
	/**
339
	 * @lends OCA.CMSPico.WebsiteForm.prototype
340
	 */
341
	OCA.CMSPico.WebsiteForm.prototype = $.extend({}, OCA.CMSPico.Form.prototype, {
342
		/** @member {jQuery} */
343
		$errorTemplate: $(),
344
345
		/**
346
		 * @constructs
347
		 *
348
		 * @param {jQuery}        $element
349
		 * @param {Object}        [options]
350
		 * @param {string}        [options.route]
351
		 * @param {jQuery|string} [options.errorTemplate]
352
		 */
353
		initialize: function ($element, options) {
354
			OCA.CMSPico.Form.prototype.initialize.apply(this, arguments);
355
356
			options = $.extend({ errorTemplate: $element.data('errorTemplate') }, options);
357
358
			this.$errorTemplate = $(options.errorTemplate);
359
360
			if (!this.$errorTemplate.length) {
361
				throw 'OCA.CMSPico.WebsiteForm.initialize(): No valid error template given';
362
			}
363
		},
364
365
		/**
366
		 * @public
367
		 */
368
		prepare: function () {
369
			var that = this,
370
				$form = this.$element.find('form'),
371
				$site = $form.find('.input-site'),
372
				$path = $form.find('.input-path');
373
374
			this._inputSite($site);
375
376
			$site.on('input.CMSPicoWebsiteForm', function (event) {
377
				that._inputSite($(this));
378
			});
379
380
			$path.on('click.CMSPicoWebsiteForm', function (event) {
381
				OC.dialogs.filepicker(
382
					t('cms_pico', 'Choose website directory'),
383
					function (path, type) {
384
						$path.val(path + '/' + $site.val());
385
					},
386
					false,
387
					'httpd/unix-directory',
388
					true
389
				);
390
			});
391
392
			$form.on('submit.CMSPicoWebsiteForm', function (event) {
393
				event.preventDefault();
394
				that.submit();
395
			});
396
		},
397
398
		/**
399
		 * @public
400
		 */
401
		submit: function () {
402
			var $form = this.$element.find('form'),
403
				$submitButton = this.$element.find('.form-submit'),
404
				$loadingButton = this.$element.find('.form-submit-loading'),
405
				data = OCA.CMSPico.Util.serialize($form),
406
				that = this;
407
408
			$form.find('fieldset.form-error')
409
				.removeClass('form-error');
410
411
			$submitButton.hide();
412
			$loadingButton.show();
413
414
			$.ajax({
415
				method: 'POST',
416
				url: OC.generateUrl(this.route),
417
				data: { data: data }
418
			}).done(function (data, textStatus, jqXHR) {
419
				that._success(data);
420
			}).fail(function (jqXHR, textStatus, errorThrown) {
421
				that._formError(jqXHR.responseJSON);
422
423
				$submitButton.show();
424
				$loadingButton.hide();
425
			});
426
		},
427
428
		/**
429
		 * @private
430
		 *
431
		 * @param {jQuery} $site
432
		 */
433
		_inputSite: function ($site) {
434
			var $form = this.$element.find('form'),
435
				$address = $form.find('.input-address'),
436
				$path = $form.find('.input-path'),
437
				value = this._val($site).replace(/[\/\\]/g, '');
438
439
			this._val($site, value);
440
			this._val($address, OC.dirname(this._val($address)) + '/' + value);
441
			this._val($path, OC.dirname(this._val($path)) + '/' + value);
442
		},
443
444
		/**
445
		 * @private
446
		 *
447
		 * @param {Object} data
448
		 */
449
		_success: function (data) {
450
			OC.reload();
451
		},
452
453
		/**
454
		 * @private
455
		 *
456
		 * @param {Object}  [responseData]
457
		 * @param {string}  [responseData.error]
458
		 * @param {string}  [responseData.errorField]
459
		 * @param {int}     [responseData.status]
460
		 * @param {string}  [responseData.exception]
461
		 * @param {?string} [responseData.exceptionMessage]
462
		 * @param {?int}    [responseData.exceptionCode]
463
		 */
464
		_formError: function (responseData) {
465
			responseData = responseData || {};
466
467
			if (responseData.error && responseData.errorField) {
468
				var $inputErrorContainer = this.$element.find('.input-' + responseData.errorField + '-error');
469
470
				if ($inputErrorContainer.length) {
471
					var $inputError = $('<p></p>').text(responseData.error);
472
473
					$inputErrorContainer.empty();
474
					$inputErrorContainer.append($inputError);
475
					$inputErrorContainer.closest('fieldset').addClass('form-error');
476
					return;
477
				}
478
			}
479
480
			var $formErrorContainer = this.$element.find('.input-unknown-error'),
481
				$formError = this.$errorTemplate.octemplate(responseData);
482
483
			if (responseData.error) {
484
				$formError.filter('.error-details').show();
485
			}
486
487
			if (responseData.exception) {
488
				$formError.filter('.exception-details').show();
489
			}
490
491
			$formErrorContainer.empty();
492
			$formErrorContainer.append($formError);
493
			$formErrorContainer.closest('fieldset').addClass('form-error');
494
		}
495
	});
496
497
	$('.picocms-website-form').each(function () {
498
		var $this = $(this),
499
			websiteForm = new OCA.CMSPico.WebsiteForm($this);
500
501
		$this.data('CMSPicoWebsiteForm', websiteForm);
502
		websiteForm.prepare();
503
	});
504
})(document, jQuery, OC, OCA);
505