Passed
Pull Request — master (#77)
by Daniel
24:04
created

js/personal.js   B

Complexity

Total Complexity 48
Complexity/F 1.41

Size

Lines of Code 394
Function Count 34

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 48
eloc 173
c 0
b 0
f 0
dl 0
loc 394
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 {jQuery} */
58
		$itemTemplate: $(),
59
60
		/** @member {string} */
61
		websiteBaseUrl: '',
62
63
		/**
64
		 * @constructs
65
		 *
66
		 * @param {jQuery}        $element
67
		 * @param {Object}        [options]
68
		 * @param {string}        [options.route]
69
		 * @param {jQuery|string} [options.template]
70
		 * @param {jQuery|string} [options.itemTemplate]
71
		 * @param {jQuery|string} [options.loadingTemplate]
72
		 * @param {jQuery|string} [options.errorTemplate]
73
		 * @param {string}        [options.websiteBaseUrl]
74
		 */
75
		initialize: function ($element, options) {
76
			OCA.CMSPico.List.prototype.initialize.apply(this, arguments);
77
78
			options = $.extend({
79
				itemTemplate: $element.data('itemTemplate'),
80
				websiteBaseUrl: $element.data('websiteBaseUrl')
81
			}, options);
82
83
			this.$itemTemplate = $(options.itemTemplate);
84
			this.websiteBaseUrl = options.websiteBaseUrl + ((options.websiteBaseUrl.substr(-1) !== '/') ? '/' : '');
85
86
			var signature = 'OCA.CMSPico.WebsiteList.initialize()';
87
			if (!this.$itemTemplate.length) throw signature + ': No valid item template given';
88
			if (this.websiteBaseUrl === '/') throw signature + ': No valid website base URL given';
89
90
			this._init();
91
		},
92
93
		/**
94
		 * @private
95
		 */
96
		_init: function () {
97
			var that = this;
98
			$(document).on('click.CMSPicoWebsiteList', function (event) {
99
				var $target = $(event.target),
100
					$menu;
101
102
				if ($target.is('.icon-more')) {
103
					$menu = $target.nextAll('.popovermenu');
104
					if ($menu.length) {
105
						$menu.toggleClass('open');
106
					}
107
				} else {
108
					// if clicked inside the menu, don't close it
109
					$menu = $target.closest('.popovermenu');
110
				}
111
112
				that.$element.find('.popovermenu.open').not($menu).removeClass('open');
113
			});
114
		},
115
116
		/**
117
		 * @public
118
		 *
119
		 * @param {Object}   data
120
		 * @param {Object[]} data.websites
121
		 * @param {int}      data.websites[].id
122
		 * @param {string}   data.websites[].user_id
123
		 * @param {string}   data.websites[].name
124
		 * @param {string}   data.websites[].site
125
		 * @param {string}   data.websites[].theme
126
		 * @param {int}      data.websites[].type
127
		 * @param {Object}   data.websites[].options
128
		 * @param {string}   data.websites[].path
129
		 * @param {int}      data.websites[].creation
130
		 */
131
		update: function (data) {
132
			this._content(this.$template);
133
134
			for (var i = 0, $website; i < data.websites.length; i++) {
135
				$website = this._content(this.$itemTemplate, data.websites[i]);
136
				this._setupItem($website, data.websites[i]);
137
			}
138
139
			this._setup();
140
		},
141
142
		/**
143
		 * @protected
144
		 */
145
		_setup: function () {
146
			this.$element.find('.live-relative-timestamp').each(function() {
147
				var $this = $(this),
148
					time = parseInt($this.data('timestamp'), 10) * 1000;
149
150
				$this
151
					.attr('data-timestamp', time)
152
					.text(OC.Util.relativeModifiedDate(time))
153
					.addClass('has-tooltip')
154
					.prop('title', OC.Util.formatDate(time))
155
					.tooltip();
156
			});
157
		},
158
159
		/**
160
		 * @protected
161
		 *
162
		 * @param {jQuery} $website
163
		 * @param {Object} websiteData
164
		 * @param {int}    websiteData.id
165
		 * @param {string} websiteData.user_id
166
		 * @param {string} websiteData.name
167
		 * @param {string} websiteData.site
168
		 * @param {string} websiteData.theme
169
		 * @param {int}    websiteData.type
170
		 * @param {Object} websiteData.options
171
		 * @param {string} websiteData.path
172
		 * @param {int}    websiteData.creation
173
		 */
174
		_setupItem: function ($website, websiteData) {
175
			var that = this;
176
177
			// open website
178
			var websiteUrl = this.websiteBaseUrl + websiteData.site;
179
			this._clickRedirect($website.find('.action-open'), websiteUrl);
180
181
			// open website directory
182
			var filesUrl = OC.generateUrl('/apps/files/') + '?dir=' + OC.encodePath(websiteData.path);
183
			this._clickRedirect($website.find('.action-files'), filesUrl);
184
185
			// toggle private website
186
			$website.find('.action-private').each(function () {
187
				var $this = $(this),
188
					$icon = $this.find('[class^="icon-"], [class*=" icon-"]'),
189
					value = (websiteData.type === WEBSITE_TYPE_PRIVATE);
190
191
				$icon
192
					.addClass(value ? 'icon-lock' : 'icon-lock-open')
193
					.removeClass(value ? 'icon-lock-open' : 'icon-lock');
194
195
				$this.data('value', value);
196
197
				$this.on('click.CMSPicoWebsiteList', function (event) {
198
					event.preventDefault();
199
200
					var websiteType = $(this).data('value') ? WEBSITE_TYPE_PUBLIC : WEBSITE_TYPE_PRIVATE;
201
					that._updateItem(websiteData.id, { type: websiteType });
202
				});
203
			});
204
205
			// change website theme
206
			$website.find('.action-theme').each(function () {
207
				var $this = $(this);
208
209
				$this.val(websiteData.theme);
210
211
				$this.on('change.CMSPicoWebsiteList', function (event) {
212
					event.preventDefault();
213
					that._updateItem(websiteData.id, { theme: $(this).val() });
214
				});
215
			});
216
217
			// delete website
218
			$website.find('.action-delete').on('click.CMSPicoWebsiteList', function (event) {
219
				event.preventDefault();
220
221
				var dialogTitle = t('cms_pico', 'Confirm website deletion'),
222
					dialogText = t('cms_pico', 'This operation will delete the website "{name}". However, all of ' +
223
							'its contents will still be available in your Nextcloud.', { name: websiteData.name });
224
225
				OC.dialogs.confirm(dialogText, dialogTitle, function (result) {
226
					if (result) {
227
						that._api('DELETE', '' + websiteData.id);
228
					}
229
				});
230
			});
231
		},
232
233
		/**
234
		 * @private
235
		 *
236
		 * @param {jQuery} $elements
237
		 * @param {string} url
238
		 */
239
		_clickRedirect: function ($elements, url) {
240
			$elements.each(function () {
241
				var $element = $(this);
242
243
				if ($element.is('a')) {
244
					$element.attr('href', url);
245
				} else {
246
					$element.on('click.CMSPicoWebsiteList', function (event) {
247
						event.preventDefault();
248
						OC.redirect(url);
249
					});
250
				}
251
			});
252
		},
253
254
		/**
255
		 * @private
256
		 *
257
		 * @param {number} item
258
		 * @param {Object} data
259
		 */
260
		_updateItem: function (item, data) {
261
			this._api('POST', '' + item, { data: data });
262
		}
263
	});
