<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">/*
	Created by Michael Schuijff &lt;michael@reglobe.nl&gt;
	Copyright Lost Images, The Netherlands
	
	For more information, visit www.michaelschuijff.nl
*/

router.register('/profile', () =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}
	
	if (config.user.username) {
		router.redirect('/username/' + config.user.username);
	} else {
		router.redirect('/user/' + config.user.id);
	}
});

router.register(['/username', '/user'], () =&gt; {
	router.redirect('/');
});

router.register(['/username/$username', '/user/$id'], (values) =&gt; {
	let currentUser;
	
	if (config.user) {
		if (config.user.id == values.id) {
			currentUser = true;
		}
		
		if (config.user.username == values.username) {
			currentUser = true;
		}
	}
	
	if (!currentUser &amp;&amp; values.username) {
		let user = getUserByName(values.username);
		
		if (user) {
			values.id = user.id;
		}
	}
	
	let menuState = null;
	
	if (currentUser) {
		menuState = 'profile';
	}

	let title;
	
	if (currentUser) {
		title = getUserName(config.user);
	} else {
		title = getUserName(values.id) || __('Profile');
	}
	
	let buttons = ['close'];
	
	if (currentUser) {
		buttons[1] = 'config';
	} else if (config.user) {
		buttons[1] = 'menu';
	}
	
	let view = createView({
		menuState: menuState, titleBar: title, hideMainTitleBar: true, buttons: buttons, spinner: true, pullToRefresh: true
	});
	
	let outer = createElement('div', 'feature-follow');
	
	let timeline = createElement('div', 'timeline');
	
	view.content.append(outer, timeline);
	
	let reqUser, user, lastPost;
	
	function fetchUser () {
		let data = {};
		
		if (currentUser) {
			Object.assign(data, { user_id: config.user.id });
		} else {
			Object.assign(data, { user_id: values.id, username: values.username });
		}
		
		reqUser = api.get({
			url: '/posts/profile',
			data: data,
			success: (result) =&gt; {
				user = getUser(result.user);
				
				if (user.id) {
					view.addURL('/user/' + user.id);
				}
				
				if (user.username) {
					let url = '/username/' + user.username;
					
					if (!config.bot) {
						let state = history.state || {};
						
						if (window.cordova) {
							history.replaceState(Object.assign(state, { url: url }), '');
						} else {
							history.replaceState(state, '', url);
						}
					
						view.addURL('/username/' + user.username);
					}
					
					router.canonical(url);
				}
								
				view.setTitle(getUserName(user) || __('Profile'));
				
				let details = renderProfile(user);
				
				if (outer.firstChild) {
					outer.firstChild.replaceWith(details);
				} else {
					outer.append(details);
				}
				
				timeline.innerHTML = '';
				
				if (result.posts) {
					let posts = result.posts;
					
					for (let post of posts) {
						timeline.append(renderPost(post));
					}
					
					if (posts.length) {
						lastPost = posts[posts.length - 1].id;
					} else {
						lastPost = null;
					}
				}
				
				if (!lastPost) {
					timeline.append(createElement('div', 'no-content', __('This user hasn\'t posted anything for you yet.')));
				}
				
				if (outer.nextSibling) {
					outer.nextSibling.replaceWith(timeline);
				} else {
					view.content.append(timeline);
				}
				
				view.hideSpinner();
				
				view.videoElements();
				
				if (result.more) {
					view.enableInfiniteScrolling();
					
					if (view.content.offsetHeight &lt; window.innerHeight) {
						setTimeout(fetchPosts, 0);
					}
				} else {
					view.disableInfiniteScrolling();
				}
			},
			error: () =&gt; {
				toast(__('User not found.'));
				
				if (config.bot) {
					router.go('/404');
				} else {
					router.back();
				}
				
				view.destroy();
			},
			complete: () =&gt; {
				reqUser = null;
				view.revertPullToRefresh();
			}
		});
	}
		
	function renderProfile (user) {
		user = getUser(user);
		
		let render = createElement('div', 'profile user-' + user.id);
		
		if (user.verified) {
			render.className += ' verified';
		}
		
		if (user.following == 'yes') {
			render.className += ' following';
		}
		
		if (user.following == 'pending') {
			render.className += ' pending';
		}
		
		if (user.subscribed) {
			render.className += ' subscribed';
		}

		let avatar = createElement('span', 'avatar');
		
		preload(user.avatar, (url) =&gt; avatar.style.backgroundImage = 'url(' + url + ')');
		
		avatar.onclick = () =&gt; {
			let backdrop = createElement('div', 'backdrop');
			
			backdrop.onclick = () =&gt; {
				router.register('back', null);
				backdrop.remove();
			}
			
			let avatar = createElement('div', 'avatar');
			
			preload(user.avatar, (url) =&gt; avatar.style.backgroundImage = 'url(' + url + ')');
			
			backdrop.append(avatar);
			
			document.body.append(backdrop);
			
			router.register('back', () =&gt; backdrop.remove());
		}
		
		render.append(avatar);
		
		let summary = createElement('div', 'summary');
		
		if (config.user &amp;&amp; config.user.id != user.id) {
			summary.append(createElement('button', 'button-subscribe'), createElement('button', 'button-unsubscribe'));
		}

		summary.append(createElement('div', 'name', getUserName(user), createElement('span', 'verified-badge')));
		
		if (user.username) {
			summary.append(createElement('div', 'username uppercase', '@' + user.username));
		}
				
		let following = createElement('a', 'following');
		following.href = '/user/' + user.id + '/following';
		
		if (user.following_count == 1) {
			following.innerHTML = '&lt;strong&gt;1&lt;/strong&gt; ' + _e('following', false);
		} else {
			following.innerHTML = '&lt;strong&gt;' + formatNumber(user.following_count, '0') + '&lt;/strong&gt; ' + _e('following', true);
		}
		
		summary.append(following);
		
		let followers = createElement('a', 'followers');
		followers.href = '/user/' + user.id + '/followers';
		
		if (user.follower_count == 1) {
			followers.innerHTML = '&lt;strong&gt;1&lt;/strong&gt; ' + _e('follower', false);
		} else {
			followers.innerHTML = '&lt;strong&gt;' + formatNumber(user.follower_count, '0') + '&lt;/strong&gt; ' + _e('followers', true);
		}
		
		summary.append(followers);
		
		render.append(summary);

		let details = createElement('div', 'details');
				
		if (user.badge) {
			if (user.badge.badge) {
				details.append(createElement('div', 'badge uppercase', __(user.badge.badge)));
			}
		} else if (user.category) {
			details.append(createElement('div', 'category uppercase', __(user.category.label)));
		}
		
		if (user.private) {
			details.append(createElement('div', 'private uppercase', __('This profile is private.')));
		}

		if (user.biography) {
			let biography = createElement('div', 'biography');
			biography.innerHTML = user.biography;
			
			details.append(biography);
		}
		
		if (user.address) {
			let link = createElement('a', null, user.address);
			Object.assign(link, { href: 'https://www.google.com/maps?' + encodeURIData({ q: user.address }), target: '_blank' });
			
			details.append(createElement('div', 'address', link));
		}
		
		if (user.website) {
			let link = createElement('a', null, user.website.match(/\/\/([^?#]+)/)[1]);
			link.href = user.website;

			details.append(createElement('div', 'website', link));
		}
		
		if (user.ambassadors) {
			let ambassadors = createElement('div', 'ambassadors', __('Followed by'));
			ambassadors.innerHTML += ' ' + user.ambassadors;
			
			details.append(ambassadors);
		}
		
		if (user.badge &amp;&amp; user.badge.goal) {
			let goal = createElement('div', 'badge-goal');

			goal.append(createElement('span', 'label uppercase', __('Next: %s', __(user.badge.goal.badge))));
			
			goal.append(createElement('span', 'current', user.badge.goal.current + '/' + user.badge.goal.goal));
			
			let percentage = createElement('div', 'percentage'), inner = createElement('span', 'inner');
			Object.assign(inner.style, { width: (user.badge.goal.current / user.badge.goal.goal * 100) + '%' });
			
			percentage.append(inner);
			
			goal.append(percentage);
			
			details.append(goal);
		}
		
		if (config.user &amp;&amp; config.user.id != user.id) {
			let buttons = createElement('div', 'buttons');
			
			buttons.append(createElement('button', 'button button-message', __('Send message')));
			
			buttons.append(createElement('button', 'button button-follow', __('Follow')));
			
			buttons.append(createElement('button', 'button button-pending', __('Cancel follow')));
			
			buttons.append(createElement('button', 'button button-unfollow', __('Unfollow')));
			
			details.append(buttons);
		}
		
		if (details.firstChild) {
			render.append(details);
		}

		return render;
	}

	let reqPosts;
		
	function fetchPosts () {
		let data = { until_id: lastPost };
		
		if (currentUser) {
			Object.assign(data, { user_id: config.user.id });
		} else {
			Object.assign(data, { user_id: values.id, username: values.username });
		}

		reqPosts = api.get({
			url: '/posts/profile',
			data: data,
			success: (result) =&gt; {
				if (result.posts) {
					let posts = result.posts;
					
					for (let post of posts) {
						timeline.append(renderPost(post));
					}
					
					if (posts.length) {
						lastPost = posts[posts.length - 1].id;	
					}
				}

				if (result.more) {
					view.enableInfiniteScrolling();
					
					if (view == window.activeView &amp;&amp; view.content.offsetHeight &lt; window.innerHeight) {
						setTimeout(fetchPosts, 0);
					}
				} else {
					view.disableInfiniteScrolling();
				}
			},
			complete: () =&gt; {
				reqPosts = null;
				view.revertPullToRefresh();
			}
		});
	}
	
	view.register('load', () =&gt; {
		let user;
		
		if (currentUser) {
			user = getUser(config.user.id);
		} else {
			user = getUser(values.id);
		}
		
		if (user &amp;&amp; 'following' in user) {
			outer.append(renderProfile(user));
		}
		
		fetchUser();
	});
	
	view.register('refresh', () =&gt; {
		if (reqUser) {
			reqUser.abort();
		}
		
		if (reqPosts) {
			reqPosts.abort();
		}
		
		reqPosts = null, lastPost = null;
		
		view.spinner();
		fetchUser();
	});	
	
	view.register('pause', () =&gt; {
		if (reqPosts) {
			reqPosts.abort();
		}
		
		reqPosts = null;
	});
	
	view.register('resume', () =&gt; {
		refreshPostsCount(timeline);
	});
	
	view.register('infinite-scroll', () =&gt; {
		reqPosts || fetchPosts();
	});

	view.register('config', () =&gt; {
		contextMenu(view.buttons.config, [
			{
				text: __('Edit profile'),
				href: '/profile/edit'
			}, {
				text: __('Invite your friends'),
				hidden: !navigator.share,
				click: () =&gt; {
					let url;
					
					if (user.username) {
						url = config.url + '/username/' + user.username;
					} else {
						url = config.url + '/user/' + user.id;
					}
					
					navigator.share({ text: __('Join me on Train Siding!'), url: url });
					
					analytics.push('share', { item_id: url });
				}
			}, {
				text: __('Switch profile'),
				href: '/profile/switch'
			}, {
				text: __('Settings'),
				href: '/settings'
			}, {
				text: __('Log off'),
				href: '/logoff'
			}
		]);
	});
	
	view.register('menu', () =&gt; {
		contextMenu(view.buttons.menu, [
			{
				text: __('Block user'),
				click: () =&gt; {
					api.confirm(__('Are you sure you want to block %s?', getUserName(values.id)), () =&gt; {
						api.block(values.id);
						
						if (reqUser) {
							reqUser.abort();
						}
						
						if (reqPosts) {
							reqPosts.abort();
						}
						
						router.back();
						view.destroy();
					});
				}
			}
		]);
	});
	
	return view;
});

window.addEventListener('popstate', () =&gt; {
	let element = document.querySelector('.backdrop &gt; .avatar');
	
	if (element) {
		element.parentNode.remove();
	}
});

router.register('/profile/edit', (values) =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}

	let view = createView({
		titleBar: __('Edit profile'), hideMainTitleBar: true, hideBottomMenu: true, buttons: ['close', 'confirm']
	});
	
	let user = config.user;
	
	let avatar;
	
	let form = createElement('form');
	form.autocomplete = 'off';
	
	let outer = createElement('div', 'edit-avatar');
	
	let inner = createElement('div', 'avatar');
	
	preload(user.avatar, (url) =&gt; inner.style.backgroundImage = 'url(' + url + ')');
	
	outer.append(inner);

	{
		let button = createElement('button', 'button-gallery');
		
		button.onclick = () =&gt; {
			gallery((file) =&gt; {
				let options = { crop: true, maxWidth: 600, maxHeight: 600, canvas: true, orientation: true };
				
				loadImage(file, (canvas) =&gt; {
					if (!canvas || canvas.type == 'error') {
						toast(__('Could not open image.'));
						return;
					}
					
					canvas.toBlob((blob) =&gt; avatar = blob, 'image/jpeg');
					
					inner.style.backgroundImage = 'url(' + canvas.toDataURL('image/jpeg') + ')';
				}, options);
			}, false);
		}
		
		outer.append(button);
	}
	
	{
		let button = createElement('button', 'button-clear');
		
		button.onclick = () =&gt; {
			if ((!avatar &amp;&amp; ~user.avatar.indexOf('default.png')) || avatar == 'delete') {
				return;
			}
			
			api.confirm(__('Are you sure you want to delete your avatar?'), () =&gt; {
				inner.style.backgroundImage = null;
				avatar = null;
			});
		}
		
		outer.append(button);
	}
		
	form.append(outer);

	{
		let label = createElement('label', null, createElement('span', null, __('Username')));
		
		let input = createElement('input');
		Object.assign(input, { type: 'text', name: 'username', maxLength: 50, spellcheck: false });
		
		input.onblur = () =&gt; {
			input.value = input.value.trim();

			if (input.nextSibling) {
				input.nextSibling.remove();
			}
			
			let username = input.value;

			if (username[0] == '@') {
				username = username.substr(1);
			}
			
			if (!username) {
				label.append(createElement('span', 'error', __('username is required')));
				return;
			}
			
			if (!isValidUsername(username)) {
				label.append(createElement('span', 'error', __('this username is invalid')));
				return;
			}
				
			api.post({
				url: '/validate',
				data: { username: username },
				error: (error) =&gt; {
					if (!input.nextSibling) {
						label.append(createElement('span', 'error', error));
					}
				}
			});
		}
		
		input.value = user.username || '';
		
		label.append(input);
		
		form.append(label);
	}
	
	{
		let label = createElement('label', null, createElement('span', null, __('Your name')));
		
		let input = createElement('input');
		Object.assign(input, { type: 'text', name: 'name', maxLength: 100, spellcheck: false });
		
		input.onblur = () =&gt; {
			input.value = input.value.trim();

			if (input.nextSibling) {
				input.nextSibling.remove();
			}
			
			let name = input.value;
			
			if (!name) {
				label.append(createElement('span', 'error', __('a name is required')));
			}
		}
		
		input.value = user.name || '';
		
		label.append(input);
		
		form.append(label);
	}
	
	{
		let label = createElement('label', null, createElement('span', null, __('Type of profile')));
		
		let input = createElement('select');
		input.name = 'category';
		
		for (let category of config.categories) {
			let option = createElement('option', null, category.label);
			option.value = category.id;
			
			if (category.id == user.category.id) {
				option.selected = true;
			}
			
			input.append(option);
		}
		
		input.onchange = () =&gt; {
			let label = form.querySelector('[name=address]').parentNode;
			
			let value = +input.value;
			
			for (let category of config.categories) {
				if (category.id == value) {
					label.classList.toggle('hidden', !category.address);
					break;
				}
			}
		}
		
		label.append(input);
		
		form.append(label);
	}

	{
		let label = createElement('label', null, createElement('span', null, __('Email address')));
		
		let input = createElement('input');
		Object.assign(input, { type: 'text', name: 'email', maxLength: 200, spellcheck: false });
		
		input.onblur = () =&gt; {
			input.value = input.value.trim();

			if (input.nextSibling) {
				input.nextSibling.remove();
			}
			
			let email = input.value;
			
			if (!email) {
				if (user.email) {
					label.append(createElement('span', 'error', __('an email address is required')));
				}
				
				return;
			}
			
			if (!isValidEmail(email)) {
				label.append(createElement('span', 'error', __('invalid email address')));
				return;
			}
			
			api.post({
				url: '/validate',
				data: { email: email },
				error: (error) =&gt; {
					if (!input.nextSibling) {
						label.append(createElement('span', 'error', error));
					}
				}
			});
		}
		
		if (user.email &amp;&amp; !~user.email.toLowerCase().indexOf('@privaterelay.appleid.com')) {
			input.value = user.email;
		}
		
		label.append(input);
		
		form.append(label);
	}

	{
		let label = createElement('label', null, createElement('span', null, __('Biography')));
		
		let input = createElement('textarea', 'autofill');
		Object.assign(input, { name: 'biography', maxLength: 255, spellcheck: false });
		
		input.value = decodeHTML(user.biography || '')
		
		input.onblur = () =&gt; {
			let biography = input.value.trim();
			
			biography = biography.replace(/\r\n/g, "\n");
			biography = biography.split(/[\r\n]/);
			
			for (let i = 0, length = biography.length; i &lt; length; i ++) {
				biography[i] = biography[i].trim().replace(/\s+/g, ' ');
			}
			
			biography = biography.join("\n").replace(/\n{3,}/g, "\n\n");
			
			input.value = biography;
		}
		
		label.append(input);

		form.append(label);
	}

	let latitude = user.latitude || null, longitude = user.longitude || null;
	
	{
		let label = createElement('label', null, createElement('span', null, __('Visiting address')));
		
		for (let category of config.categories) {
			if (category.id == user.category.id) {
				if (!category.address) {
					label.className += ' hidden';
				}
				
				break;
			}
		}

		let input = createElement('textarea');
		Object.assign(input, { name: 'address', spellcheck: false });
		
		input.onblur = () =&gt; {
			let address = input.value.split(/[\r\n,]/);
			
			for (let i = 0, length = address.length; i &lt; length; i ++) {
				address[i] = address[i].trim().replace(/\s+/g, ' ');
				
				if (!address[i]) {
					address.splice(i, 1);
					i --, length --;
				}
			}
			
			input.value = address.join(', ');
			
			if (!input.value || !user.address || input.value.toLowerCase() != user.address.toLowerCase()) {
				latitude = null, longitude = null;
			}
		}
		
		input.value = user.address || '';
		
		label.append(input);
		
		form.append(label);
	}
	
	{
		let label = createElement('label', null, createElement('span', null, __('Website')));
		
		let input = createElement('input');
		Object.assign(input, { type: 'text', name: 'website', spellcheck: false });
		
		input.onblur = () =&gt; {
			input.value = input.value.trim();

			if (input.nextSibling) {
				input.nextSibling.remove();
			}
			
			let url = input.value;
			
			if (!url || isValidURL(url)) {
				return;
			}
			
			if (!~url.indexOf('://') &amp;&amp; isValidURL('http://' + url)) {
				input.value = 'http://' + url;
			} else {
				label.append(createElement('span', 'error', __('invalid URL')));
			}
		}
		
		input.value = user.website || '';
		
		label.append(input);
		
		form.append(label);
	}
	
	view.content.append(form);

	view.register('confirm', () =&gt; {
		if (form.querySelector('.error')) {
			api.alert(__('You cannot update your profile yet, there are one or more errors.'));
			return;
		}
		
		let elements = form.querySelectorAll('[name]');
		
		let data = {};
		
		for (let element of elements) {
			data[element.name] = element.value;
		}
		
		if (data.username[0] == '@') {
			data.username = data.username.substr(1);
		}
		
		if (!data.email &amp;&amp; user.email &amp;&amp; ~user.email.toLowerCase().indexOf('@privaterelay.appleid.com')) {
			delete data.email;
		}

		Object.assign(data, { latitude: latitude || '', longitude: longitude || '' });
		
		let username = user.username;
		
		api.post({
			url: '/self',
			data: data,
			success: (result) =&gt; {
				Object.assign(user, result.user);
				
				let elements = document.querySelectorAll('.user.user-' + user.id + ' span.name');
				
				for (let element of elements) {
					element.innerHTML = encodeHTML(user.name || user.username);
				}
				
				router.back(false);
			}
		});
		
		if (avatar !== undefined) {
			if (avatar) {
				api.post({
					url: '/self/avatar',
					data: { image: avatar },
					success: (result) =&gt; saveAvatar(result.avatar)
				});
			} else {
				api.delete({
					url: '/self/avatar',
					success: (result) =&gt; saveAvatar(result.avatar)
				});
			}
			
			function saveAvatar (avatar) {
				user.avatar = avatar;
				
				preload(avatar, (url) =&gt; {
					let elements = document.querySelectorAll('.profile-avatar, .profile.user-' + user.id + ' .avatar, .user.user-' + user.id + ' .avatar');
					
					for (let element of elements) {
						element.style.backgroundImage = 'url(' + url + ')';
					}
				});
			}
		}
		
		cache.delete('/user/' + user.id);
	});
	
	return view;
});

router.register('/user/$id/followers', (values) =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}

	let view = createView({
		titleBar: __('Followers'), hideMainTitleBar: true, hideBottomMenu: true, buttons: ['close'], spinner: true
	});
	
	let content = createElement('div', 'user-list feature-follow');
	
	view.content.append(content);
	
	let req, cursor;
	
	function fetchFollowers () {
		req = api.get({
			url: '/user/' + values.id + '/followers',
			data: { cursor: cursor },
			success: (result) =&gt; {
				if (!cursor) {
					content.innerHTML = '';
				}
				
				if (!result.followers.length) {
					content.append(createElement('div', 'no-content', __('%s has no followers yet.', getUserName(values.id))));
				}
				
				for (let user of result.followers) {
					content.append(renderUser(user, ['follow', 'pending', 'unfollow']));
				}
				
				cursor = result.cursor || null;
				
				view.hideSpinner();
				
				if (result.more) {
					view.enableInfiniteScrolling();
					
					if (view == window.activeView &amp;&amp; view.content.offsetHeight &lt; window.innerHeight) {
						setTimeout(fetchFollowers, 0);
					}
				} else {
					view.disableInfiniteScrolling();
				}					
			},
			complete: () =&gt; req = null
		});
	}
	
	view.register('load', () =&gt; {
		fetchFollowers();
	});
	
	view.register('infinite-scroll', () =&gt; {
		if (cursor &amp;&amp; !req) {
			fetchFollowers();
		}
	});
	
	return view;
});

