aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/views/Profile.vue
blob: eda6e4284630c40433a5d1dc2c8be6d6d3d54837 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129<
<?php
$TRANSLATIONS = array(
"Email sent" => "Correo electrónico enviado",
"Encryption" => "Cifrado",
"Unable to load list from App Store" => "No se pudo cargar la lista desde el App Store",
"Authentication error" => "Error de autenticación",
"Your full name has been changed." => "Se ha cambiado su nombre completo.",
"Unable to change full name" => "No se puede cambiar el nombre completo",
"Group already exists" => "El grupo ya existe",
"Unable to add group" => "No se pudo añadir el grupo",
"Email saved" => "Correo electrónico guardado",
"Invalid email" => "Correo electrónico no válido",
"Unable to delete group" => "No se pudo eliminar el grupo",
"Unable to delete user" => "No se pudo eliminar el usuario",
"Language changed" => "Idioma cambiado",
"Invalid request" => "Petición no válida",
"Admins can't remove themself from the admin group" => "Los administradores no se pueden eliminar a ellos mismos del grupo de administrador",
"Unable to add user to group %s" => "No se pudo añadir el usuario al grupo %s",
"Unable to remove user from group %s" => "No se pudo eliminar al usuario del grupo %s",
"Couldn't update app." => "No se pudo actualizar la aplicación.",
"Wrong password" => "Contraseña incorrecta",
"No user supplied" => "No se especificó un usuario",
"Please provide an admin recovery password, otherwise all user data will be lost" => "Por favor facilite una contraseña de recuperación de administrador, sino podrían perderse todos los datos de usuario",
"Wrong admin recovery password. Please check the password and try again." => "Contraseña de recuperación de administrador incorrecta. Por favor compruebe la contraseña e inténtelo de nuevo.",
"Back-end doesn't support password change, but the users encryption key was successfully updated." => "El back-end no soporta cambios de contraseña, pero la clave de cifrado del usuario ha sido actualizada satisfactoriamente.",
"Unable to change password" => "No se ha podido cambiar la contraseña",
"User Documentation" => "Documentación de usuario",
"Update to {appversion}" => "Actualizado a {appversion}",
"Disable" => "Desactivar",
"Enable" => "Activar",
"Please wait...." => "Espere, por favor....",
"Error while disabling app" => "Error mientras se desactivaba la aplicación",
"Error while enabling app" => "Error mientras se activaba la aplicación",
"Updating...." => "Actualizando....",
"Error while updating app" => "Error mientras se actualizaba la aplicación",
"Error" => "Error",
"Update" => "Actualizar",
"Updated" => "Actualizado",
"Select a profile picture" => "Seleccionar una imagen de perfil",
"Decrypting files... Please wait, this can take some time." => "Descifrando archivos... Espere por favor, esto puede llevar algo de tiempo.",
"undo" => "deshacer",
"Groups" => "Grupos",
"Group Admin" => "Administrador del Grupo",
"Delete" => "Eliminar",
"never" => "nunca",
"add group" => "añadir Grupo",
"A valid username must be provided" => "Se debe proporcionar un nombre de usuario válido",
"Error creating user" => "Error al crear usuario",
"A valid password must be provided" => "Se debe proporcionar una contraseña válida",
"Warning: Home directory for user \"{user}\" already exists" => "Atención: el directorio de inicio para el usuario \"{user}\" ya existe.",
"__language_name__" => "Español (México)",
"Everything (fatal issues, errors, warnings, info, debug)" => "Todo (Información, Avisos, Errores, debug y problemas fatales)",
"Info, warnings, errors and fatal issues" => "Información, Avisos, Errores y problemas fatales",
"Warnings, errors and fatal issues" => "Advertencias, errores y problemas fatales",
"Errors and fatal issues" => "Errores y problemas fatales",
"Fatal issues only" => "Problemas fatales solamente",
"Login" => "Iniciar sesión",
"Security Warning" => "Advertencia de seguridad",
"You are accessing %s via HTTP. We strongly suggest you configure your server to require using HTTPS instead." => "Está ingresando a %s vía HTTP. Le recomendamos encarecidamente que configure su servidor para que requiera HTTPS.",
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
<!--
  - @copyright Copyright (c) 2021 Christopher Ng <chrng8@gmail.com>
  -
  - @author Christopher Ng <chrng8@gmail.com>
  - @author Julius Härtl <jus@bitgrid.net>
  -
  - @license GNU AGPL version 3 or any later version
  -
  - This program is free software: you can redistribute it and/or modify
  - it under the terms of the GNU Affero General Public License as
  - published by the Free Software Foundation, either version 3 of the
  - License, or (at your option) any later version.
  -
  - This program is distributed in the hope that it will be useful,
  - but WITHOUT ANY WARRANTY; without even the implied warranty of
  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  - GNU Affero General Public License for more details.
  -
  - You should have received a copy of the GNU Affero General Public License
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -
  -->

<template>
	<div class="profile">
		<div class="profile__header">
			<div class="profile__header__container">
				<div class="profile__header__container__placeholder" />
				<h2 class="profile__header__container__displayname">
					{{ displayname || userId }}
					<a v-if="isCurrentUser"
						class="primary profile__header__container__edit-button"
						:href="settingsUrl">
						<PencilIcon class="pencil-icon"
							:size="16" />
						{{ t('core', 'Edit Profile') }}
					</a>
				</h2>
				<div v-if="status.icon || status.message"
					class="profile__header__container__status-text"
					:class="{ interactive: isCurrentUser }"
					@click.prevent.stop="openStatusModal">
					{{ status.icon }} {{ status.message }}
				</div>
			</div>
		</div>

		<div class="profile__wrapper">
			<div class="profile__content">
				<div class="profile__sidebar">
					<NcAvatar class="avatar"
						:class="{ interactive: isCurrentUser }"
						:user="userId"
						:size="180"
						:show-user-status="true"
						:show-user-status-compact="false"
						:disable-menu="true"
						:disable-tooltip="true"
						:is-no-user="!isUserAvatarVisible"
						@click.native.prevent.stop="openStatusModal" />

					<div class="user-actions">
						<!-- When a tel: URL is opened with target="_blank", a blank new tab is opened which is inconsistent with the handling of other URLs so we set target="_self" for the phone action -->
						<PrimaryActionButton v-if="primaryAction"
							class="user-actions__primary"
							:href="primaryAction.target"
							:icon="primaryAction.icon"
							:target="primaryAction.id === 'phone' ? '_self' :'_blank'">
							{{ primaryAction.title }}
						</PrimaryActionButton>
						<div class="user-actions__other">
							<!-- FIXME Remove inline styles after https://github.com/nextcloud/nextcloud-vue/issues/2315 is fixed -->
							<NcActions v-for="action in middleActions"
								:key="action.id"
								:default-icon="action.icon"
								style="
								background-position: 14px center;
								background-size: 16px;
								background-repeat: no-repeat;"
								:style="{
									backgroundImage: `url(${action.icon})`,
									...(colorMainBackground === '#181818' && { filter: 'invert(1)' })
								}">
								<NcActionLink :close-after-click="true"
									:icon="action.icon"
									:href="action.target"
									:target="action.id === 'phone' ? '_self' :'_blank'">
									{{ action.title }}
								</NcActionLink>
							</NcActions>
							<template v-if="otherActions">
								<NcActions :force-menu="true">
									<NcActionLink v-for="action in otherActions"
										:key="action.id"
										:class="{ 'icon-invert': colorMainBackground === '#181818' }"
										:close-after-click="true"
										:icon="action.icon"
										:href="action.target"
										:target="action.id === 'phone' ? '_self' :'_blank'">
										{{ action.title }}
									</NcActionLink>
								</NcActions>
							</template>
						</div>
					</div>
				</div>

				<div class="profile__blocks">
					<div v-if="organisation || role || address" class="profile__blocks-details">
						<div v-if="organisation || role" class="detail">
							<p>{{ organisation }} <span v-if="organisation && role">•</span> {{ role }}</p>
						</div>
						<div v-if="address" class="detail">
							<p>
								<MapMarkerIcon class="map-icon"
									:size="16" />
								{{ address }}
							</p>
						</div>
					</div>
					<template v-if="headline || biography || sections.length > 0">
						<div v-if="headline" class="profile__blocks-headline">
							<h3>{{ headline }}</h3>
						</div>
						<div v-if="biography" class="profile__blocks-biography">
							<p>{{ biography }}</p>
						</div>

						<!-- additional entries, use it with cautious -->
						<div v-for="(section, index) in sections"
							:ref="'section-' + index"
							:key="index"
							class="profile__additionalContent">
							<component :is="section($refs['section-'+index], userId)" :userId="userId" />
						</div>
					</template>
					<template v-else>
						<div class="profile__blocks-empty-info">
							<AccountIcon :size="60"
								fill-color="var(--color-text-maxcontrast)" />
							<h3>{{ emptyProfileMessage }}</h3>
							<p>{{ t('core', 'The headline and about sections will show up here') }}</p>
						</div>
					</template>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
