<?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>