router.register('/user/$id/following', (values) =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}

	let view = createView({
		titleBar: __('Following'), hideMainTitleBar: true, hideBottomMenu: true, buttons: ['close'], spinner: true
	});
	
	let content = createElement('div', 'user-list feature-follow');
	
	view.content.append(content);
	
	let req, cursor;
	
	function fetchFollowing () {
		req = api.get({
			url: '/user/' + values.id + '/following',
			data: { cursor: cursor },
			success: (result) =&gt; {
				if (!cursor) {
					content.innerHTML = '';
				}
				
				if (!result.following.length) {
					content.append(createElement('div', 'no-content', __('%s isn\'t following anyone yet.', getUserName(values.id))));
				}
				
				for (let user of result.following) {
					content.append(renderUser(user, ['follow', 'pending', 'unfollow']));
				}
				
				cursor = result.cursor || null;
				
				view.hideSpinner();
				
				if (result.more) {
					view.enableInfiniteScrolling();
					
					if (view == window.activeView &amp;&amp; view.content.offsetHeight &lt; window.innerHeight) {
						setTimeout(fetchFollowing, 0);
					}
				} else {
					view.disableInfiniteScrolling();
				}					
			},
			complete: () =&gt; {
				req = null;
			}
		});
	}
	
	view.register('load', () =&gt; {
		fetchFollowing();
	});
	
	view.register('infinite-scroll', () =&gt; {
		if (cursor &amp;&amp; !req) {
			fetchFollowing();
		}
	});
	
	return view;
});