import { getCurrentUser } from '@nextcloud/auth'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'

import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
import MapMarkerIcon from 'vue-material-design-icons/MapMarker.vue'
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
import AccountIcon from 'vue-material-design-icons/Account.vue'

import PrimaryActionButton from '../components/Profile/PrimaryActionButton.vue'

const status = loadState('core', 'status', {})
const {
	userId,
	displayname,
	address,
	organisation,
	role,
	headline,
	biography,
	actions,
	isUserAvatarVisible,
} = loadState('core', 'profileParameters', {
	userId: null,
	displayname: null,
	address: null,
	organisation: null,
	role: null,
	headline: null,
	biography: null,
	actions: [],
	isUserAvatarVisible: false,
})

export default {
	name: 'Profile',

	components: {
		AccountIcon,
		NcActionLink,
		NcActions,
		NcAvatar,
		MapMarkerIcon,
		PencilIcon,
		PrimaryActionButton,
	},

	data() {
		return {
			status,
			userId,
			displayname,
			address,
			organisation,
			role,
			headline,
			biography,
			actions,
			isUserAvatarVisible,
			sections: OCA.Core.ProfileSections.getSections(),
		}
	},

	computed: {
		isCurrentUser() {
			return getCurrentUser()?.uid === this.userId
		},

		allActions() {
			return this.actions
		},

		primaryAction() {
			if (this.allActions.length) {
				return this.allActions[0]
			}
			return null
		},

		middleActions() {
			if (this.allActions.slice(1, 4).length) {
				return this.allActions.slice(1, 4)
			}
			return null
		},

		otherActions() {
			if (this.allActions.slice(4).length) {
				return this.allActions.slice(4)
			}
			return null
		},

		settingsUrl() {
			return generateUrl('/settings/user')
		},

		colorMainBackground() {
			// For some reason the returned string has prepended whitespace
			return getComputedStyle(document.body).getPropertyValue('--color-main-background').trim()
		},

		emptyProfileMessage() {
			return this.isCurrentUser
				? t('core', 'You have not added any info yet')
				: t('core', '{user} has not added any info yet', { user: (this.displayname || this.userId) })
		},
	},

	mounted() {
		// Set the user's displayname or userId in the page title and preserve the default title of "Nextcloud" at the end
		document.title = `${this.displayname || this.userId} - ${document.title}`
		subscribe('user_status:status.updated', this.handleStatusUpdate)
	},

	beforeDestroy() {
		unsubscribe('user_status:status.updated', this.handleStatusUpdate)
	},

	methods: {
		handleStatusUpdate(status) {
			if (this.isCurrentUser && status.userId === this.userId) {
				this.status = status
			}
		},

		openStatusModal() {
			const statusMenuItem = document.querySelector('.user-status-menu-item__toggle')
			// Changing the user status is only enabled if you are the current user
			if (this.isCurrentUser) {
				if (statusMenuItem) {
					statusMenuItem.click()
				} else {
					showError(t('core', 'Error opening the user status modal, try hard refreshing the page'))
				}
			}
		},
	},
}
</script>

