mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-21 01:21:54 +08:00
add almost all validations from backend config
This commit is contained in:
parent
f3802c7793
commit
7d9e0c128a
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="markdown-editor">
|
||||
<div v-show="isEditing && !preview" class="ml-4">
|
||||
<!-- TODO validations for pageContent min/max length from validations state -->
|
||||
<v-textarea v-model="rawEdited" outlined :rows="rawEdited.split(/\r\n|\r|\n/g).length + 3" :rules="rules" />
|
||||
</div>
|
||||
<Markdown v-show="!isEditing" :raw="raw" class="ml-4" />
|
||||
|
@ -67,8 +67,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
import { Component, State, Vue } from 'nuxt-property-decorator';
|
||||
import Dropdown, { Control } from '~/components/layouts/Dropdown.vue';
|
||||
import { RootState } from '~/store';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -128,11 +129,14 @@ export default class Header extends Vue {
|
||||
icon: 'mdi-book',
|
||||
title: this.$t('nav.new.project'),
|
||||
});
|
||||
controls.push({
|
||||
link: '/organizations/new',
|
||||
icon: 'mdi-account-group',
|
||||
title: this.$t('nav.new.organization'),
|
||||
});
|
||||
// TODO get user orgs count
|
||||
if (0 - 1 < this.validations.maxOrgCount) {
|
||||
controls.push({
|
||||
link: '/organizations/new',
|
||||
icon: 'mdi-account-group',
|
||||
title: this.$t('nav.new.organization'),
|
||||
});
|
||||
}
|
||||
return controls;
|
||||
}
|
||||
|
||||
@ -210,6 +214,9 @@ export default class Header extends Vue {
|
||||
});
|
||||
return controls;
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -7,7 +7,15 @@
|
||||
<v-card-title>{{ $t('channel.new.title') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="validForm">
|
||||
<v-text-field v-model.trim="form.name" :label="$t('channel.new.name')" :rules="[$util.$vc.require($t('channel.new.name'))]" />
|
||||
<v-text-field
|
||||
v-model.trim="form.name"
|
||||
:label="$t('channel.new.name')"
|
||||
:rules="[
|
||||
$util.$vc.require($t('channel.new.name')),
|
||||
$util.$vc.regex($t('channel.new.name'), validations.project.channels.regex),
|
||||
$util.$vc.maxLength(validations.project.channels.max),
|
||||
]"
|
||||
/>
|
||||
<v-card-subtitle class="pa-0 text-center">{{ $t('channel.new.color') }}</v-card-subtitle>
|
||||
<v-item-group v-model="form.color" mandatory>
|
||||
<v-container>
|
||||
@ -44,9 +52,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component } from 'nuxt-property-decorator';
|
||||
import { Component, State } from 'nuxt-property-decorator';
|
||||
import { Color, ProjectChannel } from 'hangar-internal';
|
||||
import { HangarFormModal } from '../mixins';
|
||||
import { RootState } from '~/store';
|
||||
|
||||
@Component
|
||||
export default class NewChannelModal extends HangarFormModal {
|
||||
@ -82,9 +91,13 @@ export default class NewChannelModal extends HangarFormModal {
|
||||
}
|
||||
|
||||
createChannel() {
|
||||
// TODO check channel name against existing channels
|
||||
this.$emit('create', this.form);
|
||||
this.dialog = false;
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,14 +1,35 @@
|
||||
<template>
|
||||
<v-dialog v-model="dialog" max-width="500" persistent>
|
||||
<template #activator="{ on }">
|
||||
<v-btn icon class="primary" :class="activatorClass" v-on="on"><v-icon>mdi-plus</v-icon></v-btn>
|
||||
<v-btn v-if="pageRoots.length < validations.project.maxPageCount" icon class="primary" :class="activatorClass" v-on="on">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('page.new.title') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="modalForm" v-model="validForm">
|
||||
<v-text-field v-model.trim="form.name" filled :rules="[$util.$vc.require('Page name')]" :label="$t('page.new.name')" />
|
||||
<v-select v-model="form.parent" :items="pageRoots" filled clearable :label="$t('page.new.parent')" item-text="name" item-value="id" />
|
||||
<v-text-field
|
||||
v-model.trim="form.name"
|
||||
filled
|
||||
:rules="[
|
||||
$util.$vc.require($t('page.new.name')),
|
||||
$util.$vc.regex($t('page.new.name'), validations.project.pageName.regex),
|
||||
$util.$vc.maxLength(validations.project.pageName.max),
|
||||
$util.$vc.minLength(validations.project.pageName.min),
|
||||
]"
|
||||
:label="$t('page.new.name')"
|
||||
/>
|
||||
<v-select
|
||||
v-model="form.parent"
|
||||
:items="pageRoots"
|
||||
filled
|
||||
clearable
|
||||
hide-details
|
||||
:label="$t('page.new.parent')"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-end">
|
||||
@ -20,10 +41,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop } from 'nuxt-property-decorator';
|
||||
import { Component, Prop, State } from 'nuxt-property-decorator';
|
||||
import { PropType } from 'vue';
|
||||
import { HangarProjectPage } from 'hangar-internal';
|
||||
import { HangarFormModal } from '~/components/mixins';
|
||||
import { RootState } from '~/store';
|
||||
|
||||
@Component
|
||||
export default class NewPageModal extends HangarFormModal {
|
||||
@ -42,6 +64,7 @@ export default class NewPageModal extends HangarFormModal {
|
||||
return this.flatDeep(this.pages);
|
||||
}
|
||||
|
||||
// TODO these should be sorted into somewhat of an order
|
||||
flatDeep(pages: HangarProjectPage[]): HangarProjectPage[] {
|
||||
let ps: HangarProjectPage[] = [];
|
||||
for (const page of pages) {
|
||||
@ -70,5 +93,8 @@ export default class NewPageModal extends HangarFormModal {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
}
|
||||
</script>
|
||||
|
@ -527,7 +527,9 @@ const msgs: LocaleMessageObject = {
|
||||
},
|
||||
validation: {
|
||||
required: '{0} is required',
|
||||
maxLength: 'Max length is {0}',
|
||||
maxLength: 'Maximum length is {0}',
|
||||
minLength: 'Minimum length is {0}',
|
||||
invalidFormat: '{0} is invalid',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -115,12 +115,11 @@ import Tag from '~/components/Tag.vue';
|
||||
import DonationModal from '~/components/donation/DonationModal.vue';
|
||||
import MemberList from '~/components/MemberList.vue';
|
||||
import Markdown from '~/components/Markdown.vue';
|
||||
import NewPageModal from '~/components/modals/pages/NewPageModal.vue';
|
||||
import { DocPageMixin } from '~/components/mixins';
|
||||
import ProjectPageList from '~/components/projects/ProjectPageList.vue';
|
||||
|
||||
@Component({
|
||||
components: { ProjectPageList, NewPageModal, Markdown, MemberList, DonationModal, MarkdownEditor, Tag },
|
||||
components: { ProjectPageList, Markdown, MemberList, DonationModal, MarkdownEditor, Tag },
|
||||
})
|
||||
export default class DocsPage extends DocPageMixin {
|
||||
async asyncData({ $api, params, $util }: Context) {
|
||||
|
@ -40,6 +40,9 @@
|
||||
dense
|
||||
hide-details
|
||||
filled
|
||||
clearable
|
||||
:counter="validations.project.keywords.max"
|
||||
:rules="[$util.$vc.maxLength(validations.project.keywords.max)]"
|
||||
:delimiters="[' ', ',', '.']"
|
||||
prepend-inner-icon="mdi-file-word-box"
|
||||
/>
|
||||
@ -56,7 +59,7 @@
|
||||
filled
|
||||
prepend-inner-icon="mdi-home-search"
|
||||
:label="$t('project.new.step3.homepage')"
|
||||
:rules="[$util.$vc.url(false)]"
|
||||
:rules="[$util.$vc.url]"
|
||||
/>
|
||||
</div>
|
||||
<v-divider />
|
||||
@ -71,7 +74,7 @@
|
||||
filled
|
||||
prepend-inner-icon="mdi-bug"
|
||||
:label="$t('project.new.step3.issues')"
|
||||
:rules="[$util.$vc.url(false)]"
|
||||
:rules="[$util.$vc.url]"
|
||||
/>
|
||||
</div>
|
||||
<v-divider />
|
||||
@ -86,7 +89,7 @@
|
||||
filled
|
||||
prepend-inner-icon="mdi-source-branch"
|
||||
:label="$t('project.new.step3.source')"
|
||||
:rules="[$util.$vc.url(false)]"
|
||||
:rules="[$util.$vc.url]"
|
||||
/>
|
||||
</div>
|
||||
<v-divider />
|
||||
@ -101,7 +104,7 @@
|
||||
filled
|
||||
prepend-inner-icon="mdi-face-agent"
|
||||
:label="$t('project.new.step3.support')"
|
||||
:rules="[$util.$vc.url(false)]"
|
||||
:rules="[$util.$vc.url]"
|
||||
/>
|
||||
</div>
|
||||
<v-divider />
|
||||
@ -137,7 +140,7 @@
|
||||
dense
|
||||
clearable
|
||||
filled
|
||||
:rules="[$util.$vc.url(false)]"
|
||||
:rules="[$util.$vc.url]"
|
||||
:label="$t('project.settings.licenceUrl')"
|
||||
/>
|
||||
</v-col>
|
||||
|
@ -21,6 +21,7 @@
|
||||
<v-btn color="warning" block @click="reset">{{ $t('general.reset') }}</v-btn>
|
||||
</v-col>
|
||||
<v-col :md="isFile ? 4 : 6" sm="6" cols="12">
|
||||
<!-- TODO validate version string against existing versions. complex because they only have to be unique per-platform -->
|
||||
<v-text-field
|
||||
v-model="pendingVersion.versionString"
|
||||
:hide-details="isFile"
|
||||
@ -28,7 +29,10 @@
|
||||
:disabled="isFile"
|
||||
:autofocus="!isFile"
|
||||
filled
|
||||
:rules="[$util.$vc.require('Version string')]"
|
||||
:rules="[
|
||||
$util.$vc.require($t('version.new.form.versionString')),
|
||||
$util.$vc.regex($t('version.new.form.versionString'), validations.version.regex),
|
||||
]"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col v-if="isFile" md="4" sm="6" cols="12">
|
||||
@ -48,7 +52,7 @@
|
||||
v-model="pendingVersion.externalUrl"
|
||||
:label="$t('version.new.form.externalUrl')"
|
||||
filled
|
||||
:rules="[$util.$vc.require('External URL')]"
|
||||
:rules="[$util.$vc.require($t('version.new.form.externalUrl'))]"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@ -72,7 +76,7 @@
|
||||
<v-col class="flex-grow-0 pl-0 pr-4" align-self="center">
|
||||
<NewChannelModal @create="addChannel">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn color="info" v-bind="attrs" v-on="on">
|
||||
<v-btn v-if="channels.length < validations.project.maxChannelCount" color="info" v-bind="attrs" v-on="on">
|
||||
{{ $t('version.new.form.addChannel') }}
|
||||
<v-icon right>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
@ -167,7 +171,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component } from 'nuxt-property-decorator';
|
||||
import { Component, State } from 'nuxt-property-decorator';
|
||||
import remove from 'lodash-es/remove';
|
||||
import { IPlatform, PendingVersion, ProjectChannel } from 'hangar-internal';
|
||||
import { ProjectNamespace } from 'hangar-api';
|
||||
@ -337,6 +341,9 @@ export default class ProjectVersionsNewPage extends HangarProjectMixin {
|
||||
this.file = null;
|
||||
this.pendingVersion = null;
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
@ -48,7 +48,7 @@
|
||||
v-model.trim="taglineForm"
|
||||
counter="100"
|
||||
:label="$t('author.taglineLabel')"
|
||||
:rules="[$util.$vc.require($t('author.taglineLabel')), $util.$vc.maxLength(100)]"
|
||||
:rules="[$util.$vc.require($t('author.taglineLabel')), $util.$vc.maxLength(validations.userTagline.max)]"
|
||||
/>
|
||||
<template #other-btns>
|
||||
<v-btn color="info" text :loading="loading.resetTagline" :disabled="!user.tagline" @click.stop="resetTagline">{{
|
||||
@ -72,11 +72,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
import { Component, State, Vue } from 'nuxt-property-decorator';
|
||||
import { HangarUser } from 'hangar-internal';
|
||||
import { Context } from '@nuxt/types';
|
||||
import UserAvatar from '../components/UserAvatar.vue';
|
||||
import HangarModal from '~/components/modals/HangarModal.vue';
|
||||
import { RootState } from '~/store';
|
||||
|
||||
interface Button {
|
||||
icon: string;
|
||||
@ -150,6 +151,9 @@ export default class UserParentPage extends Vue {
|
||||
if (typeof user === 'undefined') return;
|
||||
return { user };
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
item-text="name"
|
||||
item-value="userId"
|
||||
:label="$t('project.new.step2.userselect')"
|
||||
:rules="[$util.$vc.require('Project owner')]"
|
||||
:rules="[$util.$vc.require($t('project.new.step2.userselect'))]"
|
||||
:append-icon="createAsIcon"
|
||||
/>
|
||||
</v-col>
|
||||
@ -57,7 +57,11 @@
|
||||
filled
|
||||
:error-messages="nameErrors"
|
||||
:label="$t('project.new.step2.projectname')"
|
||||
:rules="[$util.$vc.require('Name')]"
|
||||
:rules="[
|
||||
$util.$vc.require($t('project.new.step2.projectname')),
|
||||
$util.$vc.regex($t('project.new.step2.projectname'), validations.project.name.regex),
|
||||
$util.$vc.maxLength(validations.project.name.max),
|
||||
]"
|
||||
append-icon="mdi-form-textbox"
|
||||
/>
|
||||
</v-col>
|
||||
@ -68,7 +72,7 @@
|
||||
filled
|
||||
clearable
|
||||
:label="$t('project.new.step2.projectsummary')"
|
||||
:rules="[$util.$vc.require('Description')]"
|
||||
:rules="[$util.$vc.require($t('project.new.step2.projectsummary')), $util.$vc.maxLength(validations.project.desc.max)]"
|
||||
append-icon="mdi-card-text"
|
||||
/>
|
||||
</v-col>
|
||||
@ -82,7 +86,7 @@
|
||||
:label="$t('project.new.step2.projectcategory')"
|
||||
item-text="title"
|
||||
item-value="apiName"
|
||||
:rules="[$util.$vc.require('Category')]"
|
||||
:rules="[$util.$vc.require($t('project.new.step2.projectcategory'))]"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@ -109,6 +113,7 @@
|
||||
hide-details
|
||||
filled
|
||||
:label="$t('project.new.step3.homepage')"
|
||||
:rules="[$util.$vc.url]"
|
||||
append-icon="mdi-home-search"
|
||||
/>
|
||||
</v-col>
|
||||
@ -119,6 +124,7 @@
|
||||
hide-details
|
||||
filled
|
||||
:label="$t('project.new.step3.issues')"
|
||||
:rules="[$util.$vc.url]"
|
||||
append-icon="mdi-bug"
|
||||
/>
|
||||
</v-col>
|
||||
@ -129,6 +135,7 @@
|
||||
hide-details
|
||||
filled
|
||||
:label="$t('project.new.step3.source')"
|
||||
:rules="[$util.$vc.url]"
|
||||
append-icon="mdi-source-branch"
|
||||
/>
|
||||
</v-col>
|
||||
@ -139,6 +146,7 @@
|
||||
hide-details
|
||||
filled
|
||||
:label="$t('project.new.step3.support')"
|
||||
:rules="[$util.$vc.url]"
|
||||
append-icon="mdi-face-agent"
|
||||
/>
|
||||
</v-col>
|
||||
@ -164,7 +172,14 @@
|
||||
<v-text-field v-model.trim="form.settings.license.name" dense hide-details filled :label="$t('project.new.step3.customName')" />
|
||||
</v-col>
|
||||
<v-col cols="12" :md="isCustomLicense ? 12 : 6">
|
||||
<v-text-field v-model.trim="form.settings.license.url" dense hide-details filled :label="$t('project.new.step3.url')" />
|
||||
<v-text-field
|
||||
v-model.trim="form.settings.license.url"
|
||||
dense
|
||||
hide-details
|
||||
filled
|
||||
:label="$t('project.new.step3.url')"
|
||||
:rules="[$util.$vc.url]"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div class="text-h6 pt-5">
|
||||
@ -182,6 +197,7 @@
|
||||
dense
|
||||
hide-details
|
||||
filled
|
||||
clearable
|
||||
:delimiters="[' ', ',', '.']"
|
||||
:label="$t('project.new.step3.keywords')"
|
||||
append-icon="mdi-file-word-box"
|
||||
@ -228,7 +244,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch } from 'nuxt-property-decorator';
|
||||
import { Component, State, Vue, Watch } from 'nuxt-property-decorator';
|
||||
import { Context } from '@nuxt/types';
|
||||
import { ProjectOwner, ProjectSettingsForm } from 'hangar-internal';
|
||||
import { AxiosError } from 'axios';
|
||||
@ -293,6 +309,9 @@ export default class NewPage extends Vue {
|
||||
return ['MIT', 'Apache 2.0', 'GPL', 'LGPL', '(custom)'];
|
||||
}
|
||||
|
||||
@State((state: RootState) => state.validations)
|
||||
validations!: RootState['validations'];
|
||||
|
||||
async asyncData({ $api }: Context) {
|
||||
return {
|
||||
projectOwners: await $api.requestInternal<ProjectOwner[]>('projects/possibleOwners'),
|
||||
|
@ -219,13 +219,26 @@ const createUtil = ({ store, error, app: { i18n } }: Context) => {
|
||||
|
||||
$vc = {
|
||||
require: (name: TranslateResult = 'Field') => (v: string) => !!v || i18n.t('validation.required', [name]),
|
||||
maxLength: (maxLength: number) => (v: string) => (!!v && v.length <= maxLength) || i18n.t('validation.maxLength', [maxLength]),
|
||||
maxLength: (maxLength: number) => (v: string | any[]) => {
|
||||
return (
|
||||
((v === null || typeof v === 'string') && !v) ||
|
||||
(Array.isArray(v) && v.length === 0) ||
|
||||
v.length <= maxLength ||
|
||||
i18n.t('validation.maxLength', [maxLength])
|
||||
);
|
||||
},
|
||||
minLength: (minLength: number) => (v: string | any[]) => {
|
||||
return (
|
||||
((v === null || typeof v === 'string') && !v) ||
|
||||
(Array.isArray(v) && v.length === 0) ||
|
||||
v.length >= minLength ||
|
||||
i18n.t('validation.minLength', [minLength])
|
||||
);
|
||||
},
|
||||
requireNonEmptyArray: (name: TranslateResult = 'Field') => (v: any[]) => v.length > 0 || i18n.t('validation.required', [name]),
|
||||
url: (require: boolean) => (v: string) => {
|
||||
if (!require && !v) {
|
||||
return true;
|
||||
}
|
||||
return new RegExp((store.state as RootState).validations.urlRegex).test(v) || i18n.t('general.error.invalidUrl');
|
||||
url: (v: string) => !v || new RegExp((store.state as RootState).validations.urlRegex).test(v) || i18n.t('general.error.invalidUrl'),
|
||||
regex: (name: TranslateResult = 'Field', regexp: string) => (v: string) => {
|
||||
return !v || new RegExp(regexp).test(v) || i18n.t('validation.invalidFormat', [name]);
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user