264
265
	$('.picocms-website-list').each(function () {
266
		var $this = $(this),
267
			websiteList = new OCA.CMSPico.WebsiteList($this);
268
269
		$this.data('CMSPicoWebsiteList', websiteList);
270
		websiteList.reload();
271
	});
272
273
	/**
274
	 * @class
275
	 * @extends OCA.CMSPico.Form
276
	 *
277
	 * @param {jQuery} $element
278
	 * @param {Object} [options]
279
	 * @param {string} [options.route]
280
	 */
281
	OCA.CMSPico.WebsiteForm = function ($element, options) {
282
		this.initialize($element, options);
283
	};
284
285
	/**
286
	 * @lends OCA.CMSPico.WebsiteForm.prototype
287
	 */
288
	OCA.CMSPico.WebsiteForm.prototype = $.extend({}, OCA.CMSPico.Form.prototype, {
289
		/**
290
		 * @public
291
		 */
292
		prepare: function () {
293
			var that = this,
294
				$form = this.$element.find('form'),
295
				$site = $form.find('.input-site'),
296
				$path = $form.find('.input-path');
297
298
			this._inputSite($site);
299
300
			$site.on('input.CMSPicoWebsiteForm', function (event) {
301
				that._inputSite($(this));
302
			});
303
304
			$path.on('click.CMSPicoWebsiteForm', function (event) {
305
				event.preventDefault();
306
307
				OC.dialogs.filepicker(
308
					t('cms_pico', 'Choose website directory'),
309
					function (path, type) {
310
						$path.val(path + '/' + $site.val());
311
					},
312
					false,
313
					'httpd/unix-directory',
314
					true
315
				);
316
			});
317
318
			$form.on('submit.CMSPicoWebsiteForm', function (event) {
319
				event.preventDefault();
320
				that.submit();
321
			});
322
		},
323
324
		/**
325
		 * @public
326
		 */
327
		submit: function () {
328
			var $form = this.$element.find('form'),
329
				$submitButton = this.$element.find('.form-submit'),
330
				$loadingButton = this.$element.find('.form-submit-loading'),
331
				that = this,
332
				data = {};
333
334
			$form.find(':input').each(function () {
335
				var name = this.name,
336
					value = $(this).val();
337
338
				if (name) {
339
					data[name] = value;
340
				}
341
			});
342
343
			$form.find('fieldset.form-error')
344
				.removeClass('form-error');
345
346
			$submitButton.hide();
347
			$loadingButton.show();
348
349
			$.ajax({
350
				method: 'POST',
351
				url: OC.generateUrl(this.route),
352
				data: { data: data }
353
			}).done(function (data, textStatus, jqXHR) {
354
				that._success(data);
355
			}).fail(function (jqXHR, textStatus, errorThrown) {
356
				that._error(jqXHR.responseJSON || {});
357
358
				$submitButton.show();
359
				$loadingButton.hide();
360
			});
361
		},
362
363
		/**
364
		 * @private
365
		 *
366
		 * @param {jQuery} $site
367
		 */
368
		_inputSite: function ($site) {
369
			var $form = this.$element.find('form'),
370
				$address = $form.find('.input-address'),
371
				$path = $form.find('.input-path'),
372
				value = this._val($site).replace(/[\/\\]/g, '');
373
374
			this._val($site, value);
375
			this._val($address, OC.dirname(this._val($address)) + '/' + value);
376
			this._val($path, OC.dirname(this._val($path)) + '/' + value);
377
		},
378
379
		/**
380
		 * @private
381
		 *
382
		 * @param {Object} data
383
		 */
384
		_success: function (data) {
385
			OC.reload();
386
		},
387
388
		/**
389
		 * @private
390
		 *
391
		 * @param {Object} data
392
		 */
393
		_error: function (data) {
394
			if (data.form_error) {
395
				var $errorInput = this.$element.find('.input-' + data.form_error.field + '-error');
396
397
				if ($errorInput.length) {
398
					$errorInput.closest('fieldset').addClass('form-error');
399
					$errorInput.text(data.form_error.message);
400
					return;
401
				}
402
			}
403
404
			var $formError = this.$element.find('.input-unknown-error'),
405
				message = t('cms_pico', 'A unexpected error occured while performing this action. ' +
406
						'Please check Nextcloud\'s logs.');
407
408
			$formError.closest('fieldset').addClass('form-error');
409
			$formError.text(message);
410
		}
411
	});
412
413
	$('.picocms-website-form').each(function () {
414
		var $this = $(this),
415
			websiteForm = new OCA.CMSPico.WebsiteForm($this);
416
417
		$this.data('CMSPicoWebsiteForm', websiteForm);
418
		websiteForm.prepare();
419
	});
420
})(document, jQuery, OC, OCA);
421