router.register('/self/pending-followers', () =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}
	
	let view = createView({
		titleBar: __('Pending followers'), hideMainTitleBar: true, hideBottomMenu: true, buttons: ['close'], spinner: true
	});
	
	let content = createElement('div');
	content.className = 'user-list feature-pending', view.content.append(content);
	
	let req, cursor;
	
	function fetchPending () {
		req = api.get({
			url: '/self/pending-followers',
			data: { cursor: cursor },
			success: (result) =&gt; {
				if (!cursor) {
					content.innerHTML = '';
				}
				
				if (!result.pending.length) {
					content.append(createElement('div', 'no-content', __('You have no pending follow requests.')));
				}
				
				for (let user of result.pending) {
					content.append(renderPending(user));

				}
				
				cursor = result.cursor || null;
				
				view.hideSpinner();
				
				if (result.more) {
					view.enableInfiniteScrolling();
					
					if (view == window.activeView &amp;&amp; view.content.offsetHeight &lt; window.innerHeight) {
						setTimeout(fetchPending, 0);
					}
				} else {
					view.disableInfiniteScrolling();
				}					
			},
			complete: () =&gt; req = null
		});
	}
	
	function renderPending (user) {
		let element = createElement('div', 'user user-' + user.id + ' pending');
		
		if (user.verified) {
			element.className += ' verified';
		}
		
		let link = createElement('a');
		link.href = '/user/' + user.id;
		
		let avatar = createElement('span', 'avatar');
		
		preload(user.avatar, (url) =&gt; avatar.style.backgroundImage = 'url(' + user.avatar + ')');
		
		let label = createElement('span', 'name', user.name || user.username, createElement('span', 'verified-badge'));
		
		link.append(avatar, label);
		
		element.append(link);

		element.append(createElement('button', 'button-accept', createElement('span', null, __('Accept'))));

		element.append(createElement('button', 'button-decline', createElement('span', null, __('Decline'))));
		
		return element;
	}
	
	view.register('load', () =&gt; {
		fetchPending();
	});
	
	view.register('infinite-scroll', () =&gt; {
		if (cursor &amp;&amp; !reg) {
			fetchPending();
		}
	});
	
	return view;
});

