From 3cc2e125ee16de791c46c44c18d9e35bfe9c9089 Mon Sep 17 00:00:00 2001 From: Thomas Citharel <tcit@tcit.fr> Date: Mon, 18 Nov 2019 17:37:38 +0100 Subject: [PATCH] Close #311 and refactor identity edition pages with a mixin Signed-off-by: Thomas Citharel <tcit@tcit.fr> --- js/src/mixins/identityEdition.ts | 35 +++++++++++++ js/src/views/Account/Register.vue | 26 +++++----- .../views/Account/children/EditIdentity.vue | 49 +++++++------------ 3 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 js/src/mixins/identityEdition.ts diff --git a/js/src/mixins/identityEdition.ts b/js/src/mixins/identityEdition.ts new file mode 100644 index 000000000..d4f54f862 --- /dev/null +++ b/js/src/mixins/identityEdition.ts @@ -0,0 +1,35 @@ +import { Component, Mixins, Vue } from 'vue-property-decorator'; +import { Person } from '@/types/actor'; + +@Component +export default class IdentityEditionMixin extends Mixins(Vue) { + + identity = new Person(); + oldDisplayName: string | null = null; + + autoUpdateUsername(newDisplayName: string | null) { + const oldUsername = IdentityEditionMixin.convertToUsername(this.oldDisplayName); + + if (this.identity.preferredUsername === oldUsername) { + this.identity.preferredUsername = IdentityEditionMixin.convertToUsername(newDisplayName); + } + + this.oldDisplayName = newDisplayName; + } + + private static convertToUsername(value: string | null) { + if (!value) return ''; + + // https://stackoverflow.com/a/37511463 + return value.toLocaleLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/ /g, '_') + .replace(/[^a-z0-9_]/g, '') + ; + } + + validateUsername() { + return this.identity.preferredUsername === IdentityEditionMixin.convertToUsername(this.identity.preferredUsername); + } +} diff --git a/js/src/views/Account/Register.vue b/js/src/views/Account/Register.vue index d4b23a775..468d9c262 100644 --- a/js/src/views/Account/Register.vue +++ b/js/src/views/Account/Register.vue @@ -9,6 +9,10 @@ {{ $t('To achieve your registration, please create a first identity profile.')}} </b-message> <form v-if="!validationSent" @submit.prevent="submit"> + <b-field :label="$t('Display name')"> + <b-input aria-required="true" required v-model="identity.name" @input="autoUpdateUsername($event)"/> + </b-field> + <b-field :label="$t('Username')" :type="errors.preferred_username ? 'is-danger' : null" @@ -19,7 +23,7 @@ aria-required="true" required expanded - v-model="person.preferredUsername" + v-model="identity.preferredUsername" /> <p class="control"> <span class="button is-static">@{{ host }}</span> @@ -27,12 +31,8 @@ </b-field> </b-field> - <b-field :label="$t('Displayed name')"> - <b-input v-model="person.name"/> - </b-field> - <b-field :label="$t('Description')"> - <b-input type="textarea" v-model="person.summary"/> + <b-input type="textarea" v-model="identity.summary"/> </b-field> <p class="control has-text-centered"> @@ -45,7 +45,7 @@ <div v-if="validationSent && !userAlreadyActivated"> <b-message title="Success" type="is-success" closable="false"> <h2 class="title"> - {{ $t('Your account is nearly ready, {username}', { username: person.preferredUsername }) }} + {{ $t('Your account is nearly ready, {username}', { username: identity.preferredUsername }) }} </h2> <p> {{ $t('A validation email was sent to {email}', { email }) }} @@ -61,22 +61,22 @@ </template> <script lang="ts"> -import { Component, Prop, Vue } from 'vue-property-decorator'; -import { IPerson, Person } from '@/types/actor'; +import { Component, Prop } from 'vue-property-decorator'; +import { IPerson } from '@/types/actor'; import { IDENTITIES, REGISTER_PERSON } from '@/graphql/actor'; import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint'; import { RouteName } from '@/router'; import { changeIdentity } from '@/utils/auth'; -import { ICurrentUser } from '@/types/current-user.model'; +import { mixins } from 'vue-class-component'; +import identityEditionMixin from '@/mixins/identityEdition'; @Component -export default class Register extends Vue { +export default class Register extends mixins(identityEditionMixin) { @Prop({ type: String, required: true }) email!: string; @Prop({ type: Boolean, required: false, default: false }) userAlreadyActivated!: boolean; host?: string = MOBILIZON_INSTANCE_HOST; - person: IPerson = new Person(); errors: object = {}; validationSent: boolean = false; sendingValidation: boolean = false; @@ -94,7 +94,7 @@ export default class Register extends Vue { this.errors = {}; const { data } = await this.$apollo.mutate<{ registerPerson: IPerson }>({ mutation: REGISTER_PERSON, - variables: Object.assign({ email: this.email }, this.person), + variables: Object.assign({ email: this.email }, this.identity), update: (store, { data }) => { if (this.userAlreadyActivated) { const identitiesData = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES }); diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue index 34052b0b7..2bb8df295 100644 --- a/js/src/views/Account/children/EditIdentity.vue +++ b/js/src/views/Account/children/EditIdentity.vue @@ -7,13 +7,13 @@ <picture-upload v-model="avatarFile" class="picture-upload"></picture-upload> - <b-field :label="$t('Display name')"> + <b-field horizontal :label="$t('Display name')"> <b-input aria-required="true" required v-model="identity.name" @input="autoUpdateUsername($event)"/> </b-field> - <b-field :label="$t('Username')"> - <b-field> - <b-input aria-required="true" required v-model="identity.preferredUsername" :disabled="isUpdate"/> + <b-field horizontal custom-class="username-field" expanded :label="$t('Username')" :message="message"> + <b-field expanded> + <b-input aria-required="true" required v-model="identity.preferredUsername" :disabled="isUpdate" :use-html5-validation="!isUpdate" pattern="[a-z0-9_]+"/> <p class="control"> <span class="button is-static">@{{ getInstanceHost() }}</span> @@ -21,7 +21,7 @@ </b-field> </b-field> - <b-field :label="$t('Description')"> + <b-field horizontal :label="$t('Description')"> <b-input type="textarea" aria-required="false" v-model="identity.summary"/> </b-field> @@ -77,10 +77,14 @@ cursor: pointer; margin-top: 15px; } + + .username-field + .field { + margin-bottom: 0; + } </style> <script lang="ts"> -import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; +import { Component, Prop, Watch } from 'vue-property-decorator'; import { CREATE_PERSON, CURRENT_ACTOR_CLIENT, @@ -96,6 +100,8 @@ import { Dialog } from 'buefy/dist/components/dialog'; import { RouteName } from '@/router'; import { buildFileFromIPicture, buildFileVariable, readFileAsync } from '@/utils/image'; import { changeIdentity } from '@/utils/auth'; +import { mixins } from 'vue-class-component'; +import identityEditionMixin from '@/mixins/identityEdition'; @Component({ components: { @@ -108,18 +114,21 @@ import { changeIdentity } from '@/utils/auth'; }, }, }) -export default class EditIdentity extends Vue { +export default class EditIdentity extends mixins(identityEditionMixin) { @Prop({ type: Boolean }) isUpdate!: boolean; errors: string[] = []; identityName!: string | undefined; avatarFile: File | null = null; - identity = new Person(); - private oldDisplayName: string | null = null; private currentActor: IPerson | null = null; + get message() { + if (this.isUpdate) return null; + return this.$t('Only alphanumeric characters and underscores are supported.'); + } + @Watch('isUpdate') async isUpdateChanged () { this.resetFields(); @@ -153,16 +162,6 @@ export default class EditIdentity extends Vue { return this.createIdentity(); } - autoUpdateUsername(newDisplayName: string | null) { - const oldUsername = this.convertToUsername(this.oldDisplayName); - - if (this.identity.preferredUsername === oldUsername) { - this.identity.preferredUsername = this.convertToUsername(newDisplayName); - } - - this.oldDisplayName = newDisplayName; - } - /** * Delete an identity */ @@ -309,18 +308,6 @@ export default class EditIdentity extends Vue { } } - private convertToUsername(value: string | null) { - if (!value) return ''; - - // https://stackoverflow.com/a/37511463 - return value.toLocaleLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .replace(/ /g, '_') - .replace(/[^a-z0-9._]/g, '') - ; - } - private async buildVariables() { const avatarObj = buildFileVariable(this.avatarFile, 'avatar', `${this.identity.preferredUsername}'s avatar`); const res = Object.assign({}, this.identity, avatarObj);