<style lang="scss">
// Override header styles
#header {
	background-color: transparent !important;
	background-image: none !important;
}

#content {
	padding-top: 0px;
}
</style>

<style lang="scss" scoped>
$profile-max-width: 1024px;
$content-max-width: 640px;

.profile {
	width: 100%;
	overflow-y: auto;

	&__header {
		position: sticky;
		height: 190px;
		top: -40px;
		background-color: var(--color-main-background-blur);
		backdrop-filter: var(--filter-background-blur);
		-webkit-backdrop-filter: var(--filter-background-blur);

		&__container {
			align-self: flex-end;
			width: 100%;
			max-width: $profile-max-width;
			margin: 0 auto;
			display: grid;
			grid-template-rows: max-content max-content;
			grid-template-columns: 240px 1fr;
			justify-content: center;

			&__placeholder {
				grid-row: 1 / 3;
			}

			&__displayname, &__status-text {
				color: var(--color-main-text);
			}

			&__displayname {
				width: $content-max-width;
				height: 45px;
				margin-top: 128px;
				// Override the global style declaration
				margin-bottom: 0;
				font-size: 30px;
				display: flex;
				align-items: center;
				cursor: text;

				&:not(:last-child) {
					margin-top: 100px;
					margin-bottom: 4px;
				}
			}

			&__edit-button {
				border: none;
				margin-left: 18px;
				margin-top: 2px;
				color: var(--color-primary-element);
				background-color: var(--color-primary-text);
				box-shadow: 0 0 0 2px var(--color-primary-text);
				border-radius: var(--border-radius-pill);
				padding: 0 18px;
				font-size: var(--default-font-size);
				height: 44px;
				line-height: 44px;
				font-weight: bold;

				&:hover,
				&:focus,
				&:active {
					color: var(--color-primary-element);
					background-color: var(--color-primary-element-light);
				}

				.pencil-icon {
					display: inline-block;
					vertical-align: middle;
					margin-top: 2px;
				}
			}

			&__status-text {
				width: max-content;
				max-width: $content-max-width;
				padding: 5px 10px;
				margin-left: -12px;
				margin-top: 2px;

				&.interactive {
					cursor: pointer;

					&:hover,
					&:focus,
					&:active {
						background-color: var(--color-main-background);
						color: var(--color-main-text);
						border-radius: var(--border-radius-pill);
						font-weight: bold;
						box-shadow: 0 3px 6px var(--color-box-shadow);
					}
				}
			}
		}
	}

	&__sidebar {
		position: sticky;
		top: var(--header-height);
		align-self: flex-start;
		padding-top: 20px;
		min-width: 220px;
		margin: -150px 20px 0 0;

		// Specificity hack is needed to override Avatar component styles
		&::v-deep .avatar.avatardiv, h2 {
			text-align: center;
			margin: auto;
			display: block;
			padding: 8px;
		}

		&::v-deep .avatar.avatardiv:not(.avatardiv--unknown) {
			background-color: var(--color-main-background) !important;
			box-shadow: none;
		}

		&::v-deep .avatar.avatardiv {
			.avatardiv__user-status {
				right: 14px;
				bottom: 14px;
				width: 34px;
				height: 34px;
				background-size: 28px;
				border: none;
				// Styles when custom status icon and status text are set
				background-color: var(--color-main-background);
				line-height: 34px;
				font-size: 20px;
			}
		}

		&::v-deep .avatar.interactive.avatardiv {
			.avatardiv__user-status {
				cursor: pointer;

				&:hover,
				&:focus,
				&:active {
					box-shadow: 0 3px 6px var(--color-box-shadow);
				}
			}
		}
	}

	&__wrapper {
		background-color: var(--color-main-background);
		min-height: 100%;
	}

	&__content {
		max-width: $profile-max-width;
		margin: 0 auto;
		display: flex;
		width: 100%;
	}

	&__blocks {
		margin: 18px 0 80px 0;
		display: grid;
		gap: 16px 0;
		width: $content-max-width;

		p, h3 {
			overflow-wrap: anywhere;
		}

		&-details {
			display: flex;
			flex-direction: column;
			gap: 2px 0;

			.detail {
				display: inline-block;
				color: var(--color-text-maxcontrast);

				p .map-icon {
					display: inline-block;
					vertical-align: middle;
				}
			}
		}

		&-headline {
			margin-top: 10px;

			h3 {
				font-weight: bold;
				font-size: 20px;
				margin: 0;
			}
		}

		&-biography {
			white-space: pre-line;
		}

		h3, p {
			cursor: text;
		}

		&-empty-info {
			margin-top: 80px;
			margin-right: 100px;
			display: flex;
			flex-direction: column;
			text-align: center;

			h3 {
				font-weight: bold;
				font-size: 18px;
				margin: 8px 0;
			}
		}
	}
}

@media only screen and (max-width: 1024px) {
	.profile {
		&__header {
			height: 250px;
			position: unset;

			&__container {
				grid-template-columns: unset;

				&__displayname {
					margin: 80px 20px 0px!important;
					height: 1em;
					width: unset;
					display: unset;
					text-align: center;
				}

				&__edit-button {
					width: fit-content;
					display: block;
					margin: 60px auto;
				}

				&__status-text {
					margin: 4px auto;
				}
			}
		}

		&__content {
			display: block;
		}

		&__blocks {
			width: unset;
			max-width: 600px;
			margin: 0 auto;
			padding: 20px 50px 50px 50px;

			&-empty-info {
				margin: 0;
			}
		}

		&__sidebar {
			margin: unset;
			position: unset;
		}
	}
}

.user-actions {
	display: flex;
	flex-direction: column;
	gap: 8px 0;
	margin-top: 20px;

	&__primary {
		margin: 0 auto;
	}

	&__other {
		display: flex;
		justify-content: center;
		gap: 0 4px;
		a {
			filter: var(--background-invert-if-dark);
		}
	}
}

.icon-invert {
	&::v-deep .action-link__icon {
		filter: invert(1);
	}
}
</style>