router.register('/self/blocked-users', () =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}

	let view = createView({
		titleBar: __('Blocked users'), hideMainTitleBar: true, hideBottomMenu: true, buttons: ['close'], spinner: true
	});
	
	let content = createElement('div', 'user-list feature-unblock');
	
	view.content.append(content);
	
	let req, cursor;
	
	function fetchBlockedUsers () {
		req = api.get({
			url: '/self/blocked',
			data: { cursor: cursor },
			success: (result) =&gt; {
				if (!cursor) {
					content.innerHTML = '';
				}
				
				if (!result.blocked_users.length) {
					content.append(createElement('div', 'no-content', __('You haven\'t blocked any users.')));
				}
				
				for (let user of result.blocked_users) {
					content.append(renderUser(user, ['unblock']));
				}
				
				cursor = result.cursor || null;
				
				view.hideSpinner();
				
				if (result.more) {
					view.enableInfiniteScrolling();
					
					if (view == window.activeView &amp;&amp; view.content.offsetHeight &lt; window.innerHeight) {
						setTimeout(fetchBlockedUsers, 0);
					}
				} else {
					view.disableInfiniteScrolling();
				}					
			},
			complete: () =&gt; req = null
		});
	}
	
	view.register('load', () =&gt; {
		fetchBlockedUsers();
	});
	
	view.register('infinite-scroll', () =&gt; {
		if (cursor &amp;&amp; !reg) {
			fetchBlockedUsers();
		}
	});
	
	return view;
});

router.register('/profile/delete', () =&gt; {
	if (!config.user) {
		if (!config.bot) {
			router.redirect('/login', { url: router.getURL() });
		} else {
			router.noIndex();
		}

		return;
	}

	let view = createView({
		titleBar: __('Delete your profile'), hideMainTitleBar: true, hideBottomMenu: true,  buttons: ['close'], spinner: true
	});
	
	api.get({
		url: '/self/delete',
		success: () =&gt; {
			view.content.append(createElement('p', null, __('Are you sure you want to delete your account?')));
			
			{
				let button = createElement('button', 'button', __('Delete profile'));
				
				button.onclick = () =&gt; {
					api.post({
						url: '/self/delete',
						success: (data) =&gt; api.confirm(__('Please confirm for the second and final time that you want to delete your profile.'), () =&gt; deleteProfile(data), router.back),
						error: alert
					});
					
					view.spinner();
				}
				
				view.content.append(button);
			}
			
			{
				let button = createElement('button', 'button', __('Cancel'));
				
				button.onclick = router.back;
				
				view.content.append(button);
			}
		},
		error: (error) =&gt; {
			view.content.append(createElement('p', null, error));
			
			let button = createElement('button', 'button', __('Close'));
			
			button.onclick = () =&gt; router.back();
			
			view.content.append(button);
		},
		complete: () =&gt; {
			view.hideSpinner();
		}
	});
	
	function deleteProfile (data) {
		api.delete({
			url: '/self/delete',
			data: data,
			success: () =&gt; {
				login.logoff(true);
				deleteMessage();
			}
		});
	}
	
	function deleteMessage () {
		let message = [
			__('Your profile has been deleted.') + ' ',
			__('A backup of your profile will be stored on our systems for another 30 days, but will not be visible to the public during this time.') + "\n\n",
			__('After 30 days, your profile will be permanently deleted from our systems.') + ' ',
			__('For the next hour, you may still receive email or push notifications that are already on the way.') + "\n\n",
			__('If you have accidentally deleted your profile or changed your mind, please contact contact@trainsiding.com as soon as possible.')
		];

		api.alert(message.join(''));
	}
	
	return view;
});
</pre